diff options
-rw-r--r-- | runtime/doc/options.txt | 7 | ||||
-rw-r--r-- | runtime/doc/sign.txt | 4 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 1 | ||||
-rw-r--r-- | src/nvim/api/buffer.c | 9 | ||||
-rw-r--r-- | src/nvim/buffer.c | 19 | ||||
-rw-r--r-- | src/nvim/eval/typval.h | 11 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 66 | ||||
-rw-r--r-- | src/nvim/fileio.c | 563 | ||||
-rw-r--r-- | src/nvim/screen.c | 21 | ||||
-rw-r--r-- | src/nvim/sign_defs.h | 21 | ||||
-rw-r--r-- | src/nvim/testdir/test_signs.vim | 6 | ||||
-rw-r--r-- | src/nvim/testdir/test_writefile.vim | 18 | ||||
-rw-r--r-- | test/functional/ex_cmds/sign_spec.lua | 2 | ||||
-rw-r--r-- | test/functional/ui/bufhl_spec.lua | 11 | ||||
-rw-r--r-- | test/functional/ui/sign_spec.lua | 33 |
15 files changed, 469 insertions, 323 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ab866da320..bfdc09662e 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4256,12 +4256,11 @@ A jump table for the options with a short description can be found at |Q_op|. Print the line number in front of each line. When the 'n' option is excluded from 'cpoptions' a wrapped line will not use the column of line numbers. - The 'numberwidth' option can be used to set the room used for the line - number. + Use the 'numberwidth' option to adjust the room for the line number. When a long, wrapped line doesn't start with the first character, '-' characters are put before the number. - See |hl-LineNr| and |hl-CursorLineNr| for the highlighting used for - the number. + For highlighting see |hl-LineNr|, |hl-CursorLineNr|, and the + |:sign-define| "numhl" argument. *number_relativenumber* The 'relativenumber' option changes the displayed number to be relative to the cursor. Together with 'number' there are these diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index 977d73b7b2..7cdb460943 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -85,6 +85,10 @@ DEFINING A SIGN. *:sign-define* *E255* *E160* *E612* Highlighting group used for the whole line the sign is placed in. Most useful is defining a background color. + numhl={group} + Highlighting group used for 'number' column at the associated + line. Overrides |hl-LineNr|, |hl-CursorLineNr|. + text={text} *E239* Define the text that is displayed when there is no icon or the GUI is not being used. Only printable characters are allowed diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 2615d8a108..4fbfb0d7a0 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -136,6 +136,7 @@ Commands: |:cquit| can use [count] to set the exit code |:drop| is always available |:Man| is available by default, with many improvements such as completion + |:sign-define| accepts a `numhl` argument, to highlight the line number |:tchdir| tab-local |current-directory| Events: diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 16196ec910..1491901877 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -13,6 +13,7 @@ #include "nvim/api/private/defs.h" #include "nvim/vim.h" #include "nvim/buffer.h" +#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -933,7 +934,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, return src_id; } -/// Clears highlights from a given source group and a range of lines +/// Clears highlights and virtual text from a given source id and range of lines /// /// To clear a source group in the entire buffer, pass in 0 and -1 to /// line_start and line_end respectively. @@ -976,6 +977,10 @@ void nvim_buf_clear_highlight(Buffer buffer, /// begin after one cell to the right of the ordinary text, this will contain /// the |lcs-eol| char if set, otherwise just be a space. /// +/// The same src_id can be used for both virtual text and highlights added by +/// nvim_buf_add_highlight. Virtual text is cleared using +/// nvim_buf_clear_highlight. +/// /// @param buffer Buffer handle /// @param src_id Source group to use or 0 to use a new group, /// or -1 for a ungrouped annotation @@ -1025,7 +1030,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, } String str = chunk.items[0].data.string; - char *text = xstrdup(str.size > 0 ? str.data : ""); + char *text = transstr(str.size > 0 ? str.data : ""); // allocates int hl_id = 0; if (chunk.size == 2) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 68f6ff303b..ce6aa69239 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -5084,13 +5084,16 @@ linenr_T buf_change_sign_type( return (linenr_T)0; } -int buf_getsigntype( - buf_T *buf, - linenr_T lnum, - int type /* SIGN_ICON, SIGN_TEXT, SIGN_ANY, SIGN_LINEHL */ - ) +/// Gets a sign from a given line. +/// In case of multiple signs, returns the most recently placed one. +/// +/// @param buf Buffer in which to search +/// @param lnum Line in which to search +/// @param type Type of sign to look for +/// @return Identifier of the first matching sign, or 0 +int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type) { - signlist_T *sign; /* a sign in a b_signlist */ + signlist_T *sign; // a sign in a b_signlist for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->lnum == lnum @@ -5098,7 +5101,9 @@ int buf_getsigntype( || (type == SIGN_TEXT && sign_get_text(sign->typenr) != NULL) || (type == SIGN_LINEHL - && sign_get_attr(sign->typenr, TRUE) != 0))) { + && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) + || (type == SIGN_NUMHL + && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { return sign->typenr; } } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 664bf7332c..e99289c430 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -162,19 +162,20 @@ struct listwatch_S { }; /// Structure to hold info about a list +/// Order of members is optimized to reduce padding. struct listvar_S { listitem_T *lv_first; ///< First item, NULL if none. listitem_T *lv_last; ///< Last item, NULL if none. - int lv_refcount; ///< Reference count. - int lv_len; ///< Number of items. listwatch_T *lv_watch; ///< First watcher, NULL if none. - int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx]. listitem_T *lv_idx_item; ///< When not NULL item at index "lv_idx". - int lv_copyID; ///< ID used by deepcopy(). list_T *lv_copylist; ///< Copied list used by deepcopy(). - VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED. list_T *lv_used_next; ///< next list in used lists list. list_T *lv_used_prev; ///< Previous list in used lists list. + int lv_refcount; ///< Reference count. + int lv_len; ///< Number of items. + int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx]. + int lv_copyID; ///< ID used by deepcopy(). + VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED. }; // Static list with 10 items. Use tv_list_init_static10() to initialize. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4710ae669b..bce0c35f67 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5471,13 +5471,14 @@ void ex_helptags(exarg_T *eap) struct sign { - sign_T *sn_next; /* next sign in list */ - int sn_typenr; /* type number of sign */ - char_u *sn_name; /* name of sign */ - char_u *sn_icon; /* name of pixmap */ - char_u *sn_text; /* text used instead of pixmap */ - int sn_line_hl; /* highlight ID for line */ - int sn_text_hl; /* highlight ID for text */ + sign_T *sn_next; // next sign in list + int sn_typenr; // type number of sign + char_u *sn_name; // name of sign + char_u *sn_icon; // name of pixmap + char_u *sn_text; // text used instead of pixmap + int sn_line_hl; // highlight ID for line + int sn_text_hl; // highlight ID for text + int sn_num_hl; // highlight ID for line number }; static sign_T *first_sign = NULL; @@ -5675,6 +5676,9 @@ void ex_sign(exarg_T *eap) } else if (STRNCMP(arg, "texthl=", 7) == 0) { arg += 7; sp->sn_text_hl = syn_check_group(arg, (int)(p - arg)); + } else if (STRNCMP(arg, "numhl=", 6) == 0) { + arg += 6; + sp->sn_num_hl = syn_check_group(arg, (int)(p - arg)); } else { EMSG2(_(e_invarg2), arg); return; @@ -5901,6 +5905,16 @@ static void sign_list_defined(sign_T *sp) 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); + } + } } /* @@ -5918,25 +5932,33 @@ static void sign_undefine(sign_T *sp, sign_T *sp_prev) xfree(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) +/// Gets highlighting attribute for sign "typenr" corresponding to "type". +int sign_get_attr(int typenr, SignType type) { sign_T *sp; + int sign_hl = 0; - for (sp = first_sign; sp != NULL; sp = sp->sn_next) + 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); + switch (type) { + case SIGN_TEXT: + sign_hl = sp->sn_text_hl; + break; + case SIGN_LINEHL: + sign_hl = sp->sn_line_hl; + break; + case SIGN_NUMHL: + sign_hl = sp->sn_num_hl; + break; + default: + abort(); + } + if (sign_hl > 0) { + return syn_id2attr(sign_hl); } break; } + } return 0; } @@ -5997,7 +6019,8 @@ char_u * get_sign_name(expand_T *xp, int idx) case EXP_SUBCMD: return (char_u *)cmds[idx]; case EXP_DEFINE: { - char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", NULL }; + char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", "numhl=", + NULL }; return (char_u *)define_arg[idx]; } case EXP_PLACE: { @@ -6120,7 +6143,8 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) { case SIGNCMD_DEFINE: if (STRNCMP(last, "texthl", p - last) == 0 - || STRNCMP(last, "linehl", p - last) == 0) { + || STRNCMP(last, "linehl", p - last) == 0 + || STRNCMP(last, "numhl", p - last) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; } else if (STRNCMP(last, "icon", p - last) == 0) { xp->xp_context = EXPAND_FILES; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index a5ff13552b..d0e30ddbd3 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2267,15 +2267,16 @@ buf_write ( char_u smallbuf[SMBUFSIZE]; char_u *backup_ext; int bufsize; - long perm; /* file permissions */ + long perm; // file permissions int retval = OK; - int newfile = FALSE; /* TRUE if file doesn't exist yet */ + int newfile = false; // TRUE if file doesn't exist yet int msg_save = msg_scroll; - int overwriting; /* TRUE if writing over original */ - int no_eol = FALSE; /* no end-of-line written */ - int device = FALSE; /* writing to a device */ + int overwriting; // TRUE if writing over original + int no_eol = false; // no end-of-line written + int device = false; // writing to a device int prev_got_int = got_int; - bool file_readonly = false; /* overwritten file is read-only */ + int checking_conversion; + bool file_readonly = false; // overwritten file is read-only static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')"; #if defined(UNIX) @@ -3156,298 +3157,328 @@ nobackup: notconverted = TRUE; } - /* - * Open the file "wfname" for writing. - * We may try to open the file twice: If we can't write to the - * file and forceit is TRUE we delete the existing file and try to create - * a new one. If this still fails we may have lost the original file! - * (this may happen when the user reached his quotum for number of files). - * Appending will fail if the file does not exist and forceit is FALSE. - */ - while ((fd = os_open((char *)wfname, O_WRONLY | (append - ? (forceit ? ( - O_APPEND | - O_CREAT) : - O_APPEND) - : (O_CREAT | - O_TRUNC)) - , perm < 0 ? 0666 : (perm & 0777))) < 0) { - /* - * A forced write will try to create a new file if the old one is - * still readonly. This may also happen when the directory is - * read-only. In that case the os_remove() will fail. - */ - if (errmsg == NULL) { + // If conversion is taking place, we may first pretend to write and check + // for conversion errors. Then loop again to write for real. + // When not doing conversion this writes for real right away. + for (checking_conversion = true; ; checking_conversion = false) { + // There is no need to check conversion when: + // - there is no conversion + // - we make a backup file, that can be restored in case of conversion + // failure. + if (!converted || dobackup) { + checking_conversion = false; + } + + if (checking_conversion) { + // Make sure we don't write anything. + fd = -1; + write_info.bw_fd = fd; + } else { + // Open the file "wfname" for writing. + // We may try to open the file twice: If we can't write to the file + // and forceit is TRUE we delete the existing file and try to + // create a new one. If this still fails we may have lost the + // original file! (this may happen when the user reached his + // quotum for number of files). + // Appending will fail if the file does not exist and forceit is + // FALSE. + while ((fd = os_open((char *)wfname, + O_WRONLY | + (append ? + (forceit ? (O_APPEND | O_CREAT) : O_APPEND) + : (O_CREAT | O_TRUNC)) + , perm < 0 ? 0666 : (perm & 0777))) < 0) { + // A forced write will try to create a new file if the old one + // is still readonly. This may also happen when the directory + // is read-only. In that case the mch_remove() will fail. + if (errmsg == NULL) { #ifdef UNIX - FileInfo file_info; + FileInfo file_info; - // Don't delete the file when it's a hard or symbolic link. - if ((!newfile && os_fileinfo_hardlinks(&file_info_old) > 1) - || (os_fileinfo_link((char *)fname, &file_info) - && !os_fileinfo_id_equal(&file_info, &file_info_old))) { - SET_ERRMSG(_("E166: Can't open linked file for writing")); - } else { + // Don't delete the file when it's a hard or symbolic link. + if ((!newfile && os_fileinfo_hardlinks(&file_info_old) > 1) + || (os_fileinfo_link((char *)fname, &file_info) + && !os_fileinfo_id_equal(&file_info, &file_info_old))) { + SET_ERRMSG(_("E166: Can't open linked file for writing")); + } else { #endif - SET_ERRMSG_ARG(_("E212: Can't open file for writing: %s"), fd); - if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL - && perm >= 0) { + SET_ERRMSG_ARG(_("E212: Can't open file for writing: %s"), fd); + if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL + && perm >= 0) { #ifdef UNIX - /* we write to the file, thus it should be marked - writable after all */ - if (!(perm & 0200)) - made_writable = TRUE; - perm |= 0200; - if (file_info_old.stat.st_uid != getuid() - || file_info_old.stat.st_gid != getgid()) { - perm &= 0777; - } + // we write to the file, thus it should be marked + // writable after all + if (!(perm & 0200)) { + made_writable = true; + } + perm |= 0200; + if (file_info_old.stat.st_uid != getuid() + || file_info_old.stat.st_gid != getgid()) { + perm &= 0777; + } #endif - if (!append) /* don't remove when appending */ - os_remove((char *)wfname); - continue; - } + if (!append) { // don't remove when appending + os_remove((char *)wfname); + } + continue; + } #ifdef UNIX - } + } #endif - } + } restore_backup: - { - /* - * If we failed to open the file, we don't need a backup. Throw it - * away. If we moved or removed the original file try to put the - * backup in its place. - */ - if (backup != NULL && wfname == fname) { - if (backup_copy) { - /* - * There is a small chance that we removed the original, - * try to move the copy in its place. - * This may not work if the vim_rename() fails. - * In that case we leave the copy around. - */ - // If file does not exist, put the copy in its place - if (!os_path_exists(fname)) { - vim_rename(backup, fname); + { + // If we failed to open the file, we don't need a backup. Throw it + // away. If we moved or removed the original file try to put the + // backup in its place. + if (backup != NULL && wfname == fname) { + if (backup_copy) { + // There is a small chance that we removed the original, + // try to move the copy in its place. + // This may not work if the vim_rename() fails. + // In that case we leave the copy around. + // If file does not exist, put the copy in its place + if (!os_path_exists(fname)) { + vim_rename(backup, fname); + } + // if original file does exist throw away the copy + if (os_path_exists(fname)) { + os_remove((char *)backup); + } + } else { + // try to put the original file back + vim_rename(backup, fname); + } } - // if original file does exist throw away the copy - if (os_path_exists(fname)) { - os_remove((char *)backup); + + // if original file no longer exists give an extra warning + if (!newfile && !os_path_exists(fname)) { + end = 0; } - } else { - /* try to put the original file back */ - vim_rename(backup, fname); } - } - // if original file no longer exists give an extra warning - if (!newfile && !os_path_exists(fname)) { - end = 0; + if (wfname != fname) { + xfree(wfname); + } + goto fail; } + write_info.bw_fd = fd; } + SET_ERRMSG(NULL); - if (wfname != fname) - xfree(wfname); - goto fail; - } - SET_ERRMSG(NULL); + write_info.bw_buf = buffer; + nchars = 0; - - write_info.bw_fd = fd; - write_info.bw_buf = buffer; - nchars = 0; - - /* use "++bin", "++nobin" or 'binary' */ - if (eap != NULL && eap->force_bin != 0) - write_bin = (eap->force_bin == FORCE_BIN); - else - write_bin = buf->b_p_bin; - - /* - * Skip the BOM when appending and the file already existed, the BOM - * only makes sense at the start of the file. - */ - if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) { - write_info.bw_len = make_bom(buffer, fenc); - if (write_info.bw_len > 0) { - /* don't convert */ - write_info.bw_flags = FIO_NOCONVERT | wb_flags; - if (buf_write_bytes(&write_info) == FAIL) - end = 0; - else - nchars += write_info.bw_len; + // use "++bin", "++nobin" or 'binary' + if (eap != NULL && eap->force_bin != 0) { + write_bin = (eap->force_bin == FORCE_BIN); + } else { + write_bin = buf->b_p_bin; + } + + // Skip the BOM when appending and the file already existed, the BOM + // only makes sense at the start of the file. + if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) { + write_info.bw_len = make_bom(buffer, fenc); + if (write_info.bw_len > 0) { + // don't convert + write_info.bw_flags = FIO_NOCONVERT | wb_flags; + if (buf_write_bytes(&write_info) == FAIL) { + end = 0; + } else { + nchars += write_info.bw_len; + } + } } - } - write_info.bw_start_lnum = start; + write_info.bw_start_lnum = start; - write_undo_file = (buf->b_p_udf && overwriting && !append - && !filtering && reset_changed); - if (write_undo_file) - /* Prepare for computing the hash value of the text. */ - sha256_start(&sha_ctx); + write_undo_file = (buf->b_p_udf && overwriting && !append + && !filtering && reset_changed && !checking_conversion); + if (write_undo_file) { + // Prepare for computing the hash value of the text. + sha256_start(&sha_ctx); + } - write_info.bw_len = bufsize; + write_info.bw_len = bufsize; #ifdef HAS_BW_FLAGS - write_info.bw_flags = wb_flags; + write_info.bw_flags = wb_flags; #endif - fileformat = get_fileformat_force(buf, eap); - s = buffer; - len = 0; - for (lnum = start; lnum <= end; ++lnum) { - /* - * The next while loop is done once for each character written. - * Keep it fast! - */ - ptr = ml_get_buf(buf, lnum, FALSE) - 1; - if (write_undo_file) - sha256_update(&sha_ctx, ptr + 1, (uint32_t)(STRLEN(ptr + 1) + 1)); - while ((c = *++ptr) != NUL) { - if (c == NL) - *s = NUL; /* replace newlines with NULs */ - else if (c == CAR && fileformat == EOL_MAC) - *s = NL; /* Mac: replace CRs with NLs */ - else - *s = c; - ++s; - if (++len != bufsize) - continue; - if (buf_write_bytes(&write_info) == FAIL) { - end = 0; /* write error: break loop */ + fileformat = get_fileformat_force(buf, eap); + s = buffer; + len = 0; + for (lnum = start; lnum <= end; lnum++) { + // The next while loop is done once for each character written. + // Keep it fast! + ptr = ml_get_buf(buf, lnum, false) - 1; + if (write_undo_file) { + sha256_update(&sha_ctx, ptr + 1, (uint32_t)(STRLEN(ptr + 1) + 1)); + } + while ((c = *++ptr) != NUL) { + if (c == NL) { + *s = NUL; // replace newlines with NULs + } else if (c == CAR && fileformat == EOL_MAC) { + *s = NL; // Mac: replace CRs with NLs + } else { + *s = c; + } + s++; + if (++len != bufsize) { + continue; + } + if (buf_write_bytes(&write_info) == FAIL) { + end = 0; // write error: break loop + break; + } + nchars += bufsize; + s = buffer; + len = 0; + write_info.bw_start_lnum = lnum; + } + // write failed or last line has no EOL: stop here + if (end == 0 + || (lnum == end + && (write_bin || !buf->b_p_fixeol) + && (lnum == buf->b_no_eol_lnum + || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) { + lnum++; // written the line, count it + no_eol = true; break; } - nchars += bufsize; - s = buffer; - len = 0; - write_info.bw_start_lnum = lnum; - } - /* write failed or last line has no EOL: stop here */ - if (end == 0 - || (lnum == end - && (write_bin || !buf->b_p_fixeol) - && (lnum == buf->b_no_eol_lnum - || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) { - ++lnum; /* written the line, count it */ - no_eol = TRUE; - break; - } - if (fileformat == EOL_UNIX) - *s++ = NL; - else { - *s++ = CAR; /* EOL_MAC or EOL_DOS: write CR */ - if (fileformat == EOL_DOS) { /* write CR-NL */ - if (++len == bufsize) { - if (buf_write_bytes(&write_info) == FAIL) { - end = 0; /* write error: break loop */ - break; + if (fileformat == EOL_UNIX) { + *s++ = NL; + } else { + *s++ = CAR; // EOL_MAC or EOL_DOS: write CR + if (fileformat == EOL_DOS) { // write CR-NL + if (++len == bufsize) { + if (buf_write_bytes(&write_info) == FAIL) { + end = 0; // write error: break loop + break; + } + nchars += bufsize; + s = buffer; + len = 0; } - nchars += bufsize; - s = buffer; - len = 0; + *s++ = NL; + } + } + if (++len == bufsize) { + if (buf_write_bytes(&write_info) == FAIL) { + end = 0; // Write error: break loop. + break; + } + nchars += bufsize; + s = buffer; + len = 0; + + os_breakcheck(); + if (got_int) { + end = 0; // Interrupted, break loop. + break; } - *s++ = NL; } } - if (++len == bufsize) { + if (len > 0 && end > 0) { + write_info.bw_len = len; if (buf_write_bytes(&write_info) == FAIL) { - end = 0; // Write error: break loop. - break; + end = 0; // write error } - nchars += bufsize; - s = buffer; - len = 0; + nchars += len; + } - os_breakcheck(); - if (got_int) { - end = 0; // Interrupted, break loop. + // Stop when writing done or an error was encountered. + if (!checking_conversion || end == 0) { break; - } } - } - if (len > 0 && end > 0) { - write_info.bw_len = len; - if (buf_write_bytes(&write_info) == FAIL) - end = 0; /* write error */ - nchars += len; - } - // On many journalling file systems there is a bug that causes both the - // original and the backup file to be lost when halting the system right - // after writing the file. That's because only the meta-data is - // journalled. Syncing the file slows down the system, but assures it has - // been written to disk and we don't lose it. - // For a device do try the fsync() but don't complain if it does not work - // (could be a pipe). - // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. - int error; - if (p_fs && (error = os_fsync(fd)) != 0 && !device) { - SET_ERRMSG_ARG(_("E667: Fsync failed: %s"), error); - end = 0; + // If no error happened until now, writing should be ok, so loop to + // really write the buffer. } + // If we started writing, finish writing. Also when an error was + // encountered. + if (!checking_conversion) { + // On many journalling file systems there is a bug that causes both the + // original and the backup file to be lost when halting the system right + // after writing the file. That's because only the meta-data is + // journalled. Syncing the file slows down the system, but assures it has + // been written to disk and we don't lose it. + // For a device do try the fsync() but don't complain if it does not work + // (could be a pipe). + // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. + int error; + if (p_fs && (error = os_fsync(fd)) != 0 && !device) { + SET_ERRMSG_ARG(_("E667: Fsync failed: %s"), error); + end = 0; + } + #ifdef HAVE_SELINUX - /* Probably need to set the security context. */ - if (!backup_copy) - mch_copy_sec(backup, wfname); + // Probably need to set the security context. + if (!backup_copy) { + mch_copy_sec(backup, wfname); + } #endif #ifdef UNIX - /* When creating a new file, set its owner/group to that of the original - * file. Get the new device and inode number. */ - if (backup != NULL && !backup_copy) { - /* don't change the owner when it's already OK, some systems remove - * permission or ACL stuff */ - FileInfo file_info; - if (!os_fileinfo((char *)wfname, &file_info) - || file_info.stat.st_uid != file_info_old.stat.st_uid - || file_info.stat.st_gid != file_info_old.stat.st_gid) { - os_fchown(fd, file_info_old.stat.st_uid, file_info_old.stat.st_gid); - if (perm >= 0) { // Set permission again, may have changed. - (void)os_setperm((const char *)wfname, perm); + // When creating a new file, set its owner/group to that of the original + // file. Get the new device and inode number. + if (backup != NULL && !backup_copy) { + // don't change the owner when it's already OK, some systems remove + // permission or ACL stuff + FileInfo file_info; + if (!os_fileinfo((char *)wfname, &file_info) + || file_info.stat.st_uid != file_info_old.stat.st_uid + || file_info.stat.st_gid != file_info_old.stat.st_gid) { + os_fchown(fd, file_info_old.stat.st_uid, file_info_old.stat.st_gid); + if (perm >= 0) { // Set permission again, may have changed. + (void)os_setperm((const char *)wfname, perm); + } } + buf_set_file_id(buf); + } else if (!buf->file_id_valid) { + // Set the file_id when creating a new file. + buf_set_file_id(buf); } - buf_set_file_id(buf); - } else if (!buf->file_id_valid) { - // Set the file_id when creating a new file. - buf_set_file_id(buf); - } #endif - if ((error = os_close(fd)) != 0) { - SET_ERRMSG_ARG(_("E512: Close failed: %s"), error); - end = 0; - } + if ((error = os_close(fd)) != 0) { + SET_ERRMSG_ARG(_("E512: Close failed: %s"), error); + end = 0; + } #ifdef UNIX - if (made_writable) - perm &= ~0200; /* reset 'w' bit for security reasons */ + if (made_writable) { + perm &= ~0200; // reset 'w' bit for security reasons + } #endif - if (perm >= 0) { // Set perm. of new file same as old file. - (void)os_setperm((const char *)wfname, perm); - } + if (perm >= 0) { // Set perm. of new file same as old file. + (void)os_setperm((const char *)wfname, perm); + } #ifdef HAVE_ACL - /* Probably need to set the ACL before changing the user (can't set the - * ACL on a file the user doesn't own). */ - if (!backup_copy) - mch_set_acl(wfname, acl); + // Probably need to set the ACL before changing the user (can't set the + // ACL on a file the user doesn't own). + if (!backup_copy) { + mch_set_acl(wfname, acl); + } #endif - if (wfname != fname) { - /* - * The file was written to a temp file, now it needs to be converted - * with 'charconvert' to (overwrite) the output file. - */ - if (end != 0) { - if (eval_charconvert(enc_utf8 ? "utf-8" : (char *) p_enc, (char *) fenc, - (char *) wfname, (char *) fname) == FAIL) { - write_info.bw_conv_error = true; - end = 0; + if (wfname != fname) { + // The file was written to a temp file, now it needs to be converted + // with 'charconvert' to (overwrite) the output file. + if (end != 0) { + if (eval_charconvert(enc_utf8 ? "utf-8" : (char *)p_enc, (char *)fenc, + (char *)wfname, (char *)fname) == FAIL) { + write_info.bw_conv_error = true; + end = 0; + } } + os_remove((char *)wfname); + xfree(wfname); } - os_remove((char *)wfname); - xfree(wfname); } if (end == 0) { + // Error encountered. if (errmsg == NULL) { if (write_info.bw_conv_error) { if (write_info.bw_conv_error_lnum == 0) { @@ -3470,46 +3501,48 @@ restore_backup: } } - /* - * If we have a backup file, try to put it in place of the new file, - * because the new file is probably corrupt. This avoids losing the - * original file when trying to make a backup when writing the file a - * second time. - * When "backup_copy" is set we need to copy the backup over the new - * file. Otherwise rename the backup file. - * If this is OK, don't give the extra warning message. - */ + // If we have a backup file, try to put it in place of the new file, + // because the new file is probably corrupt. This avoids losing the + // original file when trying to make a backup when writing the file a + // second time. + // When "backup_copy" is set we need to copy the backup over the new + // file. Otherwise rename the backup file. + // If this is OK, don't give the extra warning message. if (backup != NULL) { if (backup_copy) { - /* This may take a while, if we were interrupted let the user - * know we got the message. */ + // This may take a while, if we were interrupted let the user + // know we got the message. if (got_int) { MSG(_(e_interr)); ui_flush(); } if ((fd = os_open((char *)backup, O_RDONLY, 0)) >= 0) { if ((write_info.bw_fd = os_open((char *)fname, - O_WRONLY | O_CREAT | O_TRUNC, - perm & 0777)) >= 0) { - /* copy the file. */ + O_WRONLY | O_CREAT | O_TRUNC, + perm & 0777)) >= 0) { + // copy the file. write_info.bw_buf = smallbuf; #ifdef HAS_BW_FLAGS write_info.bw_flags = FIO_NOCONVERT; #endif while ((write_info.bw_len = read_eintr(fd, smallbuf, - SMBUFSIZE)) > 0) - if (buf_write_bytes(&write_info) == FAIL) + SMBUFSIZE)) > 0) { + if (buf_write_bytes(&write_info) == FAIL) { break; + } + } if (close(write_info.bw_fd) >= 0 - && write_info.bw_len == 0) - end = 1; /* success */ + && write_info.bw_len == 0) { + end = 1; // success + } } - close(fd); /* ignore errors for closing read file */ + close(fd); // ignore errors for closing read file } } else { - if (vim_rename(backup, fname) == 0) + if (vim_rename(backup, fname) == 0) { end = 1; + } } } goto fail; @@ -4099,6 +4132,10 @@ static int buf_write_bytes(struct bw_info *ip) # endif } + if (ip->bw_fd < 0) { + // Only checking conversion, which is OK if we get here. + return OK; + } wlen = write_eintr(ip->bw_fd, buf, len); return (wlen < len) ? FAIL : OK; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ec4b31c40d..a5b07bb49f 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2486,7 +2486,7 @@ win_line ( // If this line has a sign with line highlighting set line_attr. v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL); if (v != 0) { - line_attr = sign_get_attr((int)v, true); + line_attr = sign_get_attr((int)v, SIGN_LINEHL); } // Highlight the current line in the quickfix window. @@ -2794,7 +2794,7 @@ win_line ( p_extra = extra; p_extra[n_extra] = NUL; } - char_attr = sign_get_attr(text_sign, FALSE); + char_attr = sign_get_attr(text_sign, SIGN_TEXT); } } } @@ -2841,12 +2841,17 @@ win_line ( c_extra = ' '; n_extra = number_width(wp) + 1; char_attr = win_hl_attr(wp, HLF_N); - // When 'cursorline' is set highlight the line number of - // the current line differently. - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - if ((wp->w_p_cul || wp->w_p_rnu) - && lnum == wp->w_cursor.lnum) { + + int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL); + if (num_sign != 0) { + // :sign defined with "numhl" highlight. + char_attr = sign_get_attr(num_sign, SIGN_NUMHL); + } else if ((wp->w_p_cul || wp->w_p_rnu) + && lnum == wp->w_cursor.lnum) { + // When 'cursorline' is set highlight the line number of + // the current line differently. + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? char_attr = win_hl_attr(wp, HLF_CLN); } } diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index 3778f4287e..4443fd8a2e 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -9,17 +9,20 @@ typedef struct signlist signlist_T; struct signlist { - int id; /* unique identifier for each placed sign */ - linenr_T lnum; /* line number which has this sign */ - int typenr; /* typenr of sign */ - signlist_T *next; /* next signlist entry */ + int id; // unique identifier for each placed sign + linenr_T lnum; // line number which has this sign + int typenr; // typenr of sign + signlist_T *next; // next signlist entry }; -/* type argument for buf_getsigntype() */ -#define SIGN_ANY 0 -#define SIGN_LINEHL 1 -#define SIGN_ICON 2 -#define SIGN_TEXT 3 +// type argument for buf_getsigntype() and sign_get_attr() +typedef enum { + SIGN_ANY, + SIGN_LINEHL, + SIGN_ICON, + SIGN_TEXT, + SIGN_NUMHL, +} SignType; diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index a967435346..d3c6d05f4f 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -14,7 +14,7 @@ func Test_sign() " the icon name when listing signs. sign define Sign1 text=x try - sign define Sign2 text=xy texthl=Title linehl=Error icon=../../pixmaps/stock_vim_find_help.png + sign define Sign2 text=xy texthl=Title linehl=Error numhl=Number icon=../../pixmaps/stock_vim_find_help.png catch /E255:/ " ignore error: E255: Couldn't read in sign data! " This error can happen when running in gui. @@ -23,7 +23,7 @@ func Test_sign() " Test listing signs. let a=execute('sign list') - call assert_match("^\nsign Sign1 text=x \nsign Sign2 icon=../../pixmaps/stock_vim_find_help.png .*text=xy linehl=Error texthl=Title$", a) + call assert_match("^\nsign Sign1 text=x \nsign Sign2 icon=../../pixmaps/stock_vim_find_help.png .*text=xy linehl=Error texthl=Title numhl=Number$", a) let a=execute('sign list Sign1') call assert_equal("\nsign Sign1 text=x ", a) @@ -140,7 +140,7 @@ func Test_sign_completion() call assert_equal('"sign define jump list place undefine unplace', @:) call feedkeys(":sign define Sign \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign icon= linehl= text= texthl=', @:) + call assert_equal('"sign define Sign icon= linehl= numhl= text= texthl=', @:) call feedkeys(":sign define Sign linehl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign define Sign linehl=SpellBad SpellCap SpellLocal SpellRare', @:) diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 06f9d03554..1cd5fb306a 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -32,6 +32,24 @@ func Test_writefile_fails_gently() call assert_fails('call writefile([], [])', 'E730:') endfunc +func Test_writefile_fails_conversion() + if !has('multi_byte') || !has('iconv') + return + endif + set nobackup nowritebackup + new + let contents = ["line one", "line two"] + call writefile(contents, 'Xfile') + edit Xfile + call setline(1, ["first line", "cannot convert \u010b", "third line"]) + call assert_fails('write ++enc=cp932') + call assert_equal(contents, readfile('Xfile')) + + call delete('Xfile') + bwipe! + set backup& writebackup& +endfunc + func SetFlag(timer) let g:flag = 1 endfunc diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua index df0f5db860..9d08a66625 100644 --- a/test/functional/ex_cmds/sign_spec.lua +++ b/test/functional/ex_cmds/sign_spec.lua @@ -7,7 +7,7 @@ describe('sign', function() describe('without specifying buffer', function() it('deletes the sign from all buffers', function() -- place a sign with id 34 to first buffer - nvim('command', 'sign define Foo text=+ texthl=Delimiter linehl=Comment') + nvim('command', 'sign define Foo text=+ texthl=Delimiter linehl=Comment numhl=Number') local buf1 = nvim('eval', 'bufnr("%")') nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf1) -- create a second buffer and place the sign on it as well diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index ba3e44b677..29173ed7ee 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -413,5 +413,16 @@ describe('Buffer highlighting', function() | ]]) + set_virtual_text(0, 0, {{"x\tx\ny\ry", "Statement"}, {"aa\000bb", "Number"}}, {}) + screen:expect([[ + 1 + 2 {3:x^Ix^@y^My}{2:aa} | + ^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5| + , 5, 5, 5, 5, 5, 5,{1:-} | + x = 4 | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) end) end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 4fbb46ac34..6abeb0b2f4 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -17,6 +17,9 @@ describe('Signs', function() [3] = {background = Screen.colors.Gray90}, [4] = {bold = true, reverse = true}, [5] = {reverse = true}, + [6] = {foreground = Screen.colors.Brown}, + [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, + [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, } ) end) @@ -78,5 +81,35 @@ describe('Signs', function() | ]]) end) + + it('can combine text, linehl and numhl', function() + feed('ia<cr>b<cr>c<cr><esc>') + command('set number') + command('sign define piet text=>> texthl=Search') + command('sign define pietx linehl=ErrorMsg') + command('sign define pietxx numhl=Folded') + command('sign place 1 line=1 name=piet buffer=1') + command('sign place 2 line=2 name=pietx buffer=1') + command('sign place 3 line=3 name=pietxx buffer=1') + command('sign place 4 line=4 name=piet buffer=1') + command('sign place 5 line=4 name=pietx buffer=1') + command('sign place 6 line=4 name=pietxx buffer=1') + screen:expect([[ + {1:>>}{6: 1 }a | + {2: }{6: 2 }{8:b }| + {2: }{7: 3 }c | + {1:>>}{7: 4 }{8:^ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + {2: }{0:~ }| + | + ]]) + end) end) end) |