Mercurial > vim
view src/sign.c @ 17151:ebe9aab81898 v8.1.1575
patch 8.1.1575: callbacks may be garbage collected
commit https://github.com/vim/vim/commit/75a1a9415b9c207de5a29b25c0d1949c6c9c5c61
Author: Bram Moolenaar <Bram@vim.org>
Date: Thu Jun 20 03:45:36 2019 +0200
patch 8.1.1575: callbacks may be garbage collected
Problem: Callbacks may be garbage collected.
Solution: Set reference in callbacks. (Ozaki Kiichi, closes https://github.com/vim/vim/issues/4564)
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Thu, 20 Jun 2019 04:00:07 +0200 |
parents | 811c5c2f5236 |
children | 210c4c5f783d |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * sign.c: functions for managing signs */ #include "vim.h" #if defined(FEAT_SIGNS) || defined(PROTO) /* * Struct to hold the sign properties. */ typedef struct sign sign_T; struct sign { sign_T *sn_next; // next sign in list int sn_typenr; // type number of sign char_u *sn_name; // name of sign char_u *sn_icon; // name of pixmap # ifdef FEAT_SIGN_ICONS void *sn_image; // icon image # endif char_u *sn_text; // text used instead of pixmap int sn_line_hl; // highlight ID for line int sn_text_hl; // highlight ID for text }; static sign_T *first_sign = NULL; static int next_sign_typenr = 1; static void sign_list_defined(sign_T *sp); static void sign_undefine(sign_T *sp, sign_T *sp_prev); static char *cmds[] = { "define", # define SIGNCMD_DEFINE 0 "undefine", # define SIGNCMD_UNDEFINE 1 "list", # define SIGNCMD_LIST 2 "place", # define SIGNCMD_PLACE 3 "unplace", # define SIGNCMD_UNPLACE 4 "jump", # define SIGNCMD_JUMP 5 NULL # define SIGNCMD_LAST 6 }; static hashtab_T sg_table; // sign group (signgroup_T) hashtable static int next_sign_id = 1; // next sign id in the global group /* * Initialize data needed for managing signs */ void init_signs(void) { hash_init(&sg_table); // sign group hash table } /* * A new sign in group 'groupname' is added. If the group is not present, * create it. Otherwise reference the group. */ static signgroup_T * sign_group_ref(char_u *groupname) { hash_T hash; hashitem_T *hi; signgroup_T *group; hash = hash_hash(groupname); hi = hash_lookup(&sg_table, groupname, hash); if (HASHITEM_EMPTY(hi)) { // new group group = alloc(sizeof(signgroup_T) + STRLEN(groupname)); if (group == NULL) return NULL; STRCPY(group->sg_name, groupname); group->refcount = 1; group->next_sign_id = 1; hash_add_item(&sg_table, hi, group->sg_name, hash); } else { // existing group group = HI2SG(hi); group->refcount++; } return group; } /* * A sign in group 'groupname' is removed. If all the signs in this group are * removed, then remove the group. */ static void sign_group_unref(char_u *groupname) { hashitem_T *hi; signgroup_T *group; hi = hash_find(&sg_table, groupname); if (!HASHITEM_EMPTY(hi)) { group = HI2SG(hi); group->refcount--; if (group->refcount == 0) { // All the signs in this group are removed hash_remove(&sg_table, hi); vim_free(group); } } } /* * Returns TRUE if 'sign' is in 'group'. * A sign can either be in the global group (sign->group == NULL) * or in a named group. If 'group' is '*', then the sign is part of the group. */ static int sign_in_group(signlist_T *sign, char_u *group) { return ((group != NULL && STRCMP(group, "*") == 0) || (group == NULL && sign->group == NULL) || (group != NULL && sign->group != NULL && STRCMP(group, sign->group->sg_name) == 0)); } /* * Get the next free sign identifier in the specified group */ static int sign_group_get_next_signid(buf_T *buf, char_u *groupname) { int id = 1; signgroup_T *group = NULL; signlist_T *sign; hashitem_T *hi; int found = FALSE; if (groupname != NULL) { hi = hash_find(&sg_table, groupname); if (HASHITEM_EMPTY(hi)) return id; group = HI2SG(hi); } // Search for the next usable sign identifier while (!found) { if (group == NULL) id = next_sign_id++; // global group else id = group->next_sign_id++; // Check whether this sign is already placed in the buffer found = TRUE; FOR_ALL_SIGNS_IN_BUF(buf, sign) { if (id == sign->id && sign_in_group(sign, groupname)) { found = FALSE; // sign identifier is in use break; } } } return id; } /* * Insert a new sign into the signlist for buffer 'buf' between the 'prev' and * 'next' signs. */ static void insert_sign( buf_T *buf, // buffer to store sign in signlist_T *prev, // previous sign entry signlist_T *next, // next sign entry int id, // sign ID char_u *group, // sign group; NULL for global group int prio, // sign priority linenr_T lnum, // line number which gets the mark int typenr) // typenr of sign we are adding { signlist_T *newsign; newsign = lalloc_id(sizeof(signlist_T), FALSE, aid_insert_sign); if (newsign != NULL) { newsign->id = id; newsign->lnum = lnum; newsign->typenr = typenr; if (group != NULL) { newsign->group = sign_group_ref(group); if (newsign->group == NULL) { vim_free(newsign); return; } } else newsign->group = NULL; newsign->priority = prio; newsign->next = next; newsign->prev = prev; if (next != NULL) next->prev = 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, NOT_VALID); changed_line_abv_curs(); } // first sign in signlist buf->b_signlist = newsign; #ifdef FEAT_NETBEANS_INTG if (netbeans_active()) buf->b_has_sign_column = TRUE; #endif } else prev->next = newsign; } } /* * Insert a new sign sorted by line number and sign priority. */ static void insert_sign_by_lnum_prio( buf_T *buf, // buffer to store sign in signlist_T *prev, // previous sign entry int id, // sign ID char_u *group, // sign group; NULL for global group int prio, // sign priority linenr_T lnum, // line number which gets the mark int typenr) // typenr of sign we are adding { signlist_T *sign; // keep signs sorted by lnum and by priority: insert new sign at // the proper position in the list for this lnum. while (prev != NULL && prev->lnum == lnum && prev->priority <= prio) prev = prev->prev; if (prev == NULL) sign = buf->b_signlist; else sign = prev->next; insert_sign(buf, prev, sign, id, group, prio, lnum, typenr); } /* * Get the name of a sign by its typenr. */ static char_u * sign_typenr2name(int typenr) { sign_T *sp; for (sp = first_sign; sp != NULL; sp = sp->sn_next) if (sp->sn_typenr == typenr) return sp->sn_name; return (char_u *)_("[Deleted]"); } /* * Return information about a sign in a Dict */ static dict_T * sign_get_info(signlist_T *sign) { dict_T *d; if ((d = dict_alloc_id(aid_sign_getinfo)) == NULL) return NULL; dict_add_number(d, "id", sign->id); dict_add_string(d, "group", (sign->group == NULL) ? (char_u *)"" : sign->group->sg_name); dict_add_number(d, "lnum", sign->lnum); dict_add_string(d, "name", sign_typenr2name(sign->typenr)); dict_add_number(d, "priority", sign->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, signlist_T *sign) { signlist_T *p = NULL; // 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->prev == NULL || sign->prev->lnum != sign->lnum || sign->prev->priority > sign->priority) && (sign->next == NULL || sign->next->lnum != sign->lnum || sign->next->priority < sign->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 p = sign; while (p->prev != NULL && p->prev->lnum == sign->lnum && p->prev->priority <= sign->priority) p = p->prev; if (p == sign) { // Sign not found. Search forward for a sign with priority just before // 'sign'. p = sign->next; while (p->next != NULL && p->next->lnum == sign->lnum && p->next->priority > sign->priority) p = p->next; } // Remove 'sign' from the list if (buf->b_signlist == sign) buf->b_signlist = sign->next; if (sign->prev != NULL) sign->prev->next = sign->next; if (sign->next != NULL) sign->next->prev = sign->prev; sign->prev = NULL; sign->next = NULL; // Re-insert 'sign' at the right place if (p->priority <= sign->priority) { // 'sign' has a higher priority and should be inserted before 'p' sign->prev = p->prev; sign->next = p; p->prev = sign; if (sign->prev != NULL) sign->prev->next = sign; if (buf->b_signlist == p) buf->b_signlist = sign; } else { // 'sign' has a lower priority and should be inserted after 'p' sign->prev = p; sign->next = p->next; p->next = sign; if (sign->next != NULL) sign->next->prev = sign; } } /* * Add the sign into the signlist. Find the right spot to do it though. */ static void buf_addsign( buf_T *buf, // buffer to store sign in int id, // sign ID char_u *groupname, // sign group int prio, // sign priority linenr_T lnum, // line number which gets the mark int typenr) // typenr of sign we are adding { signlist_T *sign; // a sign in the signlist signlist_T *prev; // the previous sign prev = NULL; FOR_ALL_SIGNS_IN_BUF(buf, sign) { if (lnum == sign->lnum && id == sign->id && sign_in_group(sign, groupname)) { // Update an existing sign sign->typenr = typenr; sign->priority = prio; sign_sort_by_prio_on_line(buf, sign); return; } else if (lnum < sign->lnum) { insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr); return; } prev = sign; } insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr); return; } /* * 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. */ static linenr_T buf_change_sign_type( buf_T *buf, // buffer to store sign in int markId, // sign ID char_u *group, // sign group int typenr) // typenr of sign we are adding { signlist_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { if (sign->id == markId && sign_in_group(sign, group)) { sign->typenr = typenr; return sign->lnum; } } return (linenr_T)0; } /* * Return the type number of the sign at line number 'lnum' in buffer 'buf' * which has the attribute specified by 'type'. Returns 0 if a sign is not * found at the line number or it doesn't have the specified attribute. */ int buf_getsigntype( buf_T *buf, linenr_T lnum, int type) // SIGN_ICON, SIGN_TEXT, SIGN_ANY, SIGN_LINEHL { signlist_T *sign; // a sign in a b_signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) if (sign->lnum == lnum && (type == SIGN_ANY # ifdef FEAT_SIGN_ICONS || (type == SIGN_ICON && sign_get_image(sign->typenr) != NULL) # endif || (type == SIGN_TEXT && sign_get_text(sign->typenr) != NULL) || (type == SIGN_LINEHL && sign_get_attr(sign->typenr, TRUE) != 0))) return sign->typenr; return 0; } /* * Delete sign 'id' in group 'group' from buffer 'buf'. * If 'id' is zero, then delete all the signs in group 'group'. Otherwise * delete only the specified sign. * If 'group' is '*', then delete the sign in all the groups. If 'group' is * NULL, then delete the sign in the global group. Otherwise delete the sign in * the specified group. * Returns the line number of the deleted sign. If multiple signs are deleted, * then returns the line number of the last sign deleted. */ linenr_T buf_delsign( buf_T *buf, // buffer sign is stored in linenr_T atlnum, // sign at this line, 0 - at any line int id, // sign id char_u *group) // sign group { signlist_T **lastp; // pointer to pointer to current sign signlist_T *sign; // a sign in a b_signlist signlist_T *next; // the next sign in a b_signlist linenr_T lnum; // line number whose sign was deleted lastp = &buf->b_signlist; lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { next = sign->next; if ((id == 0 || sign->id == id) && (atlnum == 0 || sign->lnum == atlnum) && sign_in_group(sign, group)) { *lastp = next; if (next != NULL) next->prev = sign->prev; lnum = sign->lnum; if (sign->group != NULL) sign_group_unref(sign->group->sg_name); vim_free(sign); redraw_buf_line_later(buf, lnum); // Check whether only one sign needs to be deleted // If deleting a sign with a specific identifier in a particular // group or deleting any sign at a particular line number, delete // only one sign. if (group == NULL || (*group != '*' && id != 0) || (*group == '*' && atlnum != 0)) break; } else lastp = &sign->next; } // When 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, NOT_VALID); changed_line_abv_curs(); } return lnum; } /* * Find the line number of the sign with the requested id in group 'group'. If * the sign does not exist, return 0 as the line number. This will still let * the correct file get loaded. */ int buf_findsign( buf_T *buf, // buffer to store sign in int id, // sign ID char_u *group) // sign group { signlist_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) if (sign->id == id && sign_in_group(sign, group)) return sign->lnum; return 0; } /* * Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is * not found at the line. If 'groupname' is NULL, searches in the global group. */ static signlist_T * buf_getsign_at_line( buf_T *buf, // buffer whose sign we are searching for linenr_T lnum, // line number of sign char_u *groupname) // sign group name { signlist_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) if (sign->lnum == lnum && sign_in_group(sign, groupname)) return sign; return NULL; } /* * Return the identifier of the sign at line number 'lnum' in buffer 'buf'. */ int buf_findsign_id( buf_T *buf, // buffer whose sign we are searching for linenr_T lnum, // line number of sign char_u *groupname) // sign group name { signlist_T *sign; // a sign in the signlist sign = buf_getsign_at_line(buf, lnum, groupname); if (sign != NULL) return sign->id; return 0; } # if defined(FEAT_NETBEANS_INTG) || defined(PROTO) /* * See if a given type of sign exists on a specific line. */ int buf_findsigntype_id( buf_T *buf, // buffer whose sign we are searching for linenr_T lnum, // line number of sign int typenr) // sign type number { signlist_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) if (sign->lnum == lnum && sign->typenr == typenr) return sign->id; return 0; } # if defined(FEAT_SIGN_ICONS) || defined(PROTO) /* * Return the number of icons on the given line. */ int buf_signcount(buf_T *buf, linenr_T lnum) { signlist_T *sign; // a sign in the signlist int count = 0; FOR_ALL_SIGNS_IN_BUF(buf, sign) if (sign->lnum == lnum) if (sign_get_image(sign->typenr) != NULL) count++; return count; } # endif /* FEAT_SIGN_ICONS */ # endif /* FEAT_NETBEANS_INTG */ /* * Delete signs in group 'group' in buffer "buf". If 'group' is '*', then * delete all the signs. */ void buf_delete_signs(buf_T *buf, char_u *group) { signlist_T *sign; signlist_T **lastp; // pointer to pointer to current sign signlist_T *next; // When deleting the last sign need to redraw the windows to remove the // sign column. Not when curwin is NULL (this means we're exiting). if (buf->b_signlist != NULL && curwin != NULL) { redraw_buf_later(buf, NOT_VALID); changed_line_abv_curs(); } lastp = &buf->b_signlist; for (sign = buf->b_signlist; sign != NULL; sign = next) { next = sign->next; if (sign_in_group(sign, group)) { *lastp = next; if (next != NULL) next->prev = sign->prev; if (sign->group != NULL) sign_group_unref(sign->group->sg_name); vim_free(sign); } else lastp = &sign->next; } } /* * List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. */ static void sign_list_placed(buf_T *rbuf, char_u *sign_group) { buf_T *buf; signlist_T *sign; char lbuf[MSG_BUF_LEN]; char group[MSG_BUF_LEN]; msg_puts_title(_("\n--- Signs ---")); msg_putchar('\n'); if (rbuf == NULL) buf = firstbuf; else buf = rbuf; while (buf != NULL && !got_int) { if (buf->b_signlist != NULL) { vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname); msg_puts_attr(lbuf, HL_ATTR(HLF_D)); msg_putchar('\n'); } FOR_ALL_SIGNS_IN_BUF(buf, sign) { if (got_int) break; if (!sign_in_group(sign, sign_group)) continue; if (sign->group != NULL) vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"), sign->group->sg_name); else group[0] = '\0'; vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%ld id=%d%s name=%s priority=%d"), (long)sign->lnum, sign->id, group, sign_typenr2name(sign->typenr), sign->priority); msg_puts(lbuf); msg_putchar('\n'); } if (rbuf != NULL) break; buf = buf->b_next; } } /* * Adjust a placed sign for inserted/deleted lines. */ void sign_mark_adjust( linenr_T line1, linenr_T line2, long amount, long amount_after) { signlist_T *sign; // a sign in a b_signlist linenr_T new_lnum; FOR_ALL_SIGNS_IN_BUF(curbuf, sign) { // Ignore changes to lines after the sign if (sign->lnum < line1) continue; new_lnum = sign->lnum; if (sign->lnum >= line1 && sign->lnum <= line2) { if (amount != MAXLNUM) new_lnum += amount; } else if (sign->lnum > line2) // Lines inserted or deleted before the sign new_lnum += amount_after; // If the new sign line number is past the last line in the buffer, // then don't adjust the line number. Otherwise, it will always be past // the last line and will not be visible. if (new_lnum <= curbuf->b_ml.ml_line_count) sign->lnum = new_lnum; } } /* * Find index of a ":sign" subcmd from its name. * "*end_cmd" must be writable. */ static int sign_cmd_idx( char_u *begin_cmd, // begin of sign subcmd char_u *end_cmd) // just after sign subcmd { int idx; char save = *end_cmd; *end_cmd = NUL; for (idx = 0; ; ++idx) if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0) break; *end_cmd = save; return idx; } /* * Find a sign by name. Also returns pointer to the previous sign. */ static sign_T * sign_find(char_u *name, sign_T **sp_prev) { sign_T *sp; if (sp_prev != NULL) *sp_prev = NULL; for (sp = first_sign; sp != NULL; sp = sp->sn_next) { if (STRCMP(sp->sn_name, name) == 0) break; if (sp_prev != NULL) *sp_prev = sp; } return sp; } /* * Allocate a new sign */ static sign_T * alloc_new_sign(char_u *name) { sign_T *sp; sign_T *lp; int start = next_sign_typenr; // Allocate a new sign. sp = alloc_clear_id(sizeof(sign_T), aid_sign_define_by_name); if (sp == NULL) return NULL; // 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) { vim_free(sp); emsg(_("E612: Too many signs defined")); return NULL; } lp = first_sign; // start all over continue; } lp = lp->sn_next; } sp->sn_typenr = next_sign_typenr; if (++next_sign_typenr == MAX_TYPENR) next_sign_typenr = 1; // wrap around sp->sn_name = vim_strsave(name); if (sp->sn_name == NULL) // out of memory { vim_free(sp); return NULL; } return sp; } /* * Initialize the icon information for a new sign */ static void sign_define_init_icon(sign_T *sp, char_u *icon) { vim_free(sp->sn_icon); sp->sn_icon = vim_strsave(icon); backslash_halve(sp->sn_icon); # ifdef FEAT_SIGN_ICONS if (gui.in_use) { out_flush(); if (sp->sn_image != NULL) gui_mch_destroy_sign(sp->sn_image); sp->sn_image = gui_mch_register_sign(sp->sn_icon); } # endif } /* * Initialize the text for a new sign */ static int sign_define_init_text(sign_T *sp, char_u *text) { char_u *s; char_u *endp; int cells; int len; endp = text + (int)STRLEN(text); // Remove backslashes so that it is possible to use a space. for (s = text; s + 1 < endp; ++s) if (*s == '\\') { STRMOVE(s, s + 1); --endp; } // Count cells and check for non-printable chars if (has_mbyte) { cells = 0; for (s = text; s < endp; s += (*mb_ptr2len)(s)) { if (!vim_isprintc((*mb_ptr2char)(s))) break; cells += (*mb_ptr2cells)(s); } } else { for (s = text; s < endp; ++s) if (!vim_isprintc(*s)) break; cells = (int)(s - text); } // Currently sign text must be one or two display cells if (s != endp || cells < 1 || cells > 2) { semsg(_("E239: Invalid sign text: %s"), text); return FAIL; } vim_free(sp->sn_text); // Allocate one byte more if we need to pad up // with a space. len = (int)(endp - text + ((cells == 1) ? 1 : 0)); sp->sn_text = vim_strnsave(text, len); // For single character sign text, pad with a space. if (sp->sn_text != NULL && cells == 1) STRCPY(sp->sn_text + len - 1, " "); return OK; } /* * Define a new sign or update an existing sign */ int sign_define_by_name( char_u *name, char_u *icon, char_u *linehl, char_u *text, char_u *texthl) { sign_T *sp_prev; sign_T *sp; sp = sign_find(name, &sp_prev); if (sp == NULL) { sp = alloc_new_sign(name); if (sp == NULL) return FAIL; // add the new sign to the list of signs if (sp_prev == NULL) first_sign = sp; else sp_prev->sn_next = sp; } // set values for a defined sign. if (icon != NULL) sign_define_init_icon(sp, icon); if (text != NULL && (sign_define_init_text(sp, text) == FAIL)) return FAIL; if (linehl != NULL) sp->sn_line_hl = syn_check_group(linehl, (int)STRLEN(linehl)); if (texthl != NULL) sp->sn_text_hl = syn_check_group(texthl, (int)STRLEN(texthl)); return OK; } /* * Free the sign specified by 'name'. */ int sign_undefine_by_name(char_u *name) { sign_T *sp_prev; sign_T *sp; sp = sign_find(name, &sp_prev); if (sp == NULL) { semsg(_("E155: Unknown sign: %s"), name); return FAIL; } sign_undefine(sp, sp_prev); return OK; } /* * List the signs matching 'name' */ static void sign_list_by_name(char_u *name) { sign_T *sp; sp = sign_find(name, NULL); if (sp != NULL) sign_list_defined(sp); else semsg(_("E155: Unknown sign: %s"), name); } /* * Place a sign at the specified file location or update a sign. */ int sign_place( int *sign_id, char_u *sign_group, char_u *sign_name, buf_T *buf, linenr_T lnum, int prio) { sign_T *sp; // Check for reserved character '*' in group name if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0')) return FAIL; for (sp = first_sign; sp != NULL; sp = sp->sn_next) if (STRCMP(sp->sn_name, sign_name) == 0) break; if (sp == NULL) { semsg(_("E155: Unknown sign: %s"), sign_name); return FAIL; } if (*sign_id == 0) *sign_id = sign_group_get_next_signid(buf, sign_group); if (lnum > 0) // ":sign place {id} line={lnum} name={name} file={fname}": // place a sign buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr); else // ":sign place {id} file={fname}": change sign type lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr); if (lnum > 0) redraw_buf_line_later(buf, lnum); else { semsg(_("E885: Not possible to change sign %s"), sign_name); return FAIL; } return OK; } /* * Unplace the specified sign */ int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) { if (buf->b_signlist == NULL) // No signs in the buffer return OK; if (sign_id == 0) { // Delete all the signs in the specified buffer redraw_buf_later(buf, NOT_VALID); buf_delete_signs(buf, sign_group); } else { linenr_T lnum; // Delete only the specified signs lnum = buf_delsign(buf, atlnum, sign_id, sign_group); if (lnum == 0) return FAIL; } return OK; } /* * Unplace the sign at the current cursor line. */ static void sign_unplace_at_cursor(char_u *groupname) { int id = -1; id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname); if (id > 0) sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum); else emsg(_("E159: Missing sign number")); } /* * Jump to a sign. */ linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) { linenr_T lnum; if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0) { semsg(_("E157: Invalid sign ID: %d"), sign_id); return -1; } // goto a sign ... if (buf_jump_open_win(buf) != NULL) { // ... in a current window curwin->w_cursor.lnum = lnum; check_cursor_lnum(); beginline(BL_WHITE); } else { // ... not currently in a window char_u *cmd; if (buf->b_fname == NULL) { emsg(_("E934: Cannot jump to a buffer that does not have a name")); return -1; } cmd = alloc(STRLEN(buf->b_fname) + 25); if (cmd == NULL) return -1; sprintf((char *)cmd, "e +%ld %s", (long)lnum, buf->b_fname); do_cmdline_cmd(cmd); vim_free(cmd); } # ifdef FEAT_FOLDING foldOpenCursor(); # endif return lnum; } /* * ":sign define {name} ..." command */ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) { char_u *arg; char_u *p = cmdline; char_u *icon = NULL; char_u *text = NULL; char_u *linehl = NULL; char_u *texthl = NULL; int failed = FALSE; // set values for a defined sign. for (;;) { arg = skipwhite(p); if (*arg == NUL) break; p = skiptowhite_esc(arg); if (STRNCMP(arg, "icon=", 5) == 0) { arg += 5; icon = vim_strnsave(arg, (int)(p - arg)); } else if (STRNCMP(arg, "text=", 5) == 0) { arg += 5; text = vim_strnsave(arg, (int)(p - arg)); } else if (STRNCMP(arg, "linehl=", 7) == 0) { arg += 7; linehl = vim_strnsave(arg, (int)(p - arg)); } else if (STRNCMP(arg, "texthl=", 7) == 0) { arg += 7; texthl = vim_strnsave(arg, (int)(p - arg)); } else { semsg(_(e_invarg2), arg); failed = TRUE; break; } } if (!failed) sign_define_by_name(sign_name, icon, linehl, text, texthl); vim_free(icon); vim_free(text); vim_free(linehl); vim_free(texthl); } /* * ":sign place" command */ static void sign_place_cmd( buf_T *buf, linenr_T lnum, char_u *sign_name, int id, char_u *group, int prio) { if (id <= 0) { // List signs placed in a file/buffer // :sign place file={fname} // :sign place group={group} file={fname} // :sign place group=* file={fname} // :sign place buffer={nr} // :sign place group={group} buffer={nr} // :sign place group=* buffer={nr} // :sign place // :sign place group={group} // :sign place group=* if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) emsg(_(e_invarg)); else sign_list_placed(buf, group); } else { // Place a new sign if (sign_name == NULL || buf == NULL || (group != NULL && *group == '\0')) { emsg(_(e_invarg)); return; } sign_place(&id, group, sign_name, buf, lnum, prio); } } /* * ":sign unplace" command */ static void sign_unplace_cmd( buf_T *buf, linenr_T lnum, char_u *sign_name, int id, char_u *group) { if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) { emsg(_(e_invarg)); return; } if (id == -2) { if (buf != NULL) // :sign unplace * file={fname} // :sign unplace * group={group} file={fname} // :sign unplace * group=* file={fname} // :sign unplace * buffer={nr} // :sign unplace * group={group} buffer={nr} // :sign unplace * group=* buffer={nr} sign_unplace(0, group, buf, 0); else // :sign unplace * // :sign unplace * group={group} // :sign unplace * group=* FOR_ALL_BUFFERS(buf) if (buf->b_signlist != NULL) buf_delete_signs(buf, 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(buf) sign_unplace(id, group, buf, 0); } } } } /* * Jump to a placed sign commands: * :sign jump {id} file={fname} * :sign jump {id} buffer={nr} * :sign jump {id} group={group} file={fname} * :sign jump {id} group={group} buffer={nr} */ static void sign_jump_cmd( buf_T *buf, linenr_T lnum, char_u *sign_name, int id, char_u *group) { if (sign_name == NULL && group == NULL && id == -1) { emsg(_(e_argreq)); return; } if (buf == NULL || (group != NULL && *group == '\0') || lnum >= 0 || sign_name != NULL) { // File or buffer is not specified or an empty group is used // or a line number or a sign name is specified. emsg(_(e_invarg)); return; } (void)sign_jump(id, group, buf); } /* * Parse the command line arguments for the ":sign place", ":sign unplace" and * ":sign jump" commands. * The supported arguments are: line={lnum} name={name} group={group} * priority={prio} and file={fname} or buffer={nr}. */ static int parse_sign_cmd_args( int cmd, char_u *arg, char_u **sign_name, int *signid, char_u **group, int *prio, buf_T **buf, linenr_T *lnum) { char_u *arg1; char_u *name; char_u *filename = NULL; int lnum_arg = FALSE; // first arg could be placed sign id arg1 = arg; if (VIM_ISDIGIT(*arg)) { *signid = getdigits(&arg); if (!VIM_ISWHITE(*arg) && *arg != NUL) { *signid = -1; arg = arg1; } else arg = skipwhite(arg); } while (*arg != NUL) { if (STRNCMP(arg, "line=", 5) == 0) { arg += 5; *lnum = atoi((char *)arg); arg = skiptowhite(arg); lnum_arg = TRUE; } else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { if (*signid != -1) { emsg(_(e_invarg)); return FAIL; } *signid = -2; arg = skiptowhite(arg + 1); } else if (STRNCMP(arg, "name=", 5) == 0) { arg += 5; name = arg; arg = skiptowhite(arg); if (*arg != NUL) *arg++ = NUL; while (name[0] == '0' && name[1] != NUL) ++name; *sign_name = name; } else if (STRNCMP(arg, "group=", 6) == 0) { arg += 6; *group = arg; arg = skiptowhite(arg); if (*arg != NUL) *arg++ = NUL; } else if (STRNCMP(arg, "priority=", 9) == 0) { arg += 9; *prio = atoi((char *)arg); arg = skiptowhite(arg); } else if (STRNCMP(arg, "file=", 5) == 0) { arg += 5; filename = arg; *buf = buflist_findname_exp(arg); break; } else if (STRNCMP(arg, "buffer=", 7) == 0) { arg += 7; filename = arg; *buf = buflist_findnr((int)getdigits(&arg)); if (*skipwhite(arg) != NUL) emsg(_(e_trailing)); break; } else { emsg(_(e_invarg)); return FAIL; } arg = skipwhite(arg); } if (filename != NULL && *buf == NULL) { semsg(_("E158: Invalid buffer name: %s"), filename); return FAIL; } // If the filename is not supplied for the sign place or the sign jump // command, then use the current buffer. if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg) || cmd == SIGNCMD_JUMP)) *buf = curwin->w_buffer; return OK; } /* * ":sign" command */ void ex_sign(exarg_T *eap) { char_u *arg = eap->arg; char_u *p; int idx; sign_T *sp; buf_T *buf = NULL; // Parse the subcommand. p = skiptowhite(arg); idx = sign_cmd_idx(arg, p); if (idx == SIGNCMD_LAST) { semsg(_("E160: Unknown sign command: %s"), arg); return; } arg = skipwhite(p); if (idx <= SIGNCMD_LIST) { // Define, undefine or list signs. if (idx == SIGNCMD_LIST && *arg == NUL) { // ":sign list": list all defined signs for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next) sign_list_defined(sp); } else if (*arg == NUL) emsg(_("E156: Missing sign name")); else { char_u *name; // Isolate the sign name. If it's a number skip leading zeroes, // so that "099" and "99" are the same sign. But keep "0". p = skiptowhite(arg); if (*p != NUL) *p++ = NUL; while (arg[0] == '0' && arg[1] != NUL) ++arg; name = vim_strsave(arg); if (idx == SIGNCMD_DEFINE) sign_define_cmd(name, p); else if (idx == SIGNCMD_LIST) // ":sign list {name}" sign_list_by_name(name); else // ":sign undefine {name}" sign_undefine_by_name(name); vim_free(name); return; } } else { int id = -1; linenr_T lnum = -1; char_u *sign_name = NULL; char_u *group = NULL; int prio = SIGN_DEF_PRIO; // Parse command line arguments if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio, &buf, &lnum) == FAIL) return; if (idx == SIGNCMD_PLACE) sign_place_cmd(buf, lnum, sign_name, id, group, prio); else if (idx == SIGNCMD_UNPLACE) sign_unplace_cmd(buf, lnum, sign_name, id, group); else if (idx == SIGNCMD_JUMP) sign_jump_cmd(buf, lnum, sign_name, id, group); } } /* * Return information about a specified sign */ static void sign_getinfo(sign_T *sp, dict_T *retdict) { char_u *p; dict_add_string(retdict, "name", (char_u *)sp->sn_name); if (sp->sn_icon != NULL) dict_add_string(retdict, "icon", (char_u *)sp->sn_icon); if (sp->sn_text != NULL) dict_add_string(retdict, "text", (char_u *)sp->sn_text); if (sp->sn_line_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE); if (p == NULL) p = (char_u *)"NONE"; dict_add_string(retdict, "linehl", (char_u *)p); } if (sp->sn_text_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE); if (p == NULL) p = (char_u *)"NONE"; dict_add_string(retdict, "texthl", (char_u *)p); } } /* * If 'name' is NULL, return a list of all the defined signs. * Otherwise, return information about the specified sign. */ void sign_getlist(char_u *name, list_T *retlist) { sign_T *sp = first_sign; dict_T *dict; if (name != NULL) { sp = sign_find(name, NULL); if (sp == NULL) return; } for (; sp != NULL && !got_int; sp = sp->sn_next) { if ((dict = dict_alloc_id(aid_sign_getlist)) == NULL) return; if (list_append_dict(retlist, dict) == FAIL) return; sign_getinfo(sp, dict); if (name != NULL) // handle only the specified sign break; } } /* * Returns information about signs placed in a buffer as list of dicts. */ void get_buffer_signs(buf_T *buf, list_T *l) { signlist_T *sign; dict_T *d; FOR_ALL_SIGNS_IN_BUF(buf, sign) { if ((d = sign_get_info(sign)) != NULL) list_append_dict(l, d); } } /* * Return information about all the signs placed in a buffer */ static void sign_get_placed_in_buf( buf_T *buf, linenr_T lnum, int sign_id, char_u *sign_group, list_T *retlist) { dict_T *d; list_T *l; signlist_T *sign; dict_T *sdict; if ((d = dict_alloc_id(aid_sign_getplaced_dict)) == NULL) return; list_append_dict(retlist, d); dict_add_number(d, "bufnr", (long)buf->b_fnum); if ((l = list_alloc_id(aid_sign_getplaced_list)) == NULL) return; dict_add_list(d, "signs", l); FOR_ALL_SIGNS_IN_BUF(buf, sign) { if (!sign_in_group(sign, sign_group)) continue; if ((lnum == 0 && sign_id == 0) || (sign_id == 0 && lnum == sign->lnum) || (lnum == 0 && sign_id == sign->id) || (lnum == sign->lnum && sign_id == sign->id)) { if ((sdict = sign_get_info(sign)) != NULL) list_append_dict(l, sdict); } } } /* * Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the * sign placed at the line number. If 'lnum' is zero, return all the signs * placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers. */ void sign_get_placed( buf_T *buf, linenr_T lnum, int sign_id, char_u *sign_group, list_T *retlist) { if (buf != NULL) sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist); else { FOR_ALL_BUFFERS(buf) { if (buf->b_signlist != NULL) sign_get_placed_in_buf(buf, 0, sign_id, sign_group, retlist); } } } # if defined(FEAT_SIGN_ICONS) || defined(PROTO) /* * Allocate the icons. Called when the GUI has started. Allows defining * signs before it starts. */ void sign_gui_started(void) { sign_T *sp; for (sp = first_sign; sp != NULL; sp = sp->sn_next) if (sp->sn_icon != NULL) sp->sn_image = gui_mch_register_sign(sp->sn_icon); } # endif /* * List one sign. */ static void sign_list_defined(sign_T *sp) { char_u *p; smsg("sign %s", sp->sn_name); if (sp->sn_icon != NULL) { msg_puts(" icon="); msg_outtrans(sp->sn_icon); # ifdef FEAT_SIGN_ICONS if (sp->sn_image == NULL) msg_puts(_(" (NOT FOUND)")); # else msg_puts(_(" (not supported)")); # endif } if (sp->sn_text != NULL) { msg_puts(" text="); msg_outtrans(sp->sn_text); } if (sp->sn_line_hl > 0) { msg_puts(" linehl="); p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE); if (p == NULL) msg_puts("NONE"); else msg_puts((char *)p); } if (sp->sn_text_hl > 0) { msg_puts(" texthl="); p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE); if (p == NULL) msg_puts("NONE"); else msg_puts((char *)p); } } /* * Undefine a sign and free its memory. */ static void sign_undefine(sign_T *sp, sign_T *sp_prev) { vim_free(sp->sn_name); vim_free(sp->sn_icon); # ifdef FEAT_SIGN_ICONS if (sp->sn_image != NULL) { out_flush(); gui_mch_destroy_sign(sp->sn_image); } # endif vim_free(sp->sn_text); if (sp_prev == NULL) first_sign = sp->sn_next; else sp_prev->sn_next = sp->sn_next; vim_free(sp); } /* * Get highlighting attribute for sign "typenr". * If "line" is TRUE: line highl, if FALSE: text highl. */ int sign_get_attr(int typenr, int line) { sign_T *sp; for (sp = first_sign; sp != NULL; sp = sp->sn_next) if (sp->sn_typenr == typenr) { if (line) { if (sp->sn_line_hl > 0) return syn_id2attr(sp->sn_line_hl); } else { if (sp->sn_text_hl > 0) return syn_id2attr(sp->sn_text_hl); } break; } return 0; } /* * Get text mark for sign "typenr". * Returns NULL if there isn't one. */ char_u * sign_get_text(int typenr) { sign_T *sp; for (sp = first_sign; sp != NULL; sp = sp->sn_next) if (sp->sn_typenr == typenr) return sp->sn_text; return NULL; } # if defined(FEAT_SIGN_ICONS) || defined(PROTO) void * sign_get_image( int typenr) // the attribute which may have a sign { sign_T *sp; for (sp = first_sign; sp != NULL; sp = sp->sn_next) if (sp->sn_typenr == typenr) return sp->sn_image; return NULL; } # endif /* * Undefine/free all signs. */ void free_signs(void) { while (first_sign != NULL) sign_undefine(first_sign, NULL); } # if defined(FEAT_CMDL_COMPL) || defined(PROTO) static enum { EXP_SUBCMD, // expand :sign sub-commands EXP_DEFINE, // expand :sign define {name} args EXP_PLACE, // expand :sign place {id} args EXP_LIST, // expand :sign place args EXP_UNPLACE, // expand :sign unplace" EXP_SIGN_NAMES, // expand with name of placed signs EXP_SIGN_GROUPS // expand with name of placed sign groups } expand_what; /* * Return the n'th sign name (used for command line completion) */ static char_u * get_nth_sign_name(int idx) { int current_idx; sign_T *sp; // Complete with name of signs already defined current_idx = 0; for (sp = first_sign; sp != NULL; sp = sp->sn_next) if (current_idx++ == idx) return sp->sn_name; return NULL; } /* * Return the n'th sign group name (used for command line completion) */ static char_u * get_nth_sign_group_name(int idx) { int current_idx; int todo; hashitem_T *hi; signgroup_T *group; // Complete with name of sign groups already defined current_idx = 0; todo = (int)sg_table.ht_used; for (hi = sg_table.ht_array; todo > 0; ++hi) { if (!HASHITEM_EMPTY(hi)) { --todo; if (current_idx++ == idx) { group = HI2SG(hi); return group->sg_name; } } } return NULL; } /* * Function given to ExpandGeneric() to obtain the sign command * expansion. */ char_u * get_sign_name(expand_T *xp UNUSED, int idx) { switch (expand_what) { case EXP_SUBCMD: return (char_u *)cmds[idx]; case EXP_DEFINE: { char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", NULL }; return (char_u *)define_arg[idx]; } case EXP_PLACE: { char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=", "buffer=", NULL }; return (char_u *)place_arg[idx]; } case EXP_LIST: { char *list_arg[] = { "group=", "file=", "buffer=", NULL }; return (char_u *)list_arg[idx]; } case EXP_UNPLACE: { char *unplace_arg[] = { "group=", "file=", "buffer=", NULL }; return (char_u *)unplace_arg[idx]; } case EXP_SIGN_NAMES: return get_nth_sign_name(idx); case EXP_SIGN_GROUPS: return get_nth_sign_group_name(idx); default: return NULL; } } /* * Handle command line completion for :sign command. */ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) { char_u *p; char_u *end_subcmd; char_u *last; int cmd_idx; char_u *begin_subcmd_args; // Default: expand subcommands. xp->xp_context = EXPAND_SIGN; expand_what = EXP_SUBCMD; xp->xp_pattern = arg; end_subcmd = skiptowhite(arg); if (*end_subcmd == NUL) // expand subcmd name // :sign {subcmd}<CTRL-D> return; cmd_idx = sign_cmd_idx(arg, end_subcmd); // :sign {subcmd} {subcmd_args} // | // begin_subcmd_args begin_subcmd_args = skipwhite(end_subcmd); // expand last argument of subcmd // :sign define {name} {args}... // | // p // Loop until reaching last argument. p = begin_subcmd_args; do { p = skipwhite(p); last = p; p = skiptowhite(p); } while (*p != NUL); p = vim_strchr(last, '='); // :sign define {name} {args}... {last}= // | | // last p if (p == NULL) { // Expand last argument name (before equal sign). xp->xp_pattern = last; switch (cmd_idx) { case SIGNCMD_DEFINE: expand_what = EXP_DEFINE; break; case SIGNCMD_PLACE: // List placed signs if (VIM_ISDIGIT(*begin_subcmd_args)) // :sign place {id} {args}... expand_what = EXP_PLACE; else // :sign place {args}... expand_what = EXP_LIST; break; case SIGNCMD_LIST: case SIGNCMD_UNDEFINE: // :sign list <CTRL-D> // :sign undefine <CTRL-D> expand_what = EXP_SIGN_NAMES; break; case SIGNCMD_JUMP: case SIGNCMD_UNPLACE: expand_what = EXP_UNPLACE; break; default: xp->xp_context = EXPAND_NOTHING; } } else { // Expand last argument value (after equal sign). xp->xp_pattern = p + 1; switch (cmd_idx) { case SIGNCMD_DEFINE: if (STRNCMP(last, "texthl", 6) == 0 || STRNCMP(last, "linehl", 6) == 0) xp->xp_context = EXPAND_HIGHLIGHT; else if (STRNCMP(last, "icon", 4) == 0) xp->xp_context = EXPAND_FILES; else xp->xp_context = EXPAND_NOTHING; break; case SIGNCMD_PLACE: if (STRNCMP(last, "name", 4) == 0) expand_what = EXP_SIGN_NAMES; else if (STRNCMP(last, "group", 5) == 0) expand_what = EXP_SIGN_GROUPS; else if (STRNCMP(last, "file", 4) == 0) xp->xp_context = EXPAND_BUFFERS; else xp->xp_context = EXPAND_NOTHING; break; case SIGNCMD_UNPLACE: case SIGNCMD_JUMP: if (STRNCMP(last, "group", 5) == 0) expand_what = EXP_SIGN_GROUPS; else if (STRNCMP(last, "file", 4) == 0) xp->xp_context = EXPAND_BUFFERS; else xp->xp_context = EXPAND_NOTHING; break; default: xp->xp_context = EXPAND_NOTHING; } } } # endif #endif /* FEAT_SIGNS */