diff options
author | Luuk van Baal <luukvbaal@gmail.com> | 2023-11-11 00:52:50 +0100 |
---|---|---|
committer | Luuk van Baal <luukvbaal@gmail.com> | 2023-11-17 15:10:15 +0100 |
commit | c4afb9788c4f139eb2e3b7aa4d6a6a20b67ba156 (patch) | |
tree | abc3a6da1906ea61f40aaacf3bdb813c27e19d64 | |
parent | ba58c6f8a44c9c37e97fce1d802dc5b5defceb3d (diff) | |
download | rneovim-c4afb9788c4f139eb2e3b7aa4d6a6a20b67ba156.tar.gz rneovim-c4afb9788c4f139eb2e3b7aa4d6a6a20b67ba156.tar.bz2 rneovim-c4afb9788c4f139eb2e3b7aa4d6a6a20b67ba156.zip |
refactor(sign): move legacy signs to extmarks
Problem: The legacy signlist data structures and associated functions are
redundant since the introduction of extmark signs.
Solution: Store signs defined through the legacy commands in a hashmap, placed
signs in the extmark tree. Replace signlist associated functions.
Usage of the legacy sign commands should yield no change in behavior with the
exception of:
- "orphaned signs" are now always removed when the line it is placed on is
deleted. This used to depend on the value of 'signcolumn'.
- It is no longer possible to place multiple signs with the same identifier
in a single group on multiple lines. This will now move the sign instead.
Moreover, both signs placed through the legacy sign commands and through
|nvim_buf_set_extmark()|:
- Will show up in both |sign-place| and |nvim_buf_get_extmarks()|.
- Are displayed by increasing sign identifier, left to right.
Extmark signs used to be ordered decreasingly as opposed to legacy signs.
33 files changed, 763 insertions, 1894 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 6970479a78..1b11d8571a 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2566,8 +2566,8 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts}) *nvim_buf_get_extmarks()* nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {*opts}) - Gets |extmarks| in "traversal order" from a |charwise| region defined by - buffer positions (inclusive, 0-indexed |api-indexing|). + Gets |extmarks| (including |signs|) in "traversal order" from a |charwise| + region defined by buffer positions (inclusive, 0-indexed |api-indexing|). Region can be given as (row,col) tuples, or valid extmark ids (whose positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ae97772b66..818f0e5a57 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -80,6 +80,14 @@ The following changes may require adaptations in user config or plugins. the option is set (e.g. using |:set| or |nvim_set_option_value()|) without a scope, which means they now behave the same way as string options. +• Signs placed through the legacy |sign-commands| are now stored and displayed + as |extmarks| internally. Along with the following changes: + • A sign placed twice in the same group with the same identifier will be moved. + • Legacy signs are always deleted along with the line it is placed on. + • Legacy and extmark signs will show up in both |:sign-place-list| and |nvim_buf_get_extmarks()|. + • Legacy and extmark signs are displayed and listed with the same priority: + line number -> priority -> sign id -> recently placed + ============================================================================== NEW FEATURES *news-features* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 07326c8c13..f47093782c 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5624,12 +5624,6 @@ A jump table for the options with a short description can be found at |Q_op|. "number" display signs in the 'number' column. If the number column is not present, then behaves like "auto". - Note regarding "orphaned signs": with signcolumn numbers higher than - 1, deleting lines will also remove the associated signs automatically, - in contrast to the default Vim behavior of keeping and grouping them. - This is done in order for the signcolumn appearance not appear weird - during line deletion. - *'smartcase'* *'scs'* *'nosmartcase'* *'noscs'* 'smartcase' 'scs' boolean (default off) global diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index 8ecfadce17..733b0fe212 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -53,11 +53,10 @@ If 'cursorline' is enabled, then the CursorLineSign highlight group is used 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_place()| function. Each sign identifier should be a unique number. +Placing the same identifier twice will move the previously placed sign. 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. @@ -77,9 +76,8 @@ When two signs with the same priority are present, and one has an icon or text in the signcolumn while the other has line highlighting, then both are displayed. -When the line on which the sign is placed is deleted, the sign is moved to the -next line (or the last line of the buffer, if there is no next line). When -the delete is undone the sign does not move back. +When the line on which the sign is placed is deleted, the sign is removed along +with it. ============================================================================== 2. Commands *sign-commands* *:sig* *:sign* @@ -177,11 +175,8 @@ See |sign_place()| for the equivalent Vim script function. space is ignored. The sign is remembered under {id}, this can be used for - further manipulation. {id} must be a number. - It's up to the user to make sure the {id} is used only once in - each file (if it's used several times unplacing will also have - to be done several times and making changes may not work as - expected). + further manipulation. {id} must be a number. Placing the + same {id} multiple times will move the sign. The following optional sign attributes can be specified before "file=": diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 05d7e5feb9..fb23535c76 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -340,6 +340,7 @@ Shell: Signs: Signs are removed if the associated line is deleted. + Signs placed twice with the same identifier in the same group are moved. Startup: |-e| and |-es| invoke the same "improved Ex mode" as -E and -Es. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 2142a429a2..70a8b0aec2 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -317,8 +317,8 @@ function vim.api.nvim_buf_get_commands(buffer, opts) end --- @return integer[] function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end ---- Gets `extmarks` in "traversal order" from a `charwise` region defined by ---- buffer positions (inclusive, 0-indexed `api-indexing`). +--- Gets `extmarks` (including `signs`) in "traversal order" from a `charwise` +--- region defined by buffer positions (inclusive, 0-indexed `api-indexing`). --- Region can be given as (row,col) tuples, or valid extmark ids (whose --- positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) --- respectively, thus the following are equivalent: diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 19ae786177..6d693ca036 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -5923,12 +5923,6 @@ vim.go.siso = vim.go.sidescrolloff --- "number" display signs in the 'number' column. If the number --- column is not present, then behaves like "auto". --- ---- Note regarding "orphaned signs": with signcolumn numbers higher than ---- 1, deleting lines will also remove the associated signs automatically, ---- in contrast to the default Vim behavior of keeping and grouping them. ---- This is done in order for the signcolumn appearance not appear weird ---- during line deletion. ---- --- @type string vim.o.signcolumn = "auto" vim.o.scl = vim.o.signcolumn diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 84b89a7428..efcdd0e7f7 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -22,6 +22,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/pos.h" +#include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -81,7 +82,7 @@ Dictionary nvim_get_namespaces(void) return retval; } -const char *describe_ns(NS ns_id) +const char *describe_ns(NS ns_id, const char *unknown) { String name; handle_T id; @@ -90,7 +91,7 @@ const char *describe_ns(NS ns_id) return name.data; } }) - return "(UNKNOWN PLUGIN)"; + return unknown; } // Is the Namespace in use? @@ -314,8 +315,8 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return extmark_to_array(&extmark, false, details, hl_name); } -/// Gets |extmarks| in "traversal order" from a |charwise| region defined by -/// buffer positions (inclusive, 0-indexed |api-indexing|). +/// Gets |extmarks| (including |signs|) in "traversal order" from a |charwise| +/// region defined by buffer positions (inclusive, 0-indexed |api-indexing|). /// /// Region can be given as (row,col) tuples, or valid extmark ids (whose /// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) @@ -750,7 +751,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } if (HAS_KEY(opts, set_extmark, sign_text)) { - VALIDATE_S(init_sign_text(&decor.sign_text, opts->sign_text.data), + VALIDATE_S(init_sign_text(NULL, &decor.sign_text, opts->sign_text.data), "sign_text", "", { goto error; }); @@ -1150,41 +1151,6 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in }); } } -// adapted from sign.c:sign_define_init_text. -// TODO(lewis6991): Consider merging -static int init_sign_text(char **sign_text, char *text) -{ - char *s; - - char *endp = text + (int)strlen(text); - - // Count cells and check for non-printable chars - int cells = 0; - for (s = text; s < endp; s += utfc_ptr2len(s)) { - if (!vim_isprintc(utf_ptr2char(s))) { - break; - } - cells += utf_ptr2cells(s); - } - // Currently must be empty, one or two display cells - if (s != endp || cells > 2) { - return FAIL; - } - if (cells < 1) { - return OK; - } - - // Allocate one byte more if we need to pad up - // with a space. - size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0)); - *sign_text = xstrnsave(text, len); - - if (cells == 1) { - STRCPY(*sign_text + len - 1, " "); - } - - return OK; -} VirtText parse_virt_text(Array chunks, Error *err, int *width) { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1d5898c488..88cbdbe1de 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2196,13 +2196,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * } } if (statuscol_lnum) { - HlPriId line = { 0 }; - HlPriId cul = { 0 }; - HlPriId num = { 0 }; + int line_id = 0; + int cul_id = 0; + int num_id = 0; linenr_T lnum = statuscol_lnum; - int num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs, &num, &line, &cul); - decor_redraw_signs(wp->w_buffer, lnum - 1, &num_signs, sattrs, &num, &line, &cul); wp->w_scwidth = win_signcol_count(wp); + decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id); statuscol.sattrs = sattrs; statuscol.foldinfo = fold_info(wp, lnum); @@ -2215,9 +2214,9 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR); } - statuscol.sign_cul_id = statuscol.use_cul ? cul.hl_id : 0; - if (num.hl_id) { - stc_hl_id = num.hl_id; + statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0; + if (num_id) { + stc_hl_id = num_id; } else if (statuscol.use_cul) { stc_hl_id = HLF_CLN + 1; } else if (wp->w_p_rnu) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 230cd4cef7..8c522a6c44 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -917,7 +917,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) buf_init_changedtick(buf); } uc_clear(&buf->b_ucmds); // clear local user commands - buf_delete_signs(buf, "*"); // delete any signs extmark_free_all(buf); // delete any extmarks map_clear_mode(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs @@ -4021,62 +4020,6 @@ char *buf_spname(buf_T *buf) return NULL; } -static int buf_signcols_inner(buf_T *buf, int maximum) -{ - sign_entry_T *sign; // a sign in the sign list - int signcols = 0; - int linesum = 0; - linenr_T curline = 0; - - buf->b_signcols.sentinel = 0; - - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->se_lnum > curline) { - // Counted all signs, now add extmark signs - if (curline > 0) { - linesum += decor_signcols(buf, &decor_state, (int)curline - 1, (int)curline - 1, - maximum - linesum); - } - curline = sign->se_lnum; - if (linesum > signcols) { - signcols = linesum; - buf->b_signcols.sentinel = curline; - if (signcols >= maximum) { - return maximum; - } - } - linesum = 0; - } - if (sign->se_has_text_or_icon) { - linesum++; - } - } - - if (curline > 0) { - linesum += decor_signcols(buf, &decor_state, (int)curline - 1, (int)curline - 1, - maximum - linesum); - } - if (linesum > signcols) { - signcols = linesum; - if (signcols >= maximum) { - return maximum; - } - } - - // Check extmarks between signs - linesum = decor_signcols(buf, &decor_state, 0, (int)buf->b_ml.ml_line_count - 1, maximum); - - if (linesum > signcols) { - signcols = linesum; - buf->b_signcols.sentinel = curline; - if (signcols >= maximum) { - return maximum; - } - } - - return signcols; -} - /// Invalidate the signcolumn if needed after deleting /// signs between line1 and line2 (inclusive). /// @@ -4106,18 +4049,18 @@ void buf_signcols_del_check(buf_T *buf, linenr_T line1, linenr_T line2) /// /// @param buf buffer to check /// @param added sign being added -void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) +void buf_signcols_add_check(buf_T *buf, linenr_T lnum) { if (!buf->b_signcols.valid) { return; } - if (!added || !buf->b_signcols.sentinel) { + if (!buf->b_signcols.sentinel) { buf->b_signcols.valid = false; return; } - if (added->se_lnum == buf->b_signcols.sentinel) { + if (lnum == buf->b_signcols.sentinel) { if (buf->b_signcols.size == buf->b_signcols.max) { buf->b_signcols.max++; } @@ -4126,42 +4069,32 @@ void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) return; } - sign_entry_T *s; - - // Get first sign for added lnum - for (s = added; s->se_prev && s->se_lnum == s->se_prev->se_lnum; s = s->se_prev) {} - - // Count signs for lnum - int linesum = 1; - for (; s->se_next && s->se_lnum == s->se_next->se_lnum; s = s->se_next) { - linesum++; - } - linesum += decor_signcols(buf, &decor_state, (int)s->se_lnum - 1, (int)s->se_lnum - 1, - SIGN_SHOW_MAX - linesum); + int signcols = decor_signcols(buf, lnum - 1, lnum - 1, SIGN_SHOW_MAX); - if (linesum > buf->b_signcols.size) { - buf->b_signcols.size = linesum; - buf->b_signcols.max = linesum; - buf->b_signcols.sentinel = added->se_lnum; + if (signcols > buf->b_signcols.size) { + buf->b_signcols.size = signcols; + buf->b_signcols.max = signcols; + buf->b_signcols.sentinel = lnum; redraw_buf_later(buf, UPD_NOT_VALID); } } -int buf_signcols(buf_T *buf, int maximum) +int buf_signcols(buf_T *buf, int max) { // The maximum can be determined from 'signcolumn' which is window scoped so // need to invalidate signcols if the maximum is greater than the previous - // maximum. - if (maximum > buf->b_signcols.max) { + // (valid) maximum. + if (buf->b_signcols.max && max > buf->b_signcols.max) { buf->b_signcols.valid = false; } if (!buf->b_signcols.valid) { - int signcols = buf_signcols_inner(buf, maximum); + buf->b_signcols.sentinel = 0; + int signcols = decor_signcols(buf, 0, (int)buf->b_ml.ml_line_count - 1, max); // Check if we need to redraw if (signcols != buf->b_signcols.size) { buf->b_signcols.size = signcols; - buf->b_signcols.max = maximum; + buf->b_signcols.max = max; redraw_buf_later(buf, UPD_NOT_VALID); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index cf77cea621..73fad15239 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -708,7 +708,6 @@ struct file_buffer { // normally points to this, but some windows // may use a different synblock_T. - sign_entry_T *b_signlist; // list of placed signs struct { int size; // last calculated number of sign columns bool valid; // calculated sign columns is valid diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index b111b01fe9..48dffea24b 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -11,7 +11,7 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/pos.h" -#include "nvim/sign_defs.h" +#include "nvim/sign.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.c.generated.h" @@ -92,6 +92,8 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) } } +static int sign_add_id = 0; + void decor_add(buf_T *buf, int row, int row2, Decoration *decor, bool hl_id) { if (decor) { @@ -102,12 +104,12 @@ void decor_add(buf_T *buf, int row, int row2, Decoration *decor, bool hl_id) buf->b_virt_line_blocks++; } if (decor_has_sign(decor)) { + decor->sign_add_id = sign_add_id++; buf->b_signs++; } if (decor->sign_text) { buf->b_signs_with_text++; - // TODO(lewis6991): smarter invalidation - buf_signcols_add_check(buf, NULL); + buf_signcols_add_check(buf, row + 1); } } if (decor || hl_id) { @@ -152,6 +154,7 @@ void decor_clear(Decoration *decor) } kv_destroy(decor->virt_lines); xfree(decor->sign_text); + xfree(decor->sign_name); } void decor_free(Decoration *decor) @@ -429,107 +432,73 @@ next_mark: return attr; } -void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[], - HlPriId *num_id, HlPriId *line_id, HlPriId *cul_id) +/// Return the sign attributes on the currently refreshed row. +/// +/// @param[out] sattrs Output array for sign text and texthl id +/// @param[out] line_attr Highest priority linehl id +/// @param[out] cul_attr Highest priority culhl id +/// @param[out] num_attr Highest priority numhl id +void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id, + int *cul_id, int *num_id) { - if (!buf->b_signs) { - return; - } - - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, row, 0, itr); - - // TODO(bfredl): integrate with main decor loop. - if (!marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { + MarkTreeIter itr[1]; + if (!buf->b_signs || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { return; } MTPair pair; + int num_text = 0; + kvec_t(MTKey) signs = KV_INITIAL_VALUE; + // TODO(bfredl): integrate with main decor loop. while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if (mt_invalid(pair.start) || marktree_decor_level(pair.start) < kDecorLevelVisible) { - continue; - } - - Decoration *decor = pair.start.decor_full; - - if (!decor || !decor_has_sign(decor)) { - continue; + if (!mt_invalid(pair.start) && pair.start.decor_full && decor_has_sign(pair.start.decor_full)) { + pair.start.pos.row = row; + num_text += (pair.start.decor_full->sign_text != NULL); + kv_push(signs, pair.start); } - - decor_to_sign(decor, num_signs, sattrs, num_id, line_id, cul_id); } - while (true) { + while (itr->x) { MTKey mark = marktree_itr_current(itr); - if (mark.pos.row < 0 || mark.pos.row > row) { + if (mark.pos.row != row) { break; } - - if (mt_end(mark) || mt_invalid(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { - goto next_mark; - } - - Decoration *decor = mark.decor_full; - - if (!decor || !decor_has_sign(decor)) { - goto next_mark; + if (!mt_end(mark) && !mt_invalid(mark) && mark.decor_full && decor_has_sign(mark.decor_full)) { + num_text += (mark.decor_full->sign_text != NULL); + kv_push(signs, mark); } - - decor_to_sign(decor, num_signs, sattrs, num_id, line_id, cul_id); - -next_mark: marktree_itr_next(buf->b_marktree, itr); } -} -static void decor_to_sign(Decoration *decor, int *num_signs, SignTextAttrs sattrs[], - HlPriId *num_id, HlPriId *line_id, HlPriId *cul_id) -{ - if (decor->sign_text) { - int j; - for (j = (*num_signs); j > 0; j--) { - if (sattrs[j - 1].priority >= decor->priority) { - break; + if (kv_size(signs)) { + int width = (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u') ? 1 : wp->w_scwidth; + int idx = MIN(width, num_text) - 1; + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); + + for (size_t i = 0; i < kv_size(signs); i++) { + Decoration *decor = kv_A(signs, i).decor_full; + if (idx >= 0 && decor->sign_text) { + sattrs[idx].text = decor->sign_text; + sattrs[idx--].hl_id = decor->sign_hl_id; } - if (j < SIGN_SHOW_MAX) { - sattrs[j] = sattrs[j - 1]; + if (*num_id == 0) { + *num_id = decor->number_hl_id; + } + if (*line_id == 0) { + *line_id = decor->line_hl_id; + } + if (*cul_id == 0) { + *cul_id = decor->cursorline_hl_id; } } - if (j < SIGN_SHOW_MAX) { - sattrs[j] = (SignTextAttrs) { - .text = decor->sign_text, - .hl_id = decor->sign_hl_id, - .priority = decor->priority - }; - (*num_signs)++; - } - } - - struct { HlPriId *dest; int hl; } cattrs[] = { - { line_id, decor->line_hl_id }, - { num_id, decor->number_hl_id }, - { cul_id, decor->cursorline_hl_id }, - { NULL, -1 }, - }; - for (int i = 0; cattrs[i].dest; i++) { - if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) { - *cattrs[i].dest = (HlPriId) { - .hl_id = cattrs[i].hl, - .priority = decor->priority - }; - } + kv_destroy(signs); } } // Get the maximum required amount of sign columns needed between row and // end_row. -int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) +int decor_signcols(buf_T *buf, int row, int end_row, int max) { - int count = 0; // count for the number of signs on a given row - int count_remove = 0; // how much to decrement count by when iterating marks for a new row - int signcols = 0; // highest value of count - int currow = -1; // current row - if (max <= 1 && buf->b_signs_with_text >= (size_t)max) { return max; } @@ -538,66 +507,41 @@ int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) return 0; } - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, 0, -1, itr); - while (true) { - MTKey mark = marktree_itr_current(itr); - if (mark.pos.row < 0 || mark.pos.row > end_row) { - break; - } - - if ((mark.pos.row < row && mt_end(mark)) - || marktree_decor_level(mark) < kDecorLevelVisible - || !mark.decor_full) { - goto next_mark; - } - - Decoration decor = get_decor(mark); - - if (!decor.sign_text) { - goto next_mark; - } - - if (mark.pos.row > currow) { - count -= count_remove; - count_remove = 0; - currow = mark.pos.row; + int signcols = 0; // highest value of count + for (int currow = row; currow <= end_row; currow++) { + MarkTreeIter itr[1]; + if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) { + continue; } - if (!mt_paired(mark)) { - if (mark.pos.row >= row) { + int count = 0; + MTPair pair; + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + if (!mt_invalid(pair.start) && pair.start.decor_full && pair.start.decor_full->sign_text) { count++; - if (count > signcols) { - signcols = count; - if (signcols >= max) { - return max; - } - } - count_remove++; } - goto next_mark; } - MTPos altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - - if (mt_end(mark)) { - if (mark.pos.row >= row && altpos.row <= end_row) { - count_remove++; + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (mark.pos.row != currow) { + break; } - } else { - if (altpos.row >= row) { + if (!mt_invalid(mark) && !mt_end(mark) && mark.decor_full && mark.decor_full->sign_text) { count++; - if (count > signcols) { - signcols = count; - if (signcols >= max) { - return max; - } - } } + marktree_itr_next(buf->b_marktree, itr); } -next_mark: - marktree_itr_next(buf->b_marktree, itr); + if (count > signcols) { + if (row != end_row) { + buf->b_signcols.sentinel = currow + 1; + } + if (count >= max) { + return max; + } + signcols = count; + } } return signcols; diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 2e224135ed..dacdc7683a 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -60,7 +60,9 @@ struct Decoration { int col; // fixed col value, like win_col int virt_text_width; // width of virt_text char *sign_text; + char *sign_name; int sign_hl_id; + int sign_add_id; int number_hl_id; int line_hl_id; int cursorline_hl_id; @@ -71,7 +73,7 @@ struct Decoration { }; #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ kHlModeUnknown, false, false, false, false, kNone, \ - DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false } + DECOR_PRIORITY_BASE, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, false } typedef struct { int start_row; diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index cc1ae6ac92..2a00e9f373 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -25,7 +25,7 @@ static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg) { - const char *ns_name = describe_ns(provider->ns_id); + const char *ns_name = describe_ns(provider->ns_id, "(UNKNOWN PLUGIN)"); ELOG("error in provider %s.%s: %s", ns_name, name, msg); msg_schedule_semsg_multiline("Error in decoration provider %s.%s:\n%s", ns_name, name, msg); } diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 11b4e55c5c..d08154e24e 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -460,85 +460,37 @@ size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, i /// Get information needed to display the sign in line "wlv->lnum" in window "wp". /// If "nrcol" is true, the sign is going to be displayed in the number column. -/// Otherwise the sign is going to be displayed in the sign column. +/// Otherwise the sign is going to be displayed in the sign column. If there is no +/// sign, draw blank cells instead. static void get_sign_display_info(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx, int sign_cul_attr) { - // Draw cells with the sign value or blank. - wlv->c_extra = ' '; + SignTextAttrs sattr = wlv->sattrs[sign_idx]; wlv->c_final = NUL; - if (nrcol) { - wlv->n_extra = number_width(wp) + 1; - } else { - if (use_cursor_line_highlight(wp, wlv->lnum)) { - wlv->char_attr = win_hl_attr(wp, HLF_CLS); - } else { - wlv->char_attr = win_hl_attr(wp, HLF_SC); - } - wlv->n_extra = win_signcol_width(wp); - } - - if (wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) { - SignTextAttrs *sattr = sign_get_attr(sign_idx, wlv->sattrs, wp->w_scwidth); - if (sattr != NULL) { - wlv->p_extra = sattr->text; - if (wlv->p_extra != NULL) { - wlv->c_extra = NUL; - wlv->c_final = NUL; - - if (nrcol) { - int width = number_width(wp) - 2; - size_t n; - for (n = 0; (int)n < width; n++) { - wlv->extra[n] = ' '; - } - wlv->extra[n] = NUL; - snprintf(wlv->extra + n, sizeof(wlv->extra) - n, "%s ", wlv->p_extra); - wlv->p_extra = wlv->extra; - wlv->n_extra = (int)strlen(wlv->p_extra); - } else { - size_t symbol_blen = strlen(wlv->p_extra); - // TODO(oni-link): Is sign text already extended to - // full cell width? - assert((size_t)win_signcol_width(wp) >= mb_string2cells(wlv->p_extra)); - // symbol(s) bytes + (filling spaces) (one byte each) - wlv->n_extra = (int)symbol_blen + win_signcol_width(wp) - - (int)mb_string2cells(wlv->p_extra); + if (sattr.text && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) { + size_t fill = nrcol ? (size_t)number_width(wp) - SIGN_WIDTH : 0; + size_t sign_len = strlen(sattr.text); - assert(sizeof(wlv->extra) > symbol_blen); - memset(wlv->extra, ' ', sizeof(wlv->extra)); - memcpy(wlv->extra, wlv->p_extra, symbol_blen); - - wlv->p_extra = wlv->extra; - wlv->p_extra[wlv->n_extra] = NUL; - } - } - - if (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr > 0) { - wlv->char_attr = sign_cul_attr; - } else { - wlv->char_attr = sattr->hl_id ? syn_id2attr(sattr->hl_id) : 0; - } + // Spaces + sign: " " + ">>" + ' ' + wlv->n_extra = (int)(fill + sign_len + nrcol); + if (nrcol) { + memset(wlv->extra, ' ', (size_t)wlv->n_extra); + } + memcpy(wlv->extra + fill, sattr.text, sign_len); + wlv->p_extra = wlv->extra; + wlv->c_extra = NUL; + wlv->char_attr = (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr) + ? sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0; + } else { + wlv->c_extra = ' '; + wlv->n_extra = nrcol ? number_width(wp) + 1 : SIGN_WIDTH; + if (!nrcol) { + wlv->char_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC); } } } -/// Returns width of the signcolumn that should be used for the whole window -/// -/// @param wp window we want signcolumn width from -/// @return max width of signcolumn (cell unit) -/// -/// @note Returns a constant for now but hopefully we can improve neovim so that -/// the returned value width adapts to the maximum number of marks to draw -/// for the window -/// TODO(teto) -int win_signcol_width(win_T *wp) -{ - // 2 is vim default value - return 2; -} - static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len) { linenr_T num; @@ -602,8 +554,7 @@ static int get_line_number_attr(win_T *wp, winlinevars_T *wlv) /// Display the absolute or relative line number. After the first row fill with /// blanks when the 'n' flag isn't in 'cpo'. -static void handle_lnum_col(win_T *wp, winlinevars_T *wlv, int num_signs, int sign_idx, - int sign_num_attr, int sign_cul_attr) +static void handle_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int sign_cul_attr) { bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL; @@ -614,8 +565,8 @@ static void handle_lnum_col(win_T *wp, winlinevars_T *wlv, int num_signs, int si && !((has_cpo_n && !wp->w_p_bri) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) { // If 'signcolumn' is set to 'number' and a sign is present in "lnum", // then display the sign instead of the line number. - if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) { - get_sign_display_info(true, wp, wlv, sign_idx, sign_cul_attr); + if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && wlv->sattrs[0].text) { + get_sign_display_info(true, wp, wlv, 0, sign_cul_attr); } else { // Draw the line number (empty space after wrapping). if (wlv->row == wlv->startrow + wlv->filler_lines @@ -1333,15 +1284,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl area_highlighting = true; } - HlPriId line_id = { 0 }; - HlPriId sign_cul = { 0 }; - HlPriId sign_num = { 0 }; - // TODO(bfredl, vigoux): line_attr should not take priority over decoration! - int num_signs = buf_get_signattrs(buf, wlv.lnum, wlv.sattrs, &sign_num, &line_id, &sign_cul); - decor_redraw_signs(buf, wlv.lnum - 1, &num_signs, wlv.sattrs, &sign_num, &line_id, &sign_cul); - + int line_attr = 0; int sign_cul_attr = 0; int sign_num_attr = 0; + // TODO(bfredl, vigoux): line_attr should not take priority over decoration! + decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs, &line_attr, &sign_cul_attr, &sign_num_attr); + statuscol_T statuscol = { 0 }; if (*wp->w_p_stc != NUL) { // Draw the 'statuscolumn' if option is set. @@ -1350,18 +1298,18 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl statuscol.foldinfo = foldinfo; statuscol.width = win_col_off(wp) - (cmdwin_type != 0 && wp == curwin); statuscol.use_cul = use_cursor_line_highlight(wp, lnum); - statuscol.sign_cul_id = statuscol.use_cul ? sign_cul.hl_id : 0; - statuscol.num_attr = sign_num.hl_id > 0 ? syn_id2attr(sign_num.hl_id) : 0; + statuscol.sign_cul_id = statuscol.use_cul ? sign_cul_attr : 0; + statuscol.num_attr = sign_num_attr > 0 ? syn_id2attr(sign_num_attr) : 0; } else { - if (sign_cul.hl_id > 0) { - sign_cul_attr = syn_id2attr(sign_cul.hl_id); + if (sign_cul_attr > 0) { + sign_cul_attr = syn_id2attr(sign_cul_attr); } - if (sign_num.hl_id > 0) { - sign_num_attr = syn_id2attr(sign_num.hl_id); + if (sign_num_attr > 0) { + sign_num_attr = syn_id2attr(sign_num_attr); } } - if (line_id.hl_id > 0) { - wlv.line_attr = syn_id2attr(line_id.hl_id); + if (line_attr > 0) { + wlv.line_attr = syn_id2attr(line_attr); } // Highlight the current line in the quickfix window. @@ -1677,8 +1625,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.draw_state = WL_SIGN; if (wp->w_scwidth > 0) { get_sign_display_info(false, wp, &wlv, sign_idx, sign_cul_attr); - sign_idx++; - if (sign_idx < wp->w_scwidth) { + if (++sign_idx < wp->w_scwidth) { wlv.draw_state = WL_SIGN - 1; } else { sign_idx = 0; @@ -1689,14 +1636,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl if (wlv.draw_state == WL_NR - 1 && wlv.n_extra == 0) { // Show the line number, if desired. wlv.draw_state = WL_NR; - handle_lnum_col(wp, &wlv, num_signs, sign_idx, sign_num_attr, sign_cul_attr); + handle_lnum_col(wp, &wlv, sign_num_attr, sign_cul_attr); } if (wlv.draw_state == WL_STC - 1 && wlv.n_extra == 0) { wlv.draw_state = WL_STC; // Draw the 'statuscolumn' if option is set. if (statuscol.draw) { - if (sign_num.hl_id == 0) { + if (sign_num_attr == 0) { statuscol.num_attr = get_line_number_attr(wp, &wlv); } if (statuscol.textp == NULL) { diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 29f1cb2470..dbcb16cae3 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2558,7 +2558,7 @@ void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endr // draw the sign column int count = wp->w_scwidth; if (count > 0) { - n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row, + n = win_fill_end(wp, ' ', ' ', n, SIGN_WIDTH * count, row, endrow, win_hl_attr(wp, HLF_SC)); } // draw the number column @@ -2633,7 +2633,7 @@ int number_width(win_T *wp) // If 'signcolumn' is set to 'number' and there is a sign to display, then // the minimal width for the number column is 2. - if (n < 2 && (wp->w_buffer->b_signlist != NULL) + if (n < 2 && wp->w_buffer->b_signs_with_text && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { n = 2; } diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index a26ba54ca3..616c1e06fc 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -504,7 +504,7 @@ static dict_T *get_buffer_info(buf_T *buf) } tv_dict_add_list(dict, S_LEN("windows"), windows); - if (buf->b_signlist != NULL) { + if (buf->b_signs) { // List of signs placed in this buffer tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 17335d7a4b..dd32bdbea7 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -30,7 +30,6 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/decoration.h" @@ -161,21 +160,22 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) return true; } -linenr_T extmark_del_id(buf_T *buf, uint32_t ns_id, uint32_t id) +/// Remove an extmark in "ns_id" by "id" +/// +/// @return false on missing id +bool extmark_del_id(buf_T *buf, uint32_t ns_id, uint32_t id) { - MarkTreeIter it[1] = { 0 }; - MTKey key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, it); - if (!key.id) { - return 0; + MarkTreeIter itr[1] = { 0 }; + MTKey key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); + if (key.id) { + extmark_del(buf, itr, key, false); } - return extmark_del(buf, it, key, false); + return key.id > 0; } /// Remove a (paired) extmark "key" pointed to by "itr" -/// -/// @return line number of the deleted mark -linenr_T extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore) +void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore) { assert(key.pos.row >= 0); @@ -195,7 +195,6 @@ linenr_T extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore) } // TODO(bfredl): delete it from current undo header, opportunistically? - return key.pos.row + 1; } /// Free extmarks in a ns between lines @@ -242,8 +241,8 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r /// /// if upper_lnum or upper_col are negative the buffer /// will be searched to the start, or end -/// dir can be set to control the order of the array -/// amount = amount of marks to find or -1 for all +/// reverse can be set to control the order of the array +/// amount = amount of marks to find or INT64_MAX for all ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col, int64_t amount, bool reverse, ExtmarkType type_filter, bool overlap) @@ -301,7 +300,10 @@ static void push_mark(ExtmarkInfoArray *array, uint32_t ns_id, ExtmarkType type_ if (type_filter != kExtmarkNone) { Decoration *decor = mark.decor_full; if (decor && (decor->sign_text || decor->number_hl_id)) { - type_flags |= kExtmarkSign; + type_flags |= (kExtmarkSignHL|kExtmarkSign); + } + if (decor && (decor->line_hl_id || decor->cursorline_hl_id)) { + type_flags |= (kExtmarkSignHL|kExtmarkHighlight); } if (decor && decor->virt_text.size) { type_flags |= kExtmarkVirtText; @@ -309,8 +311,7 @@ static void push_mark(ExtmarkInfoArray *array, uint32_t ns_id, ExtmarkType type_ if (decor && decor->virt_lines.size) { type_flags |= kExtmarkVirtLines; } - if ((decor && (decor->line_hl_id || decor->cursorline_hl_id)) - || mark.hl_id) { + if (mark.hl_id) { type_flags |= kExtmarkHighlight; } @@ -598,6 +599,20 @@ void extmark_splice_impl(buf_T *buf, int start_row, colnr_T start_col, bcount_t extmark_splice_delete(buf, start_row, start_col, end_row, end_col, undo); } + // Move the signcolumn sentinel line + if (buf->b_signs_with_text && buf->b_signcols.sentinel) { + linenr_T se_lnum = buf->b_signcols.sentinel; + if (se_lnum >= start_row) { + if (old_row != 0 && se_lnum > old_row + start_row) { + buf->b_signcols.sentinel += new_row - old_row; + } else if (new_row == 0) { + buf->b_signcols.sentinel = 0; + } else { + buf->b_signcols.sentinel += new_row; + } + } + } + marktree_splice(buf->b_marktree, (int32_t)start_row, start_col, old_row, old_col, new_row, new_col); diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index 771edf3983..b4979c4aea 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -84,9 +84,10 @@ typedef enum { typedef enum { kExtmarkNone = 0x1, kExtmarkSign = 0x2, - kExtmarkVirtText = 0x4, - kExtmarkVirtLines = 0x8, - kExtmarkHighlight = 0x10, + kExtmarkSignHL = 0x4, + kExtmarkVirtText = 0x8, + kExtmarkVirtLines = 0x10, + kExtmarkHighlight = 0x20, } ExtmarkType; // TODO(bfredl): reduce the number of undo action types diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 0cab3d1f8c..2e9fcf8e75 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -474,10 +474,6 @@ EXTERN buf_T *curbuf INIT( = NULL); // currently active buffer #define FOR_ALL_BUF_WININFO(buf, wip) \ for ((wip) = (buf)->b_wininfo; (wip) != NULL; (wip) = (wip)->wi_next) // NOLINT -// 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)->se_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. EXTERN alist_T global_alist; // global argument list diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index faec66b2bc..96a38b752f 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -246,9 +246,3 @@ typedef struct { } ColorItem; #define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1, \ .is_default = false, .link_global = false } - -/// highlight attributes with associated priorities -typedef struct { - int hl_id; - int priority; -} HlPriId; diff --git a/src/nvim/main.c b/src/nvim/main.c index 00eb6e75a5..8728861145 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -70,7 +70,6 @@ #include "nvim/quickfix.h" #include "nvim/runtime.h" #include "nvim/shada.h" -#include "nvim/sign.h" #include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -218,8 +217,6 @@ void early_init(mparm_T *paramp) TIME_MSG("inits 1"); set_lang_var(); // set v:lang and v:ctype - - init_signs(); } #ifdef MAKE_LIB diff --git a/src/nvim/mark.c b/src/nvim/mark.c index b4d405e2f1..532c20b874 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -33,7 +33,6 @@ #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/quickfix.h" -#include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/textobject.h" #include "nvim/vim.h" @@ -1190,8 +1189,6 @@ void mark_adjust_buf(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount if (!found_one) { buf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY; } - - sign_mark_adjust(buf, line1, line2, amount, amount_after); } if (op != kExtmarkNOOP) { diff --git a/src/nvim/move.c b/src/nvim/move.c index 7cc368a36b..9427719988 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -758,8 +758,7 @@ int win_col_off(win_T *wp) return ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL) ? (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) + ((cmdwin_type == 0 || wp != curwin) ? 0 : 1) - + win_fdccol_count(wp) - + (win_signcol_count(wp) * win_signcol_width(wp)); + + win_fdccol_count(wp) + (win_signcol_count(wp) * SIGN_WIDTH); } int curwin_col_off(void) diff --git a/src/nvim/option.c b/src/nvim/option.c index d3a8e2ce73..d64fb0c615 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -6170,7 +6170,7 @@ bool fish_like_shell(void) /// buffer signs and on user configuration. int win_signcol_count(win_T *wp) { - return win_signcol_configured(wp, NULL); + return win_signcol_configured(wp); } /// Return true when window "wp" has no sign column. @@ -6182,14 +6182,10 @@ bool win_no_signcol(win_T *wp) } /// Return the number of requested sign columns, based on user / configuration. -int win_signcol_configured(win_T *wp, int *is_fixed) +int win_signcol_configured(win_T *wp) { const char *scl = wp->w_p_scl; - if (is_fixed) { - *is_fixed = 1; - } - if (win_no_signcol(wp)) { return 0; } @@ -6203,11 +6199,6 @@ int win_signcol_configured(win_T *wp, int *is_fixed) return 1; } - if (is_fixed) { - // auto or auto:<NUM> - *is_fixed = 0; - } - int minimum = 0, maximum = 1; if (!strncmp(scl, "auto:", 5)) { diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 9ca1396753..c5dfa91f27 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7590,12 +7590,6 @@ return { number (maximum 9), e.g. "yes:3" "number" display signs in the 'number' column. If the number column is not present, then behaves like "auto". - - Note regarding "orphaned signs": with signcolumn numbers higher than - 1, deleting lines will also remove the associated signs automatically, - in contrast to the default Vim behavior of keeping and grouping them. - This is done in order for the signcolumn appearance not appear weird - during line deletion. ]=], expand_cb = 'expand_set_signcolumn', full_name = 'signcolumn', diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 73ef1580ba..4f696f5c91 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -2,11 +2,11 @@ #include <inttypes.h> #include <stdbool.h> -#include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include "nvim/api/extmark.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" @@ -19,10 +19,10 @@ #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" +#include "nvim/extmark.h" #include "nvim/fold.h" #include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/hashtab.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/macros.h" @@ -30,7 +30,6 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" -#include "nvim/option.h" #include "nvim/pos.h" #include "nvim/sign.h" #include "nvim/sign_defs.h" @@ -39,26 +38,8 @@ #include "nvim/vim.h" #include "nvim/window.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 *sn_name; // name of sign - char *sn_icon; // name of pixmap - char *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_cul_hl; // highlight ID for text on current line when 'cursorline' is set - 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 PMap(cstr_t) sign_map INIT( = MAP_INIT); +static kvec_t(Integer) sign_ns INIT( = MAP_INIT); static char *cmds[] = { "define", @@ -77,736 +58,229 @@ static char *cmds[] = { #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 *groupname) -{ - hash_T hash; - hashitem_T *hi; - signgroup_T *group; - - hash = hash_hash(groupname); - hi = hash_lookup(&sg_table, groupname, strlen(groupname), hash); - if (HASHITEM_EMPTY(hi)) { - // new group - group = xmalloc(offsetof(signgroup_T, sg_name) + strlen(groupname) + 1); - - STRCPY(group->sg_name, groupname); - group->sg_refcount = 1; - group->sg_next_sign_id = 1; - hash_add_item(&sg_table, hi, group->sg_name, hash); - } else { - // existing group - group = HI2SG(hi); - group->sg_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 *groupname) +// Convert the supplied "group" to a namespace filter +static int64_t group_get_ns(const char *group) { - hashitem_T *hi = hash_find(&sg_table, groupname); - if (HASHITEM_EMPTY(hi)) { - return; - } - - signgroup_T *group = HI2SG(hi); - group->sg_refcount--; - if (group->sg_refcount == 0) { - // All the signs in this group are removed - hash_remove(&sg_table, hi); - xfree(group); + if (group == NULL) { + return 0; // Global namespace + } else if (strcmp(group, "*") == 0) { + return UINT32_MAX; // All namespaces } + // Specific or non-existing namespace + int ns = map_get(String, int)(&namespace_ids, cstr_as_string((char *)group)); + return ns ? ns : -1; } -/// @return 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. -static bool sign_in_group(sign_entry_T *sign, const char *group) +static const char *sign_get_name(MTKey mark) { - return ((group != NULL && strcmp(group, "*") == 0) - || (group == NULL && sign->se_group == NULL) - || (group != NULL && sign->se_group != NULL - && strcmp(group, sign->se_group->sg_name) == 0)); + char *name = mark.decor_full->sign_name; + return !name ? "" : map_has(cstr_t, &sign_map, name) ? name : "[Deleted]"; } -/// Get the next free sign identifier in the specified group -static int sign_group_get_next_signid(buf_T *buf, const char *groupname) -{ - int id = 1; - signgroup_T *group = NULL; - sign_entry_T *sign; - int found = false; - - if (groupname != NULL) { - hashitem_T *hi = hash_find(&sg_table, groupname); - if (HASHITEM_EMPTY(hi)) { - return id; - } - group = HI2SG(hi); - } - - // Search for the next usable sign identifier - while (!found) { - if (group == NULL) { - id = next_sign_id++; // global group - } else { - id = group->sg_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->se_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. +/// Create or update a sign extmark. /// /// @param buf buffer to store sign in -/// @param prev previous sign entry -/// @param next next sign entry /// @param id sign ID -/// @param group sign group; NULL for global group +/// @param group sign group /// @param prio sign priority /// @param lnum line number which gets the mark -/// @param typenr typenr of sign we are adding -/// @param has_text_or_icon sign has text or icon -static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int id, - const char *group, int prio, linenr_T lnum, int typenr, - bool has_text_or_icon) +/// @param sp sign properties +static void buf_set_sign(buf_T *buf, uint32_t *id, char *group, int prio, linenr_T lnum, sign_T *sp) { - sign_entry_T *newsign = xmalloc(sizeof(sign_entry_T)); - newsign->se_id = id; - newsign->se_lnum = lnum; - newsign->se_typenr = typenr; - newsign->se_has_text_or_icon = has_text_or_icon; - if (group != NULL) { - newsign->se_group = sign_group_ref(group); - } else { - newsign->se_group = NULL; + if (group && !map_get(String, int)(&namespace_ids, cstr_as_string(group))) { + kv_push(sign_ns, nvim_create_namespace(cstr_as_string(group))); } - newsign->se_priority = prio; - newsign->se_next = next; - newsign->se_prev = prev; - if (next != NULL) { - next->se_prev = newsign; - } - - buf_signcols_add_check(buf, newsign); - - 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, UPD_NOT_VALID); - changed_line_abv_curs(); - } - // first sign in signlist - buf->b_signlist = newsign; - } else { - prev->se_next = newsign; - } + uint32_t ns = group ? (uint32_t)nvim_create_namespace(cstr_as_string(group)) : 0; + Decoration decor = DECORATION_INIT; + decor.sign_text = sp->sn_text ? xstrdup(sp->sn_text) : NULL; + decor.sign_name = xstrdup(sp->sn_name); + decor.sign_hl_id = sp->sn_text_hl; + decor.line_hl_id = sp->sn_line_hl; + decor.number_hl_id = sp->sn_num_hl; + decor.cursorline_hl_id = sp->sn_cul_hl; + decor.priority = (DecorPriority)prio; + extmark_set(buf, ns, id, lnum - 1, 0, -1, -1, &decor, true, false, true, true, NULL); } -/// Insert a new sign sorted by line number and sign priority. +/// For an existing, placed sign with "id", modify the sign, group or priority. +/// Returns the line number of the sign, or zero if the sign is not found. /// /// @param buf buffer to store sign in -/// @param prev previous sign entry /// @param id sign ID -/// @param group sign group; NULL for global group +/// @param group sign group /// @param prio sign priority -/// @param lnum line number which gets the mark -/// @param typenr typenr of sign we are adding -/// @param has_text_or_icon sign has text or icon -static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, const char *group, - int prio, linenr_T lnum, int typenr, bool has_text_or_icon) -{ - sign_entry_T *sign; - - // keep signs sorted by lnum, priority and id: insert new sign at - // the proper position in the list for this lnum. - while (prev != NULL && prev->se_lnum == lnum - && (prev->se_priority < prio - || (prev->se_priority == prio && prev->se_id <= id))) { - prev = prev->se_prev; - } - if (prev == NULL) { - sign = buf->b_signlist; - } else { - sign = prev->se_next; - } - - insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon); -} - -/// Lookup a sign by typenr. Returns NULL if sign is not found. -static sign_T *find_sign_by_typenr(int typenr) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - return sp; - } - } - return NULL; -} - -/// Get the name of a sign by its typenr. -static char *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 _("[Deleted]"); -} - -/// Return information about a sign in a Dict -static dict_T *sign_get_info(sign_entry_T *sign) -{ - dict_T *d = tv_dict_alloc(); - tv_dict_add_nr(d, S_LEN("id"), sign->se_id); - tv_dict_add_str(d, S_LEN("group"), ((sign->se_group == NULL) - ? "" - : sign->se_group->sg_name)); - tv_dict_add_nr(d, S_LEN("lnum"), sign->se_lnum); - tv_dict_add_str(d, S_LEN("name"), sign_typenr2name(sign->se_typenr)); - tv_dict_add_nr(d, S_LEN("priority"), sign->se_priority); - - return d; -} - -// Sort the signs placed on the same line as "sign" by priority. Invoked after -// changing the priority of an already placed sign. Assumes the signs in the -// buffer are sorted by line number and priority. -static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign) - FUNC_ATTR_NONNULL_ALL +/// @param sp sign pointer +static linenr_T buf_mod_sign(buf_T *buf, uint32_t *id, char *group, int prio, sign_T *sp) { - // If there is only one sign in the buffer or only one sign on the line or - // the sign is already sorted by priority, then return. - if ((sign->se_prev == NULL - || sign->se_prev->se_lnum != sign->se_lnum - || sign->se_prev->se_priority > sign->se_priority) - && (sign->se_next == NULL - || sign->se_next->se_lnum != sign->se_lnum - || sign->se_next->se_priority < sign->se_priority)) { - return; - } - - // One or more signs on the same line as 'sign' - // Find a sign after which 'sign' should be inserted - - // First search backward for a sign with higher priority on the same line - sign_entry_T *p = sign; - while (p->se_prev != NULL - && p->se_prev->se_lnum == sign->se_lnum - && p->se_prev->se_priority <= sign->se_priority) { - p = p->se_prev; - } - if (p == sign) { - // Sign not found. Search forward for a sign with priority just before - // 'sign'. - p = sign->se_next; - while (p->se_next != NULL - && p->se_next->se_lnum == sign->se_lnum - && p->se_next->se_priority > sign->se_priority) { - p = p->se_next; - } - } - - // Remove 'sign' from the list - if (buf->b_signlist == sign) { - buf->b_signlist = sign->se_next; - } - if (sign->se_prev != NULL) { - sign->se_prev->se_next = sign->se_next; - } - if (sign->se_next != NULL) { - sign->se_next->se_prev = sign->se_prev; + int64_t ns = group_get_ns(group); + if (ns < 0 || (group && ns == 0)) { + return 0; } - sign->se_prev = NULL; - sign->se_next = NULL; - // Re-insert 'sign' at the right place - if (p->se_priority <= sign->se_priority) { - // 'sign' has a higher priority and should be inserted before 'p' - sign->se_prev = p->se_prev; - sign->se_next = p; - p->se_prev = sign; - if (sign->se_prev != NULL) { - sign->se_prev->se_next = sign; - } - if (buf->b_signlist == p) { - buf->b_signlist = sign; - } - } else { - // 'sign' has a lower priority and should be inserted after 'p' - sign->se_prev = p; - sign->se_next = p->se_next; - p->se_next = sign; - if (sign->se_next != NULL) { - sign->se_next->se_prev = sign; - } + MTKey mark = marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, *id, false, NULL); + if (mark.pos.row >= 0) { + buf_set_sign(buf, id, group, prio, mark.pos.row + 1, sp); } + return mark.pos.row + 1; } -/// Add the sign into the signlist. Find the right spot to do it though. +/// 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. /// /// @param buf buffer to store sign in /// @param id sign ID -/// @param groupname sign group -/// @param prio sign priority -/// @param lnum line number which gets the mark -/// @param typenr typenr of sign we are adding -/// @param has_text_or_icon sign has text or icon -static void buf_addsign(buf_T *buf, int id, const char *groupname, int prio, linenr_T lnum, - int typenr, bool has_text_or_icon) -{ - sign_entry_T *sign; // a sign in the signlist - sign_entry_T *prev; // the previous sign - - prev = NULL; - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (lnum == sign->se_lnum && id == sign->se_id - && sign_in_group(sign, groupname)) { - // Update an existing sign - sign->se_typenr = typenr; - sign->se_priority = prio; - sign_sort_by_prio_on_line(buf, sign); - return; - } else if (lnum < sign->se_lnum) { - insert_sign_by_lnum_prio(buf, - prev, - id, - groupname, - prio, - lnum, - typenr, - has_text_or_icon); - return; - } - prev = sign; - } - - insert_sign_by_lnum_prio(buf, - prev, - id, - groupname, - prio, - lnum, - typenr, - has_text_or_icon); -} - -/// 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. -/// -/// @param buf buffer to store sign in -/// @param markId sign ID /// @param group sign group -/// @param typenr typenr of sign we are adding -/// @param prio sign priority -static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char *group, int typenr, - int prio) +static int buf_findsign(buf_T *buf, int id, char *group) { - sign_entry_T *sign; // a sign in the signlist - - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->se_id == markId && sign_in_group(sign, group)) { - sign->se_typenr = typenr; - sign->se_priority = prio; - sign_sort_by_prio_on_line(buf, sign); - return sign->se_lnum; - } + int64_t ns = group_get_ns(group); + if (ns < 0 || (group && ns == 0)) { + return 0; } - - return 0; + return marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, (uint32_t)id, false, NULL).pos.row + 1; } -/// Return the sign attrs which has the attribute specified by 'type'. Returns -/// NULL if a sign is not found with the specified attribute. -/// @param type Type of sign to look for -/// @param sattrs Sign attrs to search through -/// @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 Attrs of the matching sign, or NULL -SignTextAttrs *sign_get_attr(int idx, SignTextAttrs sattrs[], int max_signs) +/// qsort() function to sort signs by line number, priority, id and recency. +int sign_cmp(const void *p1, const void *p2) { - SignTextAttrs *matches[SIGN_SHOW_MAX]; - int sattr_matches = 0; - - for (int i = 0; i < SIGN_SHOW_MAX; i++) { - if (sattrs[i].text != NULL) { - matches[sattr_matches++] = &sattrs[i]; - // attr list is sorted with most important (priority, id), thus we - // may stop as soon as we have max_signs matches - if (sattr_matches >= max_signs) { - break; - } - } - } - - if (sattr_matches > idx) { - return matches[sattr_matches - idx - 1]; - } + const MTKey *s1 = (MTKey *)p1; + const MTKey *s2 = (MTKey *)p2; + int n = s1->pos.row - s2->pos.row; - return NULL; + return n ? n : (n = s2->decor_full->priority - s1->decor_full->priority) + ? n : (n = (int)(s2->id - s1->id)) + ? n : (s2->decor_full->sign_add_id - s1->decor_full->sign_add_id); } -/// Return the attributes of all the signs placed on line 'lnum' in buffer -/// 'buf'. Used when refreshing the screen. Returns the number of signs. -/// @param buf Buffer in which to search -/// @param lnum Line in which to search -/// @param sattrs Output array for attrs -/// @return Number of signs of which attrs were found -int buf_get_signattrs(buf_T *buf, linenr_T lnum, SignTextAttrs sattrs[], HlPriId *num_id, - HlPriId *line_id, HlPriId *cul_id) -{ - sign_entry_T *sign; - - int sattr_matches = 0; - - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->se_lnum > lnum) { - // Signs are sorted by line number in the buffer. No need to check - // for signs after the specified line number 'lnum'. - break; - } - - if (sign->se_lnum < lnum) { - continue; - } - - sign_T *sp = find_sign_by_typenr(sign->se_typenr); - if (sp == NULL) { - continue; - } - - if (sp->sn_text != NULL && sattr_matches < SIGN_SHOW_MAX) { - sattrs[sattr_matches++] = (SignTextAttrs) { - .text = sp->sn_text, - .hl_id = sp->sn_text_hl, - .priority = sign->se_priority - }; - } - - struct { HlPriId *dest; int hl; } cattrs[] = { - { line_id, sp->sn_line_hl }, - { num_id, sp->sn_num_hl }, - { cul_id, sp->sn_cul_hl }, - { NULL, -1 }, - }; - for (int i = 0; cattrs[i].dest; i++) { - if (cattrs[i].hl != 0 && sign->se_priority >= cattrs[i].dest->priority) { - *cattrs[i].dest = (HlPriId) { - .hl_id = cattrs[i].hl, - .priority = sign->se_priority - }; - } - } - } - return sattr_matches; -} - -/// 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. +/// Delete the specified signs /// -/// @param buf buffer sign is stored in -/// @param atlnum sign at this line, 0 - at any line -/// @param id sign id +/// @param buf buffer sign is stored in or NULL for all buffers /// @param group sign group -/// -/// @return the line number of the deleted sign. If multiple signs are deleted, -/// then returns the line number of the last sign deleted. -static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char *group) +/// @param id sign id +/// @param atlnum sign at this line, -1 at any line +static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum) { - sign_entry_T **lastp; // pointer to pointer to current sign - sign_entry_T *sign; // a sign in a b_signlist - sign_entry_T *next; // the next sign in a b_signlist - linenr_T lnum; // line number whose sign was deleted - - lastp = &buf->b_signlist; - lnum = 0; - for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->se_next; - if ((id == 0 || sign->se_id == id) - && (atlnum == 0 || sign->se_lnum == atlnum) - && sign_in_group(sign, group)) { - *lastp = next; - if (next != NULL) { - next->se_prev = sign->se_prev; - } - lnum = sign->se_lnum; - buf_signcols_del_check(buf, lnum, lnum); - if (sign->se_group != NULL) { - sign_group_unref(sign->se_group->sg_name); - } - xfree(sign); - redraw_buf_line_later(buf, lnum, false); - // 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->se_next; - } - } - - // When deleting the last sign the cursor position may change, because the - // sign columns no longer shows. And the 'signcolumn' may be hidden. - if (buf->b_signlist == NULL) { - redraw_buf_later(buf, UPD_NOT_VALID); - changed_line_abv_curs(); + int64_t ns = group_get_ns(group); + if (ns < 0) { + return FAIL; } - 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. -/// -/// @param buf buffer to store sign in -/// @param id sign ID -/// @param group sign group -static int buf_findsign(buf_T *buf, int id, char *group) -{ - sign_entry_T *sign; // a sign in the signlist + MarkTreeIter itr[1]; + int row = atlnum > 0 ? atlnum - 1 : 0; + kvec_t(MTKey) signs = KV_INITIAL_VALUE; + // Store and sort when removing a single sign at a specific line number. + if (atlnum > 0) { + if (!marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { + return FAIL; + } - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->se_id == id && sign_in_group(sign, group)) { - return (int)sign->se_lnum; + MTPair pair; + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + if ((ns == UINT32_MAX || ns == pair.start.ns) + && pair.start.decor_full && decor_has_sign(pair.start.decor_full)) { + kv_push(signs, pair.start); + } } + } else { + marktree_itr_get(buf->b_marktree, 0, 0, itr); } - 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. -/// -/// @param buf buffer whose sign we are searching for -/// @param lnum line number of sign -/// @param groupname sign group name -static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char *groupname) -{ - sign_entry_T *sign; // a sign in the signlist - - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->se_lnum > lnum) { - // Signs are sorted by line number in the buffer. No need to check - // for signs after the specified line number 'lnum'. + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (row && mark.pos.row > row) { break; } - - if (sign->se_lnum == lnum && sign_in_group(sign, groupname)) { - return sign; + if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full) + && (id == 0 || (int)mark.id == id) + && (ns == UINT32_MAX || ns == mark.ns)) { + if (atlnum > 0) { + kv_push(signs, mark); + marktree_itr_next(buf->b_marktree, itr); + } else { + extmark_del(buf, itr, mark, true); + } + } else { + marktree_itr_next(buf->b_marktree, itr); } } - return NULL; -} - -/// Return the identifier of the sign at line number 'lnum' in buffer 'buf'. -/// -/// @param buf buffer whose sign we are searching for -/// @param lnum line number of sign -/// @param groupname sign group name -static int buf_findsign_id(buf_T *buf, linenr_T lnum, char *groupname) -{ - sign_entry_T *sign; // a sign in the signlist - - sign = buf_getsign_at_line(buf, lnum, groupname); - if (sign != NULL) { - return sign->se_id; + if (kv_size(signs)) { + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); + extmark_del_id(buf, kv_A(signs, 0).ns, kv_A(signs, 0).id); + kv_destroy(signs); + } else if (atlnum > 0) { + return FAIL; } - return 0; -} - -/// Delete signs in buffer "buf". -void buf_delete_signs(buf_T *buf, char *group) -{ - sign_entry_T *sign; - sign_entry_T **lastp; // pointer to pointer to current sign - sign_entry_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) { + if (!buf->b_signs_with_text && curwin != NULL) { changed_line_abv_curs(); } - lastp = &buf->b_signlist; - for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->se_next; - if (sign_in_group(sign, group)) { - *lastp = next; - if (next != NULL) { - next->se_prev = sign->se_prev; - } - if (sign->se_group != NULL) { - sign_group_unref(sign->se_group->sg_name); - } - xfree(sign); - } else { - lastp = &sign->se_next; - } - } - buf_signcols_del_check(buf, 1, MAXLNUM); + return OK; } /// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. -static void sign_list_placed(buf_T *rbuf, char *sign_group) +static void sign_list_placed(buf_T *rbuf, char *group) { - buf_T *buf; - sign_entry_T *sign; char lbuf[MSG_BUF_LEN]; - char group[MSG_BUF_LEN]; + char namebuf[MSG_BUF_LEN]; + char groupbuf[MSG_BUF_LEN]; + buf_T *buf = rbuf ? rbuf : firstbuf; + int64_t ns = group_get_ns(group); 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) { + if (buf->b_signs) { 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->se_group != NULL) { - vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"), - sign->se_group->sg_name); - } else { - group[0] = '\0'; - } - vim_snprintf(lbuf, MSG_BUF_LEN, - _(" line=%" PRIdLINENR " id=%d%s name=%s priority=%d"), - sign->se_lnum, sign->se_id, group, - sign_typenr2name(sign->se_typenr), sign->se_priority); - msg_puts(lbuf); - msg_putchar('\n'); - } - if (rbuf != NULL) { - break; - } - buf = buf->b_next; - } -} - -/// Adjust or delete a placed sign for inserted/deleted lines. -/// -/// @return the new line number of the sign, or 0 if the sign is in deleted lines. -static linenr_T sign_adjust_one(const linenr_T se_lnum, linenr_T line1, linenr_T line2, - linenr_T amount, linenr_T amount_after) -{ - if (se_lnum < line1) { - // Ignore changes to lines after the sign - return se_lnum; - } - if (se_lnum > line2) { - // Lines inserted or deleted before the sign - return se_lnum + amount_after; - } - if (amount == MAXLNUM) { // sign in deleted lines - return 0; - } - return se_lnum + amount; -} - -/// Adjust placed signs for inserted/deleted lines. -void sign_mark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount, - linenr_T amount_after) -{ - sign_entry_T *sign; // a sign in a b_signlist - sign_entry_T *next; // the next sign in a b_signlist - sign_entry_T *last = NULL; // pointer to pointer to current sign - sign_entry_T **lastp = NULL; // pointer to pointer to current sign - linenr_T new_lnum; // new line number to assign to sign - int is_fixed = 0; - int signcol = curwin->w_buffer == buf ? win_signcol_configured(curwin, &is_fixed) : 0; - if (amount == MAXLNUM) { // deleting - buf_signcols_del_check(buf, line1, line2); - } + if (ns >= 0) { + MarkTreeIter itr[1]; + kvec_t(MTKey) signs = KV_INITIAL_VALUE; + marktree_itr_get(buf->b_marktree, 0, 0, itr); - lastp = &buf->b_signlist; - - for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->se_next; - - new_lnum = sign_adjust_one(sign->se_lnum, line1, line2, amount, amount_after); - if (new_lnum == 0) { // sign in deleted lines - if (!is_fixed || signcol >= 2) { - *lastp = next; - if (next) { - next->se_prev = last; + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full) + && (ns == UINT32_MAX || ns == mark.ns)) { + kv_push(signs, mark); } - xfree(sign); - continue; + marktree_itr_next(buf->b_marktree, itr); } - } else { - // 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 (new_lnum <= buf->b_ml.ml_line_count) { - sign->se_lnum = new_lnum; + + if (kv_size(signs)) { + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); + + for (size_t i = 0; i < kv_size(signs); i++) { + namebuf[0] = '\0'; + groupbuf[0] = '\0'; + MTKey mark = kv_A(signs, i); + if (mark.decor_full->sign_name != NULL) { + vim_snprintf(namebuf, MSG_BUF_LEN, _(" name=%s"), sign_get_name(mark)); + } + if (mark.ns != 0) { + vim_snprintf(groupbuf, MSG_BUF_LEN, _(" group=%s"), describe_ns((int)mark.ns, "")); + } + vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%" PRIdLINENR " id=%u%s%s priority=%d"), + mark.pos.row + 1, mark.id, groupbuf, namebuf, mark.decor_full->priority); + msg_puts(lbuf); + msg_putchar('\n'); + } + kv_destroy(signs); } } - last = sign; - lastp = &sign->se_next; - } - - new_lnum = sign_adjust_one(buf->b_signcols.sentinel, line1, line2, amount, amount_after); - if (new_lnum != 0) { - buf->b_signcols.sentinel = new_lnum; + if (rbuf != NULL) { + return; + } + buf = buf->b_next; } } @@ -830,93 +304,22 @@ static int sign_cmd_idx(char *begin_cmd, char *end_cmd) return idx; } -/// Find a sign by name. Also returns pointer to the previous sign. -static sign_T *sign_find(const char *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 *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 = xstrdup(name); - - return sp; -} - -/// Initialize the icon information for a new sign -static void sign_define_init_icon(sign_T *sp, char *icon) -{ - xfree(sp->sn_icon); - sp->sn_icon = xstrdup(icon); - backslash_halve(sp->sn_icon); -} - -/// Initialize the text for a new sign -static int sign_define_init_text(sign_T *sp, char *text) +/// Initialize the "text" for a new sign and store in "sign_text". +/// "sp" is NULL for signs added through nvim_buf_set_extmark(). +int init_sign_text(sign_T *sp, char **sign_text, char *text) { char *s; - char *endp; - int cells; - size_t len; + char *endp = text + (int)strlen(text); - endp = text + (int)strlen(text); - for (s = text; s + 1 < endp; s++) { + for (s = sp ? text : endp; s + 1 < endp; s++) { if (*s == '\\') { - // Remove a backslash, so that it is possible - // to use a space. + // 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; + int cells = 0; for (s = text; s < endp; s += utfc_ptr2len(s)) { if (!vim_isprintc(utf_ptr2char(s))) { break; @@ -925,95 +328,69 @@ static int sign_define_init_text(sign_T *sp, char *text) } // Currently must be empty, one or two display cells if (s != endp || cells > 2) { - semsg(_("E239: Invalid sign text: %s"), text); + if (sp != NULL) { + semsg(_("E239: Invalid sign text: %s"), text); + } return FAIL; } if (cells < 1) { - sp->sn_text = NULL; + if (sp != NULL) { + sp->sn_text = NULL; + } return OK; } - 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 = xstrnsave(text, len); + if (sp != NULL) { + xfree(sp->sn_text); + } + // Allocate one byte more if we need to pad up with a space. + size_t len = (size_t)(endp - text + (cells == 1)); + *sign_text = xstrnsave(text, len); if (cells == 1) { - STRCPY(sp->sn_text + len - 1, " "); + STRCPY(*sign_text + len - 1, " "); } return OK; } /// Define a new sign or update an existing sign -static int sign_define_by_name(char *name, char *icon, char *linehl, char *text, char *texthl, +static int sign_define_by_name(char *name, char *icon, char *text, char *linehl, char *texthl, char *culhl, char *numhl) { - sign_T *sp_prev; - sign_T *sp; + cstr_t *key; + sign_T **sp = (sign_T **)pmap_put_ref(cstr_t)(&sign_map, name, &key, NULL); - 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; - } + if (*sp == NULL) { + *key = xstrdup(name); + *sp = xcalloc(1, sizeof(sign_T)); + (*sp)->sn_name = (char *)(*key); } else { - // Signs may already exist, a redraw is needed in windows with a - // non-empty sign list. + // Signs may already exist, a redraw is needed in windows with a non-empty sign list. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->b_signlist != NULL) { + if (wp->w_buffer->b_signs) { redraw_buf_later(wp->w_buffer, UPD_NOT_VALID); } } } - // set values for a defined sign. + // Set values for a defined sign. if (icon != NULL) { - sign_define_init_icon(sp, icon); + /// Initialize the icon information for a new sign + xfree((*sp)->sn_icon); + (*sp)->sn_icon = xstrdup(icon); + backslash_halve((*sp)->sn_icon); } - if (text != NULL && (sign_define_init_text(sp, text) == FAIL)) { + if (text != NULL && (init_sign_text(*sp, &(*sp)->sn_text, text) == FAIL)) { return FAIL; } - if (linehl != NULL) { - if (*linehl == NUL) { - sp->sn_line_hl = 0; - } else { - sp->sn_line_hl = syn_check_group(linehl, strlen(linehl)); - } - } - - if (texthl != NULL) { - if (*texthl == NUL) { - sp->sn_text_hl = 0; - } else { - sp->sn_text_hl = syn_check_group(texthl, strlen(texthl)); - } - } - - if (culhl != NULL) { - if (*culhl == NUL) { - sp->sn_cul_hl = 0; - } else { - sp->sn_cul_hl = syn_check_group(culhl, strlen(culhl)); - } - } - - if (numhl != NULL) { - if (*numhl == NUL) { - sp->sn_num_hl = 0; - } else { - sp->sn_num_hl = syn_check_group(numhl, strlen(numhl)); + char *arg[] = { linehl, texthl, culhl, numhl }; + int *hl[] = { &(*sp)->sn_line_hl, &(*sp)->sn_text_hl, &(*sp)->sn_cul_hl, &(*sp)->sn_num_hl }; + for (int i = 0; i < 4; i++) { + if (arg[i] != NULL) { + *hl[i] = *arg[i] ? syn_check_group(arg[i], strlen(arg[i])) : 0; } } @@ -1023,25 +400,47 @@ static int sign_define_by_name(char *name, char *icon, char *linehl, char *text, /// Free the sign specified by 'name'. static int sign_undefine_by_name(const char *name) { - sign_T *sp_prev; - sign_T *sp; - - sp = sign_find(name, &sp_prev); + sign_T *sp = pmap_del(cstr_t)(&sign_map, name, NULL); if (sp == NULL) { semsg(_("E155: Unknown sign: %s"), name); return FAIL; } - sign_undefine(sp, sp_prev); + xfree(sp->sn_name); + xfree(sp->sn_text); + xfree(sp->sn_icon); + xfree(sp); return OK; } +/// List one sign. +static void sign_list_defined(sign_T *sp) +{ + smsg(0, "sign %s", sp->sn_name); + if (sp->sn_icon != NULL) { + msg_puts(" icon="); + msg_outtrans(sp->sn_icon, 0); + msg_puts(_(" (not supported)")); + } + if (sp->sn_text != NULL) { + msg_puts(" text="); + msg_outtrans(sp->sn_text, 0); + } + static char *arg[] = { " linehl=", " texthl=", " culhl=", " numhl=" }; + int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl }; + for (int i = 0; i < 4; i++) { + if (hl[i] > 0) { + msg_puts(arg[i]); + const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false); + msg_puts(p ? p : "NONE"); + } + } +} + /// List the signs matching 'name' static void sign_list_by_name(char *name) { - sign_T *sp; - - sp = sign_find(name, NULL); + sign_T *sp = pmap_get(cstr_t)(&sign_map, name); if (sp != NULL) { sign_list_defined(sp); } else { @@ -1061,103 +460,90 @@ static void may_force_numberwidth_recompute(buf_T *buf, int unplace) } /// Place a sign at the specified file location or update a sign. -static int sign_place(int *sign_id, const char *sign_group, const char *sign_name, buf_T *buf, - linenr_T lnum, int prio) +static int sign_place(uint32_t *id, char *group, char *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')) { + if (group != NULL && (*group == '*' || *group == '\0')) { return FAIL; } - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (strcmp(sp->sn_name, sign_name) == 0) { - break; - } - } + sign_T *sp = pmap_get(cstr_t)(&sign_map, name); if (sp == NULL) { - semsg(_("E155: Unknown sign: %s"), sign_name); + semsg(_("E155: Unknown sign: %s"), 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 - bool has_text_or_icon = sp->sn_text != NULL || sp->sn_icon != NULL; - buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr, has_text_or_icon); + // ":sign place {id} line={lnum} name={name} file={fname}": place a sign + buf_set_sign(buf, id, group, prio, lnum, sp); } else { // ":sign place {id} file={fname}": change sign type and/or priority - lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr, prio); + lnum = buf_mod_sign(buf, id, group, prio, sp); } if (lnum > 0) { - redraw_buf_line_later(buf, lnum, false); - // When displaying signs in the 'number' column, if the width of the // number column is less than 2, then force recomputing the width. may_force_numberwidth_recompute(buf, false); } else { - semsg(_("E885: Not possible to change sign %s"), sign_name); + semsg(_("E885: Not possible to change sign %s"), name); return FAIL; } return OK; } -/// Unplace the specified sign -static int sign_unplace(int sign_id, char *sign_group, buf_T *buf, linenr_T atlnum) +static int sign_unplace_inner(buf_T *buf, int id, char *group, linenr_T atlnum) { - if (buf->b_signlist == NULL) { // No signs in the buffer - return OK; + if (!buf->b_signs) { // No signs in the buffer + return FAIL; } - if (sign_id == 0) { - // Delete all the signs in the specified buffer - redraw_buf_later(buf, UPD_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) { + if (id == 0 || atlnum > 0 || (group != NULL && *group == '*')) { + // Delete multiple specified signs + if (!buf_delete_signs(buf, group, id, atlnum)) { + return FAIL; + } + } else { + // Delete only a single sign + int64_t ns = group_get_ns(group); + if (ns < 0 || !extmark_del_id(buf, (uint32_t)ns, (uint32_t)id)) { return FAIL; } - redraw_buf_line_later(buf, lnum, false); } // When all the signs in a buffer are removed, force recomputing the // number column width (if enabled) in all the windows displaying the // buffer if 'signcolumn' is set to 'number' in that window. - if (buf->b_signlist == NULL) { + if (!buf->b_signs_with_text) { may_force_numberwidth_recompute(buf, true); } return OK; } -/// Unplace the sign at the current cursor line. -static void sign_unplace_at_cursor(char *groupname) +/// Unplace the specified sign for a single or all buffers +static int sign_unplace(buf_T *buf, int id, char *group, linenr_T atlnum) { - 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); + if (buf != NULL) { + return sign_unplace_inner(buf, id, group, atlnum); } else { - emsg(_("E159: Missing sign number")); + int retval = OK; + FOR_ALL_BUFFERS(cbuf) { + if (!sign_unplace_inner(cbuf, id, group, atlnum)) { + retval = FAIL; + } + } + return retval; } } /// Jump to a sign. -static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf) +static linenr_T sign_jump(int id, char *group, buf_T *buf) { - linenr_T lnum; + linenr_T lnum = buf_findsign(buf, id, group); - if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0) { - semsg(_("E157: Invalid sign ID: %" PRId64), (int64_t)sign_id); + if (lnum <= 0) { + semsg(_("E157: Invalid sign ID: %" PRId32), id); return -1; } @@ -1173,8 +559,7 @@ static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf) } size_t cmdlen = strlen(buf->b_fname) + 24; char *cmd = xmallocz(cmdlen); - snprintf(cmd, cmdlen, "e +%" PRId64 " %s", - (int64_t)lnum, buf->b_fname); + snprintf(cmd, cmdlen, "e +%" PRId64 " %s", (int64_t)lnum, buf->b_fname); do_cmdline_cmd(cmd); xfree(cmd); } @@ -1185,9 +570,8 @@ static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf) } /// ":sign define {name} ..." command -static void sign_define_cmd(char *sign_name, char *cmdline) +static void sign_define_cmd(char *name, char *cmdline) { - char *p = cmdline; char *icon = NULL; char *text = NULL; char *linehl = NULL; @@ -1198,58 +582,38 @@ static void sign_define_cmd(char *sign_name, char *cmdline) // set values for a defined sign. while (true) { - char *arg = skipwhite(p); - if (*arg == NUL) { - break; - } - p = skiptowhite_esc(arg); + char *arg = skipwhite(cmdline); + cmdline = skiptowhite_esc(arg); if (strncmp(arg, "icon=", 5) == 0) { - arg += 5; - XFREE_CLEAR(icon); - icon = xmemdupz(arg, (size_t)(p - arg)); + icon = arg + 5; } else if (strncmp(arg, "text=", 5) == 0) { - arg += 5; - XFREE_CLEAR(text); - text = xmemdupz(arg, (size_t)(p - arg)); + text = arg + 5; } else if (strncmp(arg, "linehl=", 7) == 0) { - arg += 7; - XFREE_CLEAR(linehl); - linehl = xmemdupz(arg, (size_t)(p - arg)); + linehl = arg + 7; } else if (strncmp(arg, "texthl=", 7) == 0) { - arg += 7; - XFREE_CLEAR(texthl); - texthl = xmemdupz(arg, (size_t)(p - arg)); + texthl = arg + 7; } else if (strncmp(arg, "culhl=", 6) == 0) { - arg += 6; - XFREE_CLEAR(culhl); - culhl = xmemdupz(arg, (size_t)(p - arg)); + culhl = arg + 6; } else if (strncmp(arg, "numhl=", 6) == 0) { - arg += 6; - XFREE_CLEAR(numhl); - numhl = xmemdupz(arg, (size_t)(p - arg)); + numhl = arg + 6; } else { semsg(_(e_invarg2), arg); failed = true; break; } + if (*cmdline == NUL) { + break; + } + *cmdline++ = NUL; } if (!failed) { - sign_define_by_name(sign_name, icon, linehl, text, - texthl, culhl, numhl); + sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl); } - - xfree(icon); - xfree(text); - xfree(linehl); - xfree(texthl); - xfree(culhl); - xfree(numhl); } /// ":sign place" command -static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group, - int prio) +static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *name, int id, char *group, int prio) { if (id <= 0) { // List signs placed in a file/buffer @@ -1262,74 +626,37 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, c // :sign place // :sign place group={group} // :sign place group=* - if (lnum >= 0 || sign_name != NULL - || (group != NULL && *group == '\0')) { + if (lnum >= 0 || 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')) { + if (name == NULL || buf == NULL || (group != NULL && *group == '\0')) { emsg(_(e_invarg)); return; } - - sign_place(&id, group, sign_name, buf, lnum, prio); + uint32_t uid = (uint32_t)id; + sign_place(&uid, group, name, buf, lnum, prio); } } /// ":sign unplace" command -static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group) +static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group) { - if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) { + if (lnum >= 0 || 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); - } - } - } + if (id == -1) { + lnum = curwin->w_cursor.lnum; + buf = curwin->w_buffer; + } + + if (!sign_unplace(buf, MAX(0, id), group, lnum) && lnum > 0) { + emsg(_("E159: Missing sign number")); } } @@ -1338,15 +665,14 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, i /// :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, const char *sign_name, int id, char *group) +static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group) { - if (sign_name == NULL && group == NULL && id == -1) { + if (name == NULL && group == NULL && id == -1) { emsg(_(e_argreq)); return; } - if (buf == NULL || (group != NULL && *group == '\0') - || lnum >= 0 || sign_name != NULL) { + if (buf == NULL || (group != NULL && *group == '\0') || lnum >= 0 || 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)); @@ -1360,20 +686,18 @@ static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int /// ":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 *arg, char **sign_name, int *signid, char **group, - int *prio, buf_T **buf, linenr_T *lnum) +static int parse_sign_cmd_args(int cmd, char *arg, char **name, int *id, char **group, int *prio, + buf_T **buf, linenr_T *lnum) { - char *arg1; - char *name; + char *arg1 = arg; char *filename = NULL; int lnum_arg = false; // first arg could be placed sign id - arg1 = arg; if (ascii_isdigit(*arg)) { - *signid = getdigits_int(&arg, true, 0); + *id = getdigits_int(&arg, true, 0); if (!ascii_iswhite(*arg) && *arg != NUL) { - *signid = -1; + *id = -1; arg = arg1; } else { arg = skipwhite(arg); @@ -1387,23 +711,23 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid arg = skiptowhite(arg); lnum_arg = true; } else if (strncmp(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { - if (*signid != -1) { + if (*id != -1) { emsg(_(e_invarg)); return FAIL; } - *signid = -2; + *id = -2; arg = skiptowhite(arg + 1); } else if (strncmp(arg, "name=", 5) == 0) { arg += 5; - name = arg; + char *namep = arg; arg = skiptowhite(arg); if (*arg != NUL) { *arg++ = NUL; } - while (name[0] == '0' && name[1] != NUL) { - name++; + while (namep[0] == '0' && namep[1] != NUL) { + namep++; } - *sign_name = name; + *name = namep; } else if (strncmp(arg, "group=", 6) == 0) { arg += 6; *group = arg; @@ -1442,8 +766,7 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid // 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)) { + if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg) || cmd == SIGNCMD_JUMP)) { *buf = curwin->w_buffer; } return OK; @@ -1453,13 +776,10 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid void ex_sign(exarg_T *eap) { char *arg = eap->arg; - char *p; - int idx; - sign_T *sp; // Parse the subcommand. - p = skiptowhite(arg); - idx = sign_cmd_idx(arg, p); + char *p = skiptowhite(arg); + int idx = sign_cmd_idx(arg, p); if (idx == SIGNCMD_LAST) { semsg(_("E160: Unknown sign command: %s"), arg); return; @@ -1470,14 +790,13 @@ void ex_sign(exarg_T *eap) // 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_T *sp; + map_foreach_value(&sign_map, sp, { sign_list_defined(sp); - } + }); } else if (*arg == NUL) { emsg(_("E156: Missing sign name")); } else { - char *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); @@ -1487,246 +806,161 @@ void ex_sign(exarg_T *eap) while (arg[0] == '0' && arg[1] != NUL) { arg++; } - name = xstrdup(arg); if (idx == SIGNCMD_DEFINE) { - sign_define_cmd(name, p); + sign_define_cmd(arg, p); } else if (idx == SIGNCMD_LIST) { // ":sign list {name}" - sign_list_by_name(name); + sign_list_by_name(arg); } else { // ":sign undefine {name}" - sign_undefine_by_name(name); + sign_undefine_by_name(arg); } - xfree(name); return; } } else { int id = -1; linenr_T lnum = -1; - char *sign_name = NULL; + char *name = NULL; char *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) { + if (parse_sign_cmd_args(idx, arg, &name, &id, &group, &prio, &buf, &lnum) == FAIL) { return; } if (idx == SIGNCMD_PLACE) { - sign_place_cmd(buf, lnum, sign_name, id, group, prio); + sign_place_cmd(buf, lnum, name, id, group, prio); } else if (idx == SIGNCMD_UNPLACE) { - sign_unplace_cmd(buf, lnum, sign_name, id, group); + sign_unplace_cmd(buf, lnum, name, id, group); } else if (idx == SIGNCMD_JUMP) { - sign_jump_cmd(buf, lnum, sign_name, id, group); + sign_jump_cmd(buf, lnum, name, id, group); } } } -/// Return information about a specified sign -static void sign_getinfo(sign_T *sp, dict_T *retdict) +/// Append dictionary of information for a defined sign "sp", or placed +/// sign "mark" to "retlist". Either "sp", or "mark" is NULL. +static void sign_list_append_info(sign_T *sp, MTKey *mark, list_T *retlist) { - const char *p; + dict_T *d = tv_dict_alloc(); + tv_list_append_dict(retlist, d); + + tv_dict_add_str(d, S_LEN("name"), sp ? sp->sn_name : sign_get_name(*mark)); + if (mark != NULL) { + tv_dict_add_nr(d, S_LEN("id"), (int)mark->id); + tv_dict_add_str(d, S_LEN("group"), describe_ns((int)mark->ns, "")); + tv_dict_add_nr(d, S_LEN("lnum"), mark->pos.row + 1); + tv_dict_add_nr(d, S_LEN("priority"), mark->decor_full->priority); + return; + } - tv_dict_add_str(retdict, S_LEN("name"), sp->sn_name); if (sp->sn_icon != NULL) { - tv_dict_add_str(retdict, S_LEN("icon"), sp->sn_icon); + tv_dict_add_str(d, S_LEN("icon"), sp->sn_icon); } if (sp->sn_text != NULL) { - tv_dict_add_str(retdict, S_LEN("text"), 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"), 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"), p); - } - if (sp->sn_cul_hl > 0) { - p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false); - if (p == NULL) { - p = "NONE"; - } - tv_dict_add_str(retdict, S_LEN("culhl"), p); + tv_dict_add_str(d, S_LEN("text"), sp->sn_text); } - 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"), p); - } -} - -/// If 'name' is NULL, return a list of all the defined signs. -/// Otherwise, return information about the specified sign. -static void sign_getlist(const char *name, list_T *retlist) -{ - sign_T *sp = first_sign; - - if (name != NULL) { - sp = sign_find(name, NULL); - if (sp == NULL) { - return; - } - } - - for (; sp != NULL && !got_int; sp = sp->sn_next) { - dict_T *dict = tv_dict_alloc(); - tv_list_append_dict(retlist, dict); - sign_getinfo(sp, dict); - - if (name != NULL) { // handle only the specified sign - break; + static char *arg[] = { "linehl", "texthl", "culhl", "numhl" }; + int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl }; + for (int i = 0; i < 4; i++) { + if (hl[i] > 0) { + const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false); + tv_dict_add_str(d, arg[i], strlen(arg[i]), p ? p : "NONE"); } } } /// 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 + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - sign_entry_T *sign; list_T *const l = tv_list_alloc(kListLenMayKnow); + MarkTreeIter itr[1]; + marktree_itr_get(buf->b_marktree, 0, 0, itr); - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - dict_T *d = sign_get_info(sign); - tv_list_append_dict(l, d); + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full)) { + sign_list_append_info(NULL, &mark, l); + } + marktree_itr_next(buf->b_marktree, itr); } + 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 *sign_group, +static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char *group, list_T *retlist) { - dict_T *d; - list_T *l; - sign_entry_T *sign; - - d = tv_dict_alloc(); + dict_T *d = tv_dict_alloc(); tv_list_append_dict(retlist, d); tv_dict_add_nr(d, S_LEN("bufnr"), buf->b_fnum); - l = tv_list_alloc(kListLenMayKnow); + list_T *l = tv_list_alloc(kListLenMayKnow); tv_dict_add_list(d, S_LEN("signs"), l); - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (!sign_in_group(sign, sign_group)) { - continue; + int64_t ns = group_get_ns(group); + if (!buf->b_signs || ns < 0) { + return; + } + + MarkTreeIter itr[1]; + kvec_t(MTKey) signs = KV_INITIAL_VALUE; + marktree_itr_get(buf->b_marktree, lnum ? lnum - 1 : 0, 0, itr); + + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (lnum && mark.pos.row >= lnum) { + break; + } + if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full) + && (ns == UINT32_MAX || ns == mark.ns) + && ((lnum == 0 && sign_id == 0) + || (sign_id == 0 && lnum == mark.pos.row + 1) + || (lnum == 0 && sign_id == (int)mark.id) + || (lnum == mark.pos.row + 1 && sign_id == (int)mark.id))) { + kv_push(signs, mark); } - if ((lnum == 0 && sign_id == 0) - || (sign_id == 0 && lnum == sign->se_lnum) - || (lnum == 0 && sign_id == sign->se_id) - || (lnum == sign->se_lnum && sign_id == sign->se_id)) { - tv_list_append_dict(l, sign_get_info(sign)); + marktree_itr_next(buf->b_marktree, itr); + } + + if (kv_size(signs)) { + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); + for (size_t i = 0; i < kv_size(signs); i++) { + sign_list_append_info(NULL, &kv_A(signs, i), l); } + kv_destroy(signs); } } /// 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. -static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char *sign_group, - list_T *retlist) +static void sign_get_placed(buf_T *buf, linenr_T lnum, int id, const char *group, list_T *retlist) { if (buf != NULL) { - sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist); + sign_get_placed_in_buf(buf, lnum, id, 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 (cbuf->b_signs) { + sign_get_placed_in_buf(cbuf, 0, id, group, retlist); } } } } -/// List one sign. -static void sign_list_defined(sign_T *sp) -{ - smsg(0, "sign %s", sp->sn_name); - if (sp->sn_icon != NULL) { - msg_puts(" icon="); - msg_outtrans(sp->sn_icon, 0); - msg_puts(_(" (not supported)")); - } - if (sp->sn_text != NULL) { - msg_puts(" text="); - msg_outtrans(sp->sn_text, 0); - } - 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_cul_hl > 0) { - msg_puts(" culhl="); - const char *const p = get_highlight_name_ext(NULL, - sp->sn_cul_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); -} - -/// Undefine/free all signs. void free_signs(void) { - while (first_sign != NULL) { - sign_undefine(first_sign, NULL); - } + cstr_t name; + map_foreach_key(&sign_map, name, { + sign_undefine_by_name(name); + }); } static enum { @@ -1744,12 +978,13 @@ static char *get_nth_sign_name(int idx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // Complete with name of signs already defined + cstr_t name; int current_idx = 0; - for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) { + map_foreach_key(&sign_map, name, { if (current_idx++ == idx) { - return sp->sn_name; + return (char *)name; } - } + }); return NULL; } @@ -1757,35 +992,24 @@ static char *get_nth_sign_name(int idx) static char *get_nth_sign_group_name(int idx) { // Complete with name of sign groups already defined - int current_idx = 0; - int todo = (int)sg_table.ht_used; - for (hashitem_T *hi = sg_table.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - if (current_idx++ == idx) { - signgroup_T *const group = HI2SG(hi); - return group->sg_name; - } - } + if (idx < (int)kv_size(sign_ns)) { + return (char *)describe_ns((NS)kv_A(sign_ns, idx), ""); } return NULL; } -/// Function given to ExpandGeneric() to obtain the sign command -/// expansion. +/// Function given to ExpandGeneric() to obtain the sign command expansion. char *get_sign_name(expand_T *xp, int idx) { switch (expand_what) { case EXP_SUBCMD: return cmds[idx]; case EXP_DEFINE: { - char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", - NULL }; + char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", NULL }; return define_arg[idx]; } case EXP_PLACE: { - char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=", - "buffer=", NULL }; + char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=", "buffer=", NULL }; return place_arg[idx]; } case EXP_LIST: { @@ -1808,29 +1032,24 @@ char *get_sign_name(expand_T *xp, int idx) /// Handle command line completion for :sign command. void set_context_in_sign_cmd(expand_T *xp, char *arg) { - char *end_subcmd; - char *last; - int cmd_idx; - char *begin_subcmd_args; - // Default: expand subcommands. xp->xp_context = EXPAND_SIGN; expand_what = EXP_SUBCMD; xp->xp_pattern = arg; - end_subcmd = skiptowhite(arg); + char *end_subcmd = skiptowhite(arg); if (*end_subcmd == NUL) { // expand subcmd name // :sign {subcmd}<CTRL-D> return; } - cmd_idx = sign_cmd_idx(arg, end_subcmd); + int cmd_idx = sign_cmd_idx(arg, end_subcmd); // :sign {subcmd} {subcmd_args} // | // begin_subcmd_args - begin_subcmd_args = skipwhite(end_subcmd); + char *begin_subcmd_args = skipwhite(end_subcmd); // Expand last argument of subcmd. // @@ -1839,6 +1058,7 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg) // p // Loop until reaching last argument. + char *last; char *p = begin_subcmd_args; do { p = skipwhite(p); @@ -1926,63 +1146,44 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg) /// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on /// failure. -static int sign_define_from_dict(const char *name_arg, dict_T *dict) +static int sign_define_from_dict(char *name, dict_T *dict) { - char *name = NULL; + if (name == NULL) { + name = tv_dict_get_string(dict, "name", false); + if (name == NULL || name[0] == NUL) { + return -1; + } + } + char *icon = NULL; char *linehl = NULL; char *text = NULL; char *texthl = NULL; char *culhl = NULL; char *numhl = NULL; - int retval = -1; - if (name_arg == NULL) { - if (dict == NULL) { - return -1; - } - name = tv_dict_get_string(dict, "name", true); - } else { - name = xstrdup(name_arg); + if (dict != NULL) { + icon = tv_dict_get_string(dict, "icon", false); + linehl = tv_dict_get_string(dict, "linehl", false); + text = tv_dict_get_string(dict, "text", false); + texthl = tv_dict_get_string(dict, "texthl", false); + culhl = tv_dict_get_string(dict, "culhl", false); + numhl = tv_dict_get_string(dict, "numhl", false); } - if (name == NULL || name[0] == NUL) { - goto cleanup; + + if (sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl) == OK) { + return 0; } - if (dict != NULL) { - icon = tv_dict_get_string(dict, "icon", true); - linehl = tv_dict_get_string(dict, "linehl", true); - text = tv_dict_get_string(dict, "text", true); - texthl = tv_dict_get_string(dict, "texthl", true); - culhl = tv_dict_get_string(dict, "culhl", true); - numhl = tv_dict_get_string(dict, "numhl", true); - } - - if (sign_define_by_name(name, icon, linehl, - text, texthl, culhl, numhl) - == OK) { - retval = 0; - } - -cleanup: - xfree(name); - xfree(icon); - xfree(linehl); - xfree(text); - xfree(texthl); - xfree(culhl); - xfree(numhl); - - return retval; + + return -1; } /// Define multiple signs using attributes from list 'l' and store the return /// values in 'retlist'. static void sign_define_multiple(list_T *l, list_T *retlist) { - int retval; - TV_LIST_ITER_CONST(l, li, { - retval = -1; + int retval = -1; if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { retval = sign_define_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); } else { @@ -1995,8 +1196,6 @@ static void sign_define_multiple(list_T *l, list_T *retlist) /// "sign_define()" function void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - const char *name; - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { // Define multiple signs tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -2008,7 +1207,7 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Define a single sign rettv->vval.v_number = -1; - name = tv_get_string_chk(&argvars[0]); + char *name = (char *)tv_get_string_chk(&argvars[0]); if (name == NULL) { return; } @@ -2017,30 +1216,33 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - rettv->vval.v_number = sign_define_from_dict(name, - argvars[1].v_type == - VAR_DICT ? argvars[1].vval.v_dict : NULL); + dict_T *d = argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL; + rettv->vval.v_number = sign_define_from_dict(name, d); } /// "sign_getdefined()" function void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - const char *name = NULL; + sign_T *sp; tv_list_alloc_ret(rettv, 0); - if (argvars[0].v_type != VAR_UNKNOWN) { - name = tv_get_string(&argvars[0]); + if (argvars[0].v_type == VAR_UNKNOWN) { + map_foreach_value(&sign_map, sp, { + sign_list_append_info(sp, NULL, rettv->vval.v_list); + }); + } else { + sp = pmap_get(cstr_t)(&sign_map, tv_get_string(&argvars[0])); + if (sp != NULL) { + sign_list_append_info(sp, NULL, rettv->vval.v_list); + } } - - sign_getlist(name, rettv->vval.v_list); } /// "sign_getplaced()" function void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf = NULL; - dictitem_T *di; linenr_T lnum = 0; int sign_id = 0; const char *group = NULL; @@ -2059,15 +1261,14 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (tv_check_for_nonnull_dict_arg(argvars, 1) == FAIL) { return; } + dictitem_T *di; dict_T *dict = argvars[1].vval.v_dict; 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) { + lnum = tv_get_lnum(&di->di_tv); + if (lnum <= 0) { return; } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); } if ((di = tv_dict_find(dict, "id", -1)) != NULL) { // get sign placed with this identifier @@ -2094,103 +1295,81 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "sign_jump()" function void f_sign_jump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int sign_id; - char *sign_group = NULL; - buf_T *buf; - bool notanum = false; - rettv->vval.v_number = -1; // Sign identifier - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + bool notanum = false; + int id = (int)tv_get_number_chk(&argvars[0], ¬anum); if (notanum) { return; } - if (sign_id <= 0) { + if (id <= 0) { emsg(_(e_invarg)); return; } // Sign group - const char *sign_group_chk = tv_get_string_chk(&argvars[1]); - if (sign_group_chk == NULL) { + char *group = (char *)tv_get_string_chk(&argvars[1]); + if (group == NULL) { return; } - if (sign_group_chk[0] == '\0') { - sign_group = NULL; // global sign group - } else { - sign_group = xstrdup(sign_group_chk); + if (group[0] == NUL) { + group = NULL; } // Buffer to place the sign - buf = get_buf_arg(&argvars[2]); + buf_T *buf = get_buf_arg(&argvars[2]); if (buf == NULL) { - goto cleanup; + return; } - rettv->vval.v_number = sign_jump(sign_id, sign_group, buf); - -cleanup: - xfree(sign_group); + rettv->vval.v_number = sign_jump(id, group, buf); } /// Place a new sign using the values specified in dict 'dict'. Returns the sign -/// identifier if successfully placed, otherwise returns 0. +/// identifier if successfully placed, otherwise returns -1. static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv, typval_T *buf_tv, dict_T *dict) { - int sign_id = 0; - char *group = NULL; - const char *sign_name = NULL; - buf_T *buf = NULL; dictitem_T *di; - linenr_T lnum = 0; - int prio = SIGN_DEF_PRIO; - bool notanum = false; - int ret_sign_id = -1; - // sign identifier + int id = 0; + bool notanum = false; if (id_tv == NULL) { di = tv_dict_find(dict, "id", -1); if (di != NULL) { id_tv = &di->di_tv; } } - if (id_tv == NULL) { - sign_id = 0; - } else { - sign_id = (int)tv_get_number_chk(id_tv, ¬anum); + if (id_tv != NULL) { + id = (int)tv_get_number_chk(id_tv, ¬anum); if (notanum) { return -1; } - if (sign_id < 0) { + if (id < 0) { emsg(_(e_invarg)); return -1; } } - // sign group + char *group = NULL; if (group_tv == NULL) { di = tv_dict_find(dict, "group", -1); if (di != NULL) { group_tv = &di->di_tv; } } - if (group_tv == NULL) { - group = NULL; // global group - } else { + if (group_tv != NULL) { group = (char *)tv_get_string_chk(group_tv); if (group == NULL) { - goto cleanup; + return -1; } - if (group[0] == '\0') { // global sign group + if (group[0] == NUL) { group = NULL; - } else { - group = xstrdup(group); } } - // sign name + char *name = NULL; if (name_tv == NULL) { di = tv_dict_find(dict, "name", -1); if (di != NULL) { @@ -2198,14 +1377,13 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n } } if (name_tv == NULL) { - goto cleanup; + return -1; } - sign_name = tv_get_string_chk(name_tv); - if (sign_name == NULL) { - goto cleanup; + name = (char *)tv_get_string_chk(name_tv); + if (name == NULL) { + return -1; } - // buffer to place the sign if (buf_tv == NULL) { di = tv_dict_find(dict, "buffer", -1); if (di != NULL) { @@ -2213,40 +1391,38 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n } } if (buf_tv == NULL) { - goto cleanup; + return -1; } - buf = get_buf_arg(buf_tv); + buf_T *buf = get_buf_arg(buf_tv); if (buf == NULL) { - goto cleanup; + return -1; } - // line number of the sign + linenr_T lnum = 0; di = tv_dict_find(dict, "lnum", -1); if (di != NULL) { lnum = tv_get_lnum(&di->di_tv); if (lnum <= 0) { emsg(_(e_invarg)); - goto cleanup; + return -1; } } - // sign priority + int prio = SIGN_DEF_PRIO; di = tv_dict_find(dict, "priority", -1); if (di != NULL) { prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); if (notanum) { - goto cleanup; + return -1; } } - if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK) { - ret_sign_id = sign_id; + uint32_t uid = (uint32_t)id; + if (sign_place(&uid, group, name, buf, lnum, prio) == OK) { + return (int)uid; } -cleanup: - xfree(group); - - return ret_sign_id; + return -1; } /// "sign_place()" function @@ -2263,15 +1439,13 @@ void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) dict = argvars[4].vval.v_dict; } - rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3], - dict); + rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], + &argvars[2], &argvars[3], dict); } /// "sign_placelist()" function. Place multiple signs. void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int sign_id; - tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_LIST) { @@ -2281,7 +1455,7 @@ void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Process the List of sign attributes TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - sign_id = -1; + int sign_id = -1; if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); } else { @@ -2294,12 +1468,9 @@ void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// Undefine multiple signs static void sign_undefine_multiple(list_T *l, list_T *retlist) { - char *name; - int retval; - TV_LIST_ITER_CONST(l, li, { - retval = -1; - name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li)); + int retval = -1; + char *name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li)); if (name != NULL && (sign_undefine_by_name(name) == OK)) { retval = 0; } @@ -2342,57 +1513,31 @@ void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) { dictitem_T *di; - int sign_id = 0; + int id = 0; buf_T *buf = NULL; - char *group = NULL; - int retval = -1; - - // sign group - if (group_tv != NULL) { - group = (char *)tv_get_string(group_tv); - } else { - group = tv_dict_get_string(dict, "group", false); - } - if (group != NULL) { - if (group[0] == '\0') { // global sign group - group = NULL; - } else { - group = xstrdup(group); - } + char *group = (group_tv != NULL) ? (char *)tv_get_string(group_tv) + : tv_dict_get_string(dict, "group", false); + if (group != NULL && group[0] == NUL) { + group = NULL; } if (dict != NULL) { if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { buf = get_buf_arg(&di->di_tv); if (buf == NULL) { - goto cleanup; + return -1; } } if (tv_dict_find(dict, "id", -1) != NULL) { - sign_id = (int)tv_dict_get_number(dict, "id"); - if (sign_id <= 0) { + id = (int)tv_dict_get_number(dict, "id"); + if (id <= 0) { emsg(_(e_invarg)); - goto cleanup; + return -1; } } } - if (buf == NULL) { - // Delete the sign in all the buffers - retval = 0; - FOR_ALL_BUFFERS(buf2) { - if (sign_unplace(sign_id, group, buf2, 0) != OK) { - retval = -1; - } - } - } else if (sign_unplace(sign_id, group, buf, 0) == OK) { - retval = 0; - } - -cleanup: - xfree(group); - - return retval; + return sign_unplace(buf, id, group, 0) ? 0 : -1; } /// "sign_unplace()" function @@ -2417,8 +1562,6 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "sign_unplacelist()" function void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int retval; - tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_LIST) { @@ -2427,7 +1570,7 @@ void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - retval = -1; + int retval = -1; if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); } else { diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index 65ab7a72f6..6b8deca02f 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -5,47 +5,23 @@ #include "nvim/pos.h" #include "nvim/types.h" -// signs: line annotations - -// Sign group -typedef struct signgroup_S { - int sg_next_sign_id; ///< next sign id for this group - uint16_t sg_refcount; ///< number of signs in this group - char sg_name[]; ///< 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 sign_entry sign_entry_T; - -struct sign_entry { - int se_id; // unique identifier for each placed sign - int se_typenr; // typenr of sign - int se_priority; // priority for highlighting - bool se_has_text_or_icon; // has text or icon - linenr_T se_lnum; // line number which has this sign - signgroup_T *se_group; // sign group - sign_entry_T *se_next; // next entry in a list of signs - sign_entry_T *se_prev; // previous entry -- for easy reordering -}; - /// Sign attributes. Used by the screen refresh routines. typedef struct { char *text; int hl_id; - int priority; } SignTextAttrs; -#define SIGN_SHOW_MAX 9 - -// Default sign priority for highlighting -#define SIGN_DEF_PRIO 10 - -// type argument for sign_get_attr() -typedef enum { - SIGN_LINEHL, - SIGN_NUMHL, - SIGN_TEXT, -} SignType; +/// Struct to hold the sign properties. +typedef struct sign { + char *sn_name; // name of sign + char *sn_icon; // name of pixmap + char *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_cul_hl; // highlight ID for text on current line when 'cursorline' is set + int sn_num_hl; // highlight ID for line number +} sign_T; + +#define SIGN_WIDTH 2 // Number of display cells for a sign in the signcolumn +#define SIGN_SHOW_MAX 9 // Maximum number of signs shown on a single line +#define SIGN_DEF_PRIO 10 // Default sign highlight priority diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 2f989491d0..a99313cc4e 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -1656,9 +1656,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n varnumber_T virtnum = get_vim_var_nr(VV_VIRTNUM); for (int i = 0; i < width; i++) { if (!fold) { - SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, stcp->sattrs, wp->w_scwidth); + SignTextAttrs *sattr = virtnum ? NULL : &stcp->sattrs[i]; p = sattr && sattr->text ? sattr->text : " "; - stl_items[curitem].minwid = -(sattr ? stcp->sign_cul_id ? stcp->sign_cul_id + stl_items[curitem].minwid = -(sattr && sattr->text ? stcp->sign_cul_id ? stcp->sign_cul_id : sattr->hl_id : (stcp->use_cul ? HLF_CLS : HLF_SC) + 1); } stl_items[curitem].type = Highlight; diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 9c16f76359..b3b3128fdd 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -4680,7 +4680,7 @@ l5 screen:expect{grid=[[ {1: }^l1 | - S2S1l2 | + S1S2l2 | {1: }l3 | {1: }l4 | {1: }l5 | @@ -4720,7 +4720,7 @@ l5 screen:expect{grid=[[ {1: }^l1 | S1{1: }l2 | - S2S1l3 | + S1S2l3 | S2{1: }l4 | {1: }l5 | {1: } | @@ -4765,7 +4765,7 @@ l5 meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) screen:expect{grid=[[ - S4S1^l1 | + S1S4^l1 | x S2l2 | S5{1: }l3 | {1: }l4 | @@ -4792,9 +4792,9 @@ l5 meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) screen:expect{grid=[[ - S3S4S1^l1 | + S1S3S4^l1 | x S2S3l2 | - S5S3{1: }l3 | + S3S5{1: }l3 | S3{1: }l4 | S3{1: }l5 | {1: } | @@ -4848,15 +4848,15 @@ l5 end screen:expect{grid=[[ - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | - X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:^h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:^h} | | ]]} end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 7dcd4cff25..31b54ce0a2 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -274,9 +274,9 @@ describe('Signs', function() -- Line 3 checks that with a limit over the maximum number -- of signs, the ones with the highest Ids are being picked, -- and presented by their sorted Id order. - command('sign place 4 line=3 name=pietSearch buffer=1') - command('sign place 5 line=3 name=pietWarn buffer=1') - command('sign place 3 line=3 name=pietError buffer=1') + command('sign place 6 line=3 name=pietSearch buffer=1') + command('sign place 7 line=3 name=pietWarn buffer=1') + command('sign place 5 line=3 name=pietError buffer=1') screen:expect([[ {1:>>}{8:XX}{6: 1 }a | {8:XX}{1:>>}{6: 2 }b | diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 9f00c7a5d6..61f6419f8c 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -377,6 +377,7 @@ describe('statuscolumn', function() | ]]) command('set breakindent') + command('sign unplace 2') feed('J2gjg0') screen:expect([[ {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| diff --git a/test/old/testdir/test_signs.vim b/test/old/testdir/test_signs.vim index 64271c588e..be81ed0077 100644 --- a/test/old/testdir/test_signs.vim +++ b/test/old/testdir/test_signs.vim @@ -1501,50 +1501,33 @@ func Test_sign_priority() call sign_place(3, '', 'sign3', 'Xsign', \ {'lnum' : 4, 'priority' : 20}) let s = sign_getplaced('Xsign', {'group' : '*'}) - call assert_equal([ + let se = [ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', \ 'priority' : 20}, \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', \ 'priority' : 20}, \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}], - \ s[0].signs) + \ 'priority' : 20}] + call assert_equal(se, s[0].signs) + + " Nvim: signs are always sorted lnum->priority->sign_id->last_modified + " Last modified does not take precedence over sign_id here. + " Place the last sign again with the same priority call sign_place(1, '', 'sign1', 'Xsign', \ {'lnum' : 4, 'priority' : 20}) let s = sign_getplaced('Xsign', {'group' : '*'}) - call assert_equal([ - \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}, - \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}, - \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}], - \ s[0].signs) + call assert_equal(se, s[0].signs) " Place the first sign again with the same priority call sign_place(1, '', 'sign1', 'Xsign', \ {'lnum' : 4, 'priority' : 20}) let s = sign_getplaced('Xsign', {'group' : '*'}) - call assert_equal([ - \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}, - \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}, - \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}], - \ s[0].signs) + call assert_equal(se, s[0].signs) " Place the middle sign again with the same priority call sign_place(3, '', 'sign3', 'Xsign', \ {'lnum' : 4, 'priority' : 20}) let s = sign_getplaced('Xsign', {'group' : '*'}) - call assert_equal([ - \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}, - \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}, - \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '', - \ 'priority' : 20}], - \ s[0].signs) + call assert_equal(se, s[0].signs) call sign_unplace('*') @@ -1670,34 +1653,33 @@ func Test_sign_lnum_adjust() " changes made by this function. let &g:undolevels=&g:undolevels - " Nvim: make sign adjustment when deleting lines match Vim - set signcolumn=yes:1 + " Nvim: deleting a line removes the signs along with it. - " Delete the line with the sign - call deletebufline('', 4) - let l = sign_getplaced(bufnr('')) - call assert_equal(4, l[0].signs[0].lnum) + " " 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) + " " Undo the delete operation + " undo + " let l = sign_getplaced(bufnr('')) + " call assert_equal(5, l[0].signs[0].lnum) - " Break the undo - let &g:undolevels=&g:undolevels + " " Break the undo + " let &g:undolevels=&g: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) + " " 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) + " " 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) - set signcolumn& + " set signcolumn& sign unplace * group=* sign undefine sign1 @@ -1971,7 +1953,8 @@ func Test_sign_funcs_multi() call sign_unplace('*') " Place multiple signs at once with auto-generated sign identifier - call assert_equal([1, 1, 5], sign_placelist([ + " Nvim: next sign id is not reset and is always incremented + call assert_equal([2, 3, 4], sign_placelist([ \ {'group' : 'g1', 'name' : 'sign1', \ 'buffer' : 'Xsign', 'lnum' : 11}, \ {'group' : 'g2', 'name' : 'sign2', @@ -1980,17 +1963,17 @@ func Test_sign_funcs_multi() \ 'buffer' : 'Xsign', 'lnum' : 11}])) let s = sign_getplaced('Xsign', {'group' : '*'}) call assert_equal([ - \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11, + \ {'id' : 4, 'name' : 'sign3', 'lnum' : 11, \ 'group' : '', 'priority' : 10}, - \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11, + \ {'id' : 3, 'name' : 'sign2', 'lnum' : 11, \ 'group' : 'g2', 'priority' : 10}, - \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, + \ {'id' : 2, 'name' : 'sign1', 'lnum' : 11, \ 'group' : 'g1', 'priority' : 10}], s[0].signs) " Change an existing sign without specifying the group - call assert_equal([5], [{'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist()) - let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''}) - call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11, + call assert_equal([4], [{'id' : 4, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist()) + let s = sign_getplaced('Xsign', {'id' : 4, 'group' : ''}) + call assert_equal([{'id' : 4, 'name' : 'sign1', 'lnum' : 11, \ 'group' : '', 'priority' : 10}], s[0].signs) " Place a sign using '.' as the line number @@ -2017,8 +2000,8 @@ func Test_sign_funcs_multi() call assert_fails('call sign_placelist([100])', "E715:") " Unplace multiple signs - call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5}, - \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}])) + call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 4}, + \ {'id' : 2, 'group' : 'g1'}, {'id' : 3, 'group' : 'g2'}])) " Invalid arguments call assert_equal([], []->sign_unplacelist()) |