diff options
Diffstat (limited to 'src/nvim/charset.c')
-rw-r--r-- | src/nvim/charset.c | 519 |
1 files changed, 146 insertions, 373 deletions
diff --git a/src/nvim/charset.c b/src/nvim/charset.c index e2d844a351..f899ebf57c 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -6,14 +6,15 @@ /// Code related to character sets. #include <assert.h> +#include <inttypes.h> #include <string.h> #include <wctype.h> -#include <inttypes.h> -#include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/cursor.h" #include "nvim/func_attr.h" +#include "nvim/garray.h" #include "nvim/indent.h" #include "nvim/main.h" #include "nvim/mark.h" @@ -21,14 +22,14 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/misc1.h" -#include "nvim/garray.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os_unix.h" +#include "nvim/path.h" +#include "nvim/plines.h" #include "nvim/state.h" #include "nvim/strings.h" -#include "nvim/path.h" -#include "nvim/cursor.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "charset.c.generated.h" @@ -40,11 +41,11 @@ static bool chartab_initialized = false; // b_chartab[] is an array with 256 bits, each bit representing one of the // characters 0-255. #define SET_CHARTAB(buf, c) \ - (buf)->b_chartab[(unsigned)(c) >> 6] |= (1ull << ((c) & 0x3f)) + (buf)->b_chartab[(unsigned)(c) >> 6] |= (1ull << ((c) & 0x3f)) #define RESET_CHARTAB(buf, c) \ - (buf)->b_chartab[(unsigned)(c) >> 6] &= ~(1ull << ((c) & 0x3f)) + (buf)->b_chartab[(unsigned)(c) >> 6] &= ~(1ull << ((c) & 0x3f)) #define GET_CHARTAB_TAB(chartab, c) \ - ((chartab)[(unsigned)(c) >> 6] & (1ull << ((c) & 0x3f))) + ((chartab)[(unsigned)(c) >> 6] & (1ull << ((c) & 0x3f))) // Table used below, see init_chartab() for an explanation static char_u g_chartab[256]; @@ -211,7 +212,7 @@ int buf_init_chartab(buf_T *buf, int global) if (i == 0) { // (re)set ID flag if (tilde) { - g_chartab[c] &= (uint8_t)~CT_ID_CHAR; + g_chartab[c] &= (uint8_t) ~CT_ID_CHAR; } else { g_chartab[c] |= CT_ID_CHAR; } @@ -223,7 +224,7 @@ int buf_init_chartab(buf_T *buf, int global) if (tilde) { g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + ((dy_flags & DY_UHEX) ? 4 : 2)); - g_chartab[c] &= (uint8_t)~CT_PRINT_CHAR; + g_chartab[c] &= (uint8_t) ~CT_PRINT_CHAR; } else { g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + 1); g_chartab[c] |= CT_PRINT_CHAR; @@ -232,7 +233,7 @@ int buf_init_chartab(buf_T *buf, int global) } else if (i == 2) { // (re)set fname flag if (tilde) { - g_chartab[c] &= (uint8_t)~CT_FNAME_CHAR; + g_chartab[c] &= (uint8_t) ~CT_FNAME_CHAR; } else { g_chartab[c] |= CT_FNAME_CHAR; } @@ -309,7 +310,7 @@ void trans_characters(char_u *buf, int bufsize) /// /// @return number of bytes needed to hold a translation of `s`, NUL byte not /// included. -size_t transstr_len(const char *const s) +size_t transstr_len(const char *const s, bool untab) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { const char *p = s; @@ -330,6 +331,9 @@ size_t transstr_len(const char *const s) } } p += l; + } else if (*p == TAB && !untab) { + len += 1; + p++; } else { const int b2c_l = byte2cells((uint8_t)(*p++)); // Illegal byte sequence may occupy up to 4 characters. @@ -345,9 +349,10 @@ size_t transstr_len(const char *const s) /// @param[out] buf Buffer to which result should be saved. /// @param[in] len Buffer length. Resulting string may not occupy more then /// len - 1 bytes (one for trailing NUL byte). +/// @param[in] untab remove tab characters /// /// @return length of the resulting string, without the NUL byte. -size_t transstr_buf(const char *const s, char *const buf, const size_t len) +size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool untab) FUNC_ATTR_NONNULL_ALL { const char *p = s; @@ -378,6 +383,8 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len) } } p += l; + } else if (*p == TAB && !untab) { + *buf_p++ = *p++; } else { const char *const tb = (const char *)transchar_byte((uint8_t)(*p++)); const size_t tb_len = strlen(tb); @@ -400,14 +407,14 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len) /// @param[in] s String to replace characters from. /// /// @return [allocated] translated string -char *transstr(const char *const s) +char *transstr(const char *const s, bool untab) FUNC_ATTR_NONNULL_RET { // Compute the length of the result, taking account of unprintable // multi-byte characters. - const size_t len = transstr_len((const char *)s) + 1; + const size_t len = transstr_len((const char *)s, untab) + 1; char *const buf = xmalloc(len); - transstr_buf(s, buf, len); + transstr_buf(s, buf, len, untab); return buf; } @@ -416,7 +423,7 @@ char *transstr(const char *const s) /// /// When "buf" is NULL, return an allocated string. /// Otherwise, put the result in buf, limited by buflen, and return buf. -char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) +char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) FUNC_ATTR_NONNULL_RET { garray_T ga; @@ -733,80 +740,6 @@ int vim_strnsize(char_u *s, int len) return size; } -/// Return the number of characters 'c' will take on the screen, taking -/// into account the size of a tab. -/// Use a define to make it fast, this is used very often!!! -/// Also see getvcol() below. -/// -/// @param p -/// @param col -/// -/// @return Number of characters. -#define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ - if (*(p) == TAB && (!(wp)->w_p_list || wp->w_p_lcs_chars.tab1)) { \ - return tabstop_padding(col, (buf)->b_p_ts, (buf)->b_p_vts_array); \ - } else { \ - return ptr2cells(p); \ - } - -int chartabsize(char_u *p, colnr_T col) -{ - RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, p, col) -} - -static int win_chartabsize(win_T *wp, char_u *p, colnr_T col) -{ - RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, p, col) -} - -/// Return the number of characters the string 's' will take on the screen, -/// taking into account the size of a tab. -/// -/// @param s -/// -/// @return Number of characters the string will take on the screen. -int linetabsize(char_u *s) -{ - return linetabsize_col(0, s); -} - -/// Like linetabsize(), but starting at column "startcol". -/// -/// @param startcol -/// @param s -/// -/// @return Number of characters the string will take on the screen. -int linetabsize_col(int startcol, char_u *s) -{ - colnr_T col = startcol; - char_u *line = s; /* pointer to start of line, for breakindent */ - - while (*s != NUL) { - col += lbr_chartabsize_adv(line, &s, col); - } - return (int)col; -} - -/// Like linetabsize(), but for a given window instead of the current one. -/// -/// @param wp -/// @param line -/// @param len -/// -/// @return Number of characters the string will take on the screen. -unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) -{ - colnr_T col = 0; - - for (char_u *s = line; - *s != NUL && (len == MAXCOL || s < line + len); - MB_PTR_ADV(s)) { - col += win_lbr_chartabsize(wp, line, s, col, NULL); - } - - return (unsigned int)col; -} - /// Check that "c" is a normal identifier character: /// Letters and characters from the 'isident' option. /// @@ -936,229 +869,6 @@ bool vim_isprintc_strict(int c) return c > 0 && (g_chartab[c] & CT_PRINT_CHAR); } -/// like chartabsize(), but also check for line breaks on the screen -/// -/// @param line -/// @param s -/// @param col -/// -/// @return The number of characters taken up on the screen. -int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) -{ - if (!curwin->w_p_lbr && (*p_sbr == NUL) && !curwin->w_p_bri) { - if (curwin->w_p_wrap) { - return win_nolbr_chartabsize(curwin, s, col, NULL); - } - RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, s, col) - } - return win_lbr_chartabsize(curwin, line == NULL ? s: line, s, col, NULL); -} - -/// Call lbr_chartabsize() and advance the pointer. -/// -/// @param line -/// @param s -/// @param col -/// -/// @return The number of characters take up on the screen. -int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col) -{ - int retval; - - retval = lbr_chartabsize(line, *s, col); - MB_PTR_ADV(*s); - return retval; -} - -/// This function is used very often, keep it fast!!!! -/// -/// If "headp" not NULL, set *headp to the size of what we for 'showbreak' -/// string at start of line. Warning: *headp is only set if it's a non-zero -/// value, init to 0 before calling. -/// -/// @param wp -/// @param line -/// @param s -/// @param col -/// @param headp -/// -/// @return The number of characters taken up on the screen. -int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *headp) -{ - colnr_T col2; - colnr_T col_adj = 0; /* col + screen size of tab */ - colnr_T colmax; - int added; - int mb_added = 0; - int numberextra; - char_u *ps; - int n; - - // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && (*p_sbr == NUL)) { - if (wp->w_p_wrap) { - return win_nolbr_chartabsize(wp, s, col, headp); - } - RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, s, col) - } - - // First get normal size, without 'linebreak' - int size = win_chartabsize(wp, s, col); - int c = *s; - if (*s == TAB) { - col_adj = size - 1; - } - - // If 'linebreak' set check at a blank before a non-blank if the line - // needs a break here - if (wp->w_p_lbr - && vim_isbreak(c) - && !vim_isbreak((int)s[1]) - && wp->w_p_wrap - && (wp->w_width_inner != 0)) { - // Count all characters from first non-blank after a blank up to next - // non-blank after a blank. - numberextra = win_col_off(wp); - col2 = col; - colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj); - - if (col >= colmax) { - colmax += col_adj; - n = colmax + win_col_off2(wp); - - if (n > 0) { - colmax += (((col - colmax) / n) + 1) * n - col_adj; - } - } - - for (;;) { - ps = s; - MB_PTR_ADV(s); - c = *s; - - if (!(c != NUL - && (vim_isbreak(c) || col2 == col || !vim_isbreak((int)(*ps))))) { - break; - } - - col2 += win_chartabsize(wp, s, col2); - - if (col2 >= colmax) { /* doesn't fit */ - size = colmax - col + col_adj; - break; - } - } - } else if ((size == 2) - && (MB_BYTE2LEN(*s) > 1) - && wp->w_p_wrap - && in_win_border(wp, col)) { - // Count the ">" in the last column. - ++size; - mb_added = 1; - } - - // May have to add something for 'breakindent' and/or 'showbreak' - // string at start of line. - // Set *headp to the size of what we add. - added = 0; - - if ((*p_sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && (col != 0)) { - colnr_T sbrlen = 0; - int numberwidth = win_col_off(wp); - - numberextra = numberwidth; - col += numberextra + mb_added; - - if (col >= (colnr_T)wp->w_width_inner) { - col -= wp->w_width_inner; - numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp)); - if (col >= numberextra && numberextra > 0) { - col %= numberextra; - } - if (*p_sbr != NUL) { - sbrlen = (colnr_T)MB_CHARLEN(p_sbr); - if (col >= sbrlen) { - col -= sbrlen; - } - } - if (col >= numberextra && numberextra > 0) { - col %= numberextra; - } else if (col > 0 && numberextra > 0) { - col += numberwidth - win_col_off2(wp); - } - - numberwidth -= win_col_off2(wp); - } - - if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) { - if (*p_sbr != NUL) { - if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { - // Calculate effective window width. - int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth; - int prev_width = col ? ((colnr_T)wp->w_width_inner - (sbrlen + col)) - : 0; - - if (width <= 0) { - width = 1; - } - added += ((size - prev_width) / width) * vim_strsize(p_sbr); - if ((size - prev_width) % width) { - // Wrapped, add another length of 'sbr'. - added += vim_strsize(p_sbr); - } - } else { - added += vim_strsize(p_sbr); - } - } - - if (wp->w_p_bri) - added += get_breakindent_win(wp, line); - - size += added; - if (col != 0) { - added = 0; - } - } - } - - if (headp != NULL) { - *headp = added + mb_added; - } - return size; -} - -/// Like win_lbr_chartabsize(), except that we know 'linebreak' is off and -/// 'wrap' is on. This means we need to check for a double-byte character that -/// doesn't fit at the end of the screen line. -/// -/// @param wp -/// @param s -/// @param col -/// @param headp -/// -/// @return The number of characters take up on the screen. -static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) -{ - int n; - - if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - return tabstop_padding(col, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array); - } - n = ptr2cells(s); - - // Add one cell for a double-width character in the last column of the - // window, displayed with a ">". - if ((n == 2) && (MB_BYTE2LEN(*s) > 1) && in_win_border(wp, col)) { - if (headp != NULL) { - *headp = 1; - } - return 3; - } - return n; -} - /// Check that virtual column "vcol" is in the rightmost column of window "wp". /// /// @param wp window @@ -1202,12 +912,11 @@ bool in_win_border(win_T *wp, colnr_T vcol) /// @param start /// @param cursor /// @param end -void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, - colnr_T *end) +void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { colnr_T vcol; char_u *ptr; // points to current char - char_u *posptr; // points to char at pos->col + char_u *posptr; // points to char at pos->col char_u *line; // start of the line int incr; int head; @@ -1237,7 +946,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, // Also use this when 'list' is set but tabs take their normal size. if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL)) && !wp->w_p_lbr - && (*p_sbr == NUL) + && *get_showbreak_value(wp) == NUL && !wp->w_p_bri ) { for (;;) { head = 0; @@ -1355,8 +1064,7 @@ colnr_T getvcol_nolist(pos_T *posp) /// @param start /// @param cursor /// @param end -void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, - colnr_T *end) +void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { colnr_T col; colnr_T coladd; @@ -1411,8 +1119,7 @@ void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, /// @param pos2 /// @param left /// @param right -void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, - colnr_T *right) +void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right) { colnr_T from1; colnr_T from2; @@ -1446,15 +1153,29 @@ void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, /// skipwhite: skip over ' ' and '\t'. /// -/// @param[in] q String to skip in. +/// @param[in] p String to skip in. /// /// @return Pointer to character after the skipped whitespace. -char_u *skipwhite(const char_u *q) +char_u *skipwhite(const char_u *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - const char_u *p = q; - while (ascii_iswhite(*p)) { + return skipwhite_len(p, STRLEN(p)); +} + +/// Like `skipwhite`, but skip up to `len` characters. +/// @see skipwhite +/// +/// @param[in] p String to skip in. +/// @param[in] len Max length to skip. +/// +/// @return Pointer to character after the skipped whitespace, or the `len`-th +/// character in the string. +char_u *skipwhite_len(const char_u *p, size_t len) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_RET +{ + for (; len > 0 && ascii_iswhite(*p); len--) { p++; } return (char_u *)p; @@ -1494,7 +1215,7 @@ char_u *skipdigits(const char_u *q) /// @param q pointer to string /// /// @return Pointer to the character after the skipped digits. -const char* skipbin(const char *q) +const char *skipbin(const char *q) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET @@ -1513,7 +1234,7 @@ const char* skipbin(const char *q) /// /// @return Pointer to the character after the skipped digits and hex /// characters. -char_u* skiphex(char_u *q) +char_u *skiphex(char_u *q) { char_u *p = q; while (ascii_isxdigit(*p)) { @@ -1528,7 +1249,7 @@ char_u* skiphex(char_u *q) /// @param q /// /// @return Pointer to the digit or (NUL after the string). -char_u* skiptodigit(char_u *q) +char_u *skiptodigit(char_u *q) { char_u *p = q; while (*p != NUL && !ascii_isdigit(*p)) { @@ -1543,7 +1264,7 @@ char_u* skiptodigit(char_u *q) /// @param q pointer to string /// /// @return Pointer to the binary character or (NUL after the string). -const char* skiptobin(const char *q) +const char *skiptobin(const char *q) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET @@ -1561,7 +1282,7 @@ const char* skiptobin(const char *q) /// @param q /// /// @return Pointer to the hex character or (NUL after the string). -char_u* skiptohex(char_u *q) +char_u *skiptohex(char_u *q) { char_u *p = q; while (*p != NUL && !ascii_isxdigit(*p)) { @@ -1590,7 +1311,7 @@ char_u *skiptowhite(const char_u *p) /// @param p /// /// @return Pointer to the next whitespace character. -char_u* skiptowhite_esc(char_u *p) { +char_u *skiptowhite_esc(char_u *p) { while (*p != ' ' && *p != '\t' && *p != NUL) { if (((*p == '\\') || (*p == Ctrl_V)) && (*(p + 1) != NUL)) { ++p; @@ -1600,6 +1321,18 @@ char_u* skiptowhite_esc(char_u *p) { return p; } +/// Skip over text until '\n' or NUL. +/// +/// @param[in] p Text to skip over. +/// +/// @return Pointer to the next '\n' or NUL character. +char_u *skip_to_newline(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_RET +{ + return (char_u *)xstrchrnul((const char *)p, NL); +} + /// Gets a number from a string and skips over it, signalling overflow. /// /// @param[out] pp A pointer to a pointer to char_u. @@ -1681,6 +1414,8 @@ bool vim_isblankline(char_u *lbuf) /// If "prep" is not NULL, returns a flag to indicate the type of the number: /// 0 decimal /// '0' octal +/// 'O' octal +/// 'o' octal /// 'B' bin /// 'b' bin /// 'X' hex @@ -1692,68 +1427,81 @@ bool vim_isblankline(char_u *lbuf) /// If "what" contains STR2NR_OCT recognize octal numbers. /// If "what" contains STR2NR_HEX recognize hex numbers. /// If "what" contains STR2NR_FORCE always assume bin/oct/hex. +/// If "what" contains STR2NR_QUOTE ignore embedded single quotes /// If maxlen > 0, check at a maximum maxlen chars. +/// If strict is true, check the number strictly. return *len = 0 if fail. /// /// @param start /// @param prep Returns guessed type of number 0 = decimal, 'x' or 'X' is -/// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using -/// STR2NR_FORCE is always zero. +/// hexadecimal, '0', 'o' or 'O' is octal, 'b' or 'B' is binary. +/// When using STR2NR_FORCE is always zero. /// @param len Returns the detected length of number. /// @param what Recognizes what number passed, @see ChStr2NrFlags. /// @param nptr Returns the signed result. /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. -void vim_str2nr(const char_u *const start, int *const prep, int *const len, - const int what, varnumber_T *const nptr, - uvarnumber_T *const unptr, const int maxlen) +/// @param strict If true, fail if the number has unexpected trailing +/// alpha-numeric chars: *len is set to 0 and nothing else is +/// returned. +void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, + varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen, + const bool strict) FUNC_ATTR_NONNULL_ARG(1) { const char *ptr = (const char *)start; #define STRING_ENDED(ptr) \ - (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) + (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal const bool negative = (ptr[0] == '-'); uvarnumber_T un = 0; + if (len != NULL) { + *len = 0; + } + if (negative) { ptr++; } if (what & STR2NR_FORCE) { - // When forcing main consideration is skipping the prefix. Octal and decimal - // numbers have no prefixes to skip. pre is not set. - switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { - case STR2NR_HEX: { - if (!STRING_ENDED(ptr + 2) - && ptr[0] == '0' - && (ptr[1] == 'x' || ptr[1] == 'X') - && ascii_isxdigit(ptr[2])) { - ptr += 2; - } - goto vim_str2nr_hex; + // When forcing main consideration is skipping the prefix. Decimal numbers + // have no prefixes to skip. pre is not set. + switch (what & ~(STR2NR_FORCE | STR2NR_QUOTE)) { + case STR2NR_HEX: + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'x' || ptr[1] == 'X') + && ascii_isxdigit(ptr[2])) { + ptr += 2; } - case STR2NR_BIN: { - if (!STRING_ENDED(ptr + 2) - && ptr[0] == '0' - && (ptr[1] == 'b' || ptr[1] == 'B') - && ascii_isbdigit(ptr[2])) { - ptr += 2; - } - goto vim_str2nr_bin; - } - case STR2NR_OCT: { - goto vim_str2nr_oct; - } - case 0: { - goto vim_str2nr_dec; + goto vim_str2nr_hex; + case STR2NR_BIN: + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'b' || ptr[1] == 'B') + && ascii_isbdigit(ptr[2])) { + ptr += 2; } - default: { - abort(); + goto vim_str2nr_bin; + // Make STR2NR_OOCT work the same as STR2NR_OCT when forcing. + case STR2NR_OCT: + case STR2NR_OOCT: + case STR2NR_OCT | STR2NR_OOCT: + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'o' || ptr[1] == 'O') + && ascii_isodigit(ptr[2])) { + ptr += 2; } + goto vim_str2nr_oct; + case 0: + goto vim_str2nr_dec; + default: + abort(); } - } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) - && !STRING_ENDED(ptr + 1) - && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { + } else if ((what & (STR2NR_HEX | STR2NR_OCT | STR2NR_OOCT | STR2NR_BIN)) + && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' + && ptr[1] != '9') { pre = ptr[1]; // Detect hexadecimal: 0x or 0X followed by hex digit. if ((what & STR2NR_HEX) @@ -1771,10 +1519,18 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, ptr += 2; goto vim_str2nr_bin; } - // Detect octal number: zero followed by octal digits without '8' or '9'. + // Detect octal: 0o or 0O followed by octal digits (without '8' or '9'). + if ((what & STR2NR_OOCT) + && !STRING_ENDED(ptr + 2) + && (pre == 'O' || pre == 'o') + && ascii_isodigit(ptr[2])) { + ptr += 2; + goto vim_str2nr_oct; + } + // Detect old octal format: 0 followed by octal digits. pre = 0; if (!(what & STR2NR_OCT) - || !('0' <= ptr[1] && ptr[1] <= '7')) { + || !ascii_isodigit(ptr[1])) { goto vim_str2nr_dec; } for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { @@ -1788,11 +1544,22 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, goto vim_str2nr_dec; } - // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. + // Do the conversion manually to avoid sscanf() quirks. abort(); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ - while (!STRING_ENDED(ptr) && (cond)) { \ + const char *const after_prefix = ptr; \ + while (!STRING_ENDED(ptr)) { \ + if ((what & STR2NR_QUOTE) && ptr > after_prefix && *ptr == '\'') { \ + ptr++; \ + if (!STRING_ENDED(ptr) && (cond)) { \ + continue; \ + } \ + ptr--; \ + } \ + if (!(cond)) { \ + break; \ + } \ const uvarnumber_T digit = (uvarnumber_T)(conv); \ /* avoid ubsan error for overflow */ \ if (un < UVARNUMBER_MAX / base \ @@ -1809,7 +1576,7 @@ vim_str2nr_bin: PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); goto vim_str2nr_proceed; vim_str2nr_oct: - PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + PARSE_NUMBER(8, (ascii_isodigit(*ptr)), (*ptr - '0')); goto vim_str2nr_proceed; vim_str2nr_dec: PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); @@ -1820,6 +1587,12 @@ vim_str2nr_hex: #undef PARSE_NUMBER vim_str2nr_proceed: + // Check for an alpha-numeric character immediately following, that is + // most likely a typo. + if (strict && ptr - (const char *)start != maxlen && ASCII_ISALNUM(*ptr)) { + return; + } + if (prep != NULL) { *prep = pre; } |