aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/charset.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/charset.c')
-rw-r--r--src/nvim/charset.c1088
1 files changed, 506 insertions, 582 deletions
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 5ae4416052..c220c4e347 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -1,3 +1,6 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
/// @file charset.c
///
/// Code related to character sets.
@@ -15,17 +18,19 @@
#include "nvim/func_attr.h"
#include "nvim/indent.h"
#include "nvim/main.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/misc1.h"
-#include "nvim/misc2.h"
#include "nvim/garray.h"
#include "nvim/move.h"
+#include "nvim/option.h"
#include "nvim/os_unix.h"
+#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/path.h"
-
+#include "nvim/cursor.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "charset.c.generated.h"
@@ -40,23 +45,30 @@ static bool chartab_initialized = false;
(buf)->b_chartab[(unsigned)(c) >> 6] |= (1ull << ((c) & 0x3f))
#define RESET_CHARTAB(buf, c) \
(buf)->b_chartab[(unsigned)(c) >> 6] &= ~(1ull << ((c) & 0x3f))
+#define GET_CHARTAB_TAB(chartab, c) \
+ ((chartab)[(unsigned)(c) >> 6] & (1ull << ((c) & 0x3f)))
#define GET_CHARTAB(buf, c) \
- ((buf)->b_chartab[(unsigned)(c) >> 6] & (1ull << ((c) & 0x3f)))
+ GET_CHARTAB_TAB((buf)->b_chartab, c)
+
+// Table used below, see init_chartab() for an explanation
+static char_u g_chartab[256];
+
+// Flags for g_chartab[].
+#define CT_CELL_MASK 0x07 ///< mask: nr of display cells (1, 2 or 4)
+#define CT_PRINT_CHAR 0x10 ///< flag: set for printable chars
+#define CT_ID_CHAR 0x20 ///< flag: set for ID chars
+#define CT_FNAME_CHAR 0x40 ///< flag: set for file name chars
-/// Fill chartab[]. Also fills curbuf->b_chartab[] with flags for keyword
+/// Fill g_chartab[]. Also fills curbuf->b_chartab[] with flags for keyword
/// characters for current buffer.
///
/// Depends on the option settings 'iskeyword', 'isident', 'isfname',
/// 'isprint' and 'encoding'.
///
-/// The index in chartab[] depends on 'encoding':
-/// - For non-multi-byte index with the byte (same as the character).
-/// - For DBCS index with the first byte.
-/// - For UTF-8 index with the character (when first byte is up to 0x80 it is
-/// the same as the character, if the first byte is 0x80 and above it depends
-/// on further bytes).
+/// The index in g_chartab[] is the character when first byte is up to 0x80,
+/// if the first byte is 0x80 and above it depends on further bytes.
///
-/// The contents of chartab[]:
+/// The contents of g_chartab[]:
/// - The lower two bits, masked by CT_CELL_MASK, give the number of display
/// cells the character occupies (1 or 2). Not valid for UTF-8 above 0x80.
/// - CT_PRINT_CHAR bit is set when the character is printable (no need to
@@ -82,7 +94,6 @@ int buf_init_chartab(buf_T *buf, int global)
{
int c;
int c2;
- char_u *p;
int i;
bool tilde;
bool do_isalpha;
@@ -94,41 +105,33 @@ int buf_init_chartab(buf_T *buf, int global)
c = 0;
while (c < ' ') {
- chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2;
+ g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2;
}
while (c <= '~') {
- chartab[c++] = 1 + CT_PRINT_CHAR;
+ g_chartab[c++] = 1 + CT_PRINT_CHAR;
}
if (p_altkeymap) {
while (c < YE) {
- chartab[c++] = 1 + CT_PRINT_CHAR;
+ g_chartab[c++] = 1 + CT_PRINT_CHAR;
}
}
while (c < 256) {
- if (enc_utf8 && (c >= 0xa0)) {
+ if (c >= 0xa0) {
// UTF-8: bytes 0xa0 - 0xff are printable (latin1)
- chartab[c++] = CT_PRINT_CHAR + 1;
- } else if ((enc_dbcs == DBCS_JPNU) && (c == 0x8e)) {
- // euc-jp characters starting with 0x8e are single width
- chartab[c++] = CT_PRINT_CHAR + 1;
- } else if ((enc_dbcs != 0) && (MB_BYTE2LEN(c) == 2)) {
- // other double-byte chars can be printable AND double-width
- chartab[c++] = CT_PRINT_CHAR + 2;
+ g_chartab[c++] = CT_PRINT_CHAR + 1;
} else {
// the rest is unprintable by default
- chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2;
+ g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2;
}
}
// Assume that every multi-byte char is a filename character.
- for (c = 1; c < 256; ++c) {
- if (((enc_dbcs != 0) && (MB_BYTE2LEN(c) > 1))
- || ((enc_dbcs == DBCS_JPNU) && (c == 0x8e))
- || (enc_utf8 && (c >= 0xa0))) {
- chartab[c] |= CT_FNAME_CHAR;
+ for (c = 1; c < 256; c++) {
+ if (c >= 0xa0) {
+ g_chartab[c] |= CT_FNAME_CHAR;
}
}
}
@@ -136,15 +139,6 @@ int buf_init_chartab(buf_T *buf, int global)
// Init word char flags all to false
memset(buf->b_chartab, 0, (size_t)32);
- if (enc_dbcs != 0) {
- for (c = 0; c < 256; ++c) {
- // double-byte characters are probably word characters
- if (MB_BYTE2LEN(c) == 2) {
- SET_CHARTAB(buf, c);
- }
- }
- }
-
// In lisp mode the '-' character is included in keywords.
if (buf->b_p_lisp) {
SET_CHARTAB(buf, '-');
@@ -153,7 +147,8 @@ int buf_init_chartab(buf_T *buf, int global)
// Walk through the 'isident', 'iskeyword', 'isfname' and 'isprint'
// options Each option is a list of characters, character numbers or
// ranges, separated by commas, e.g.: "200-210,x,#-178,-"
- for (i = global ? 0 : 3; i <= 3; ++i) {
+ for (i = global ? 0 : 3; i <= 3; i++) {
+ const char_u *p;
if (i == 0) {
// first round: 'isident'
p = p_isi;
@@ -178,11 +173,9 @@ int buf_init_chartab(buf_T *buf, int global)
}
if (ascii_isdigit(*p)) {
- c = getdigits_int(&p);
- } else if (has_mbyte) {
- c = mb_ptr2char_adv(&p);
+ c = getdigits_int((char_u **)&p);
} else {
- c = *p++;
+ c = mb_ptr2char_adv(&p);
}
c2 = -1;
@@ -190,11 +183,9 @@ int buf_init_chartab(buf_T *buf, int global)
++p;
if (ascii_isdigit(*p)) {
- c2 = getdigits_int(&p);
- } else if (has_mbyte) {
- c2 = mb_ptr2char_adv(&p);
+ c2 = getdigits_int((char_u **)&p);
} else {
- c2 = *p++;
+ c2 = mb_ptr2char_adv(&p);
}
}
@@ -225,15 +216,15 @@ int buf_init_chartab(buf_T *buf, int global)
// work properly when 'encoding' is "latin1" and the locale is
// "C".
if (!do_isalpha
- || vim_islower(c)
- || vim_isupper(c)
+ || mb_islower(c)
+ || mb_isupper(c)
|| (p_altkeymap && (F_isalpha(c) || F_isdigit(c)))) {
if (i == 0) {
// (re)set ID flag
if (tilde) {
- chartab[c] &= (uint8_t)~CT_ID_CHAR;
+ g_chartab[c] &= (uint8_t)~CT_ID_CHAR;
} else {
- chartab[c] |= CT_ID_CHAR;
+ g_chartab[c] |= CT_ID_CHAR;
}
} else if (i == 1) {
// (re)set printable
@@ -241,23 +232,22 @@ int buf_init_chartab(buf_T *buf, int global)
// that we can detect it from the first byte.
if (((c < ' ')
|| (c > '~')
- || (p_altkeymap && (F_isalpha(c) || F_isdigit(c))))
- && !(enc_dbcs && (MB_BYTE2LEN(c) == 2))) {
+ || (p_altkeymap && (F_isalpha(c) || F_isdigit(c))))) {
if (tilde) {
- chartab[c] = (uint8_t)((chartab[c] & ~CT_CELL_MASK)
- + ((dy_flags & DY_UHEX) ? 4 : 2));
- chartab[c] &= (uint8_t)~CT_PRINT_CHAR;
+ 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;
} else {
- chartab[c] = (uint8_t)((chartab[c] & ~CT_CELL_MASK) + 1);
- chartab[c] |= CT_PRINT_CHAR;
+ g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + 1);
+ g_chartab[c] |= CT_PRINT_CHAR;
}
}
} else if (i == 2) {
// (re)set fname flag
if (tilde) {
- chartab[c] &= (uint8_t)~CT_FNAME_CHAR;
+ g_chartab[c] &= (uint8_t)~CT_FNAME_CHAR;
} else {
- chartab[c] |= CT_FNAME_CHAR;
+ g_chartab[c] |= CT_FNAME_CHAR;
}
} else { // i == 3
// (re)set keyword flag
@@ -303,7 +293,7 @@ void trans_characters(char_u *buf, int bufsize)
while (*buf != 0) {
// Assume a multi-byte character doesn't need translation.
- if (has_mbyte && ((trs_len = (*mb_ptr2len)(buf)) > 1)) {
+ if ((trs_len = (*mb_ptr2len)(buf)) > 1) {
len -= trs_len;
} else {
trs = transchar_byte(*buf);
@@ -323,73 +313,115 @@ void trans_characters(char_u *buf, int bufsize)
}
}
-/// Translate a string into allocated memory, replacing special chars with
-/// printable chars.
+/// Find length of a string capable of holding s with all specials replaced
///
-/// @param s
+/// Assumes replacing special characters with printable ones just like
+/// strtrans() does.
+///
+/// @param[in] s String to check.
///
-/// @return translated string
-char_u *transstr(char_u *s) FUNC_ATTR_NONNULL_RET
+/// @return number of bytes needed to hold a translation of `s`, NUL byte not
+/// included.
+size_t transstr_len(const char *const s)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
- char_u *res;
- char_u *p;
- int c;
- size_t l;
- char_u hexbuf[11];
-
- if (has_mbyte) {
- // Compute the length of the result, taking account of unprintable
- // multi-byte characters.
- size_t len = 0;
- p = s;
-
- while (*p != NUL) {
- if ((l = (size_t)(*mb_ptr2len)(p)) > 1) {
- c = (*mb_ptr2char)(p);
- p += l;
-
- if (vim_isprintc(c)) {
- len += l;
- } else {
- transchar_hex(hexbuf, c);
- len += STRLEN(hexbuf);
- }
- } else {
- l = (size_t)byte2cells(*p++);
+ const char *p = s;
+ size_t len = 0;
- if (l > 0) {
- len += l;
- } else {
- // illegal byte sequence
- len += 4;
+ while (*p) {
+ const size_t l = (size_t)utfc_ptr2len((const char_u *)p);
+ if (l > 1) {
+ int pcc[MAX_MCO + 1];
+ pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]);
+
+ if (vim_isprintc(pcc[0])) {
+ len += l;
+ } else {
+ for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ char hexbuf[9];
+ len += transchar_hex(hexbuf, pcc[i]);
}
}
+ p += l;
+ } else {
+ const int b2c_l = byte2cells((uint8_t)(*p++));
+ // Illegal byte sequence may occupy up to 4 characters.
+ len += (size_t)(b2c_l > 0 ? b2c_l : 4);
}
- res = xmallocz(len);
- } else {
- res = xmallocz((size_t)vim_strsize(s));
}
+ return len;
+}
- *res = NUL;
- p = s;
-
- while (*p != NUL) {
- if (has_mbyte && ((l = (size_t)(*mb_ptr2len)(p)) > 1)) {
- c = (*mb_ptr2char)(p);
+/// Replace special characters with printable ones
+///
+/// @param[in] s String to replace characters from.
+/// @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).
+///
+/// @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)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char *p = s;
+ char *buf_p = buf;
+ char *const buf_e = buf_p + len - 1;
+
+ while (*p != NUL && buf_p < buf_e) {
+ const size_t l = (size_t)utfc_ptr2len((const char_u *)p);
+ if (l > 1) {
+ if (buf_p + l > buf_e) {
+ break; // Exceeded `buf` size.
+ }
+ int pcc[MAX_MCO + 1];
+ pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]);
- if (vim_isprintc(c)) {
- // append printable multi-byte char
- STRNCAT(res, p, l);
+ if (vim_isprintc(pcc[0])) {
+ memmove(buf_p, p, l);
+ buf_p += l;
} else {
- transchar_hex(res + STRLEN(res), c);
+ for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ char hexbuf[9]; // <up to 6 bytes>NUL
+ const size_t hexlen = transchar_hex(hexbuf, pcc[i]);
+ if (buf_p + hexlen > buf_e) {
+ break;
+ }
+ memmove(buf_p, hexbuf, hexlen);
+ buf_p += hexlen;
+ }
}
p += l;
} else {
- STRCAT(res, transchar_byte(*p++));
+ const char *const tb = (const char *)transchar_byte((uint8_t)(*p++));
+ const size_t tb_len = strlen(tb);
+ if (buf_p + tb_len > buf_e) {
+ break; // Exceeded `buf` size.
+ }
+ memmove(buf_p, tb, tb_len);
+ buf_p += tb_len;
}
}
+ *buf_p = NUL;
+ assert(buf_p <= buf_e);
+ return (size_t)(buf_p - buf);
+}
- return res;
+/// Copy string and replace special characters with printable characters
+///
+/// Works like `strtrans()` does, used for that and in some other places.
+///
+/// @param[in] s String to replace characters from.
+///
+/// @return [allocated] translated string
+char *transstr(const char *const s)
+ 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;
+ char *const buf = xmalloc(len);
+ transstr_buf(s, buf, len);
+ return buf;
}
/// Convert the string "str[orglen]" to do ignore-case comparing.
@@ -433,79 +465,71 @@ char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen)
// Make each character lower case.
i = 0;
while (STR_CHAR(i) != NUL) {
- if (enc_utf8 || (has_mbyte && (MB_BYTE2LEN(STR_CHAR(i)) > 1))) {
- if (enc_utf8) {
- int c = utf_ptr2char(STR_PTR(i));
- int olen = utf_ptr2len(STR_PTR(i));
- int lc = utf_tolower(c);
-
- // Only replace the character when it is not an invalid
- // sequence (ASCII character or more than one byte) and
- // utf_tolower() doesn't return the original character.
- if (((c < 0x80) || (olen > 1)) && (c != lc)) {
- int nlen = utf_char2len(lc);
-
- // If the byte length changes need to shift the following
- // characters forward or backward.
- if (olen != nlen) {
- if (nlen > olen) {
- if (buf == NULL) {
- ga_grow(&ga, nlen - olen + 1);
- } else {
- if (len + nlen - olen >= buflen) {
- // out of memory, keep old char
- lc = c;
- nlen = olen;
- }
- }
- }
-
- if (olen != nlen) {
- if (buf == NULL) {
- STRMOVE(GA_PTR(i) + nlen, GA_PTR(i) + olen);
- ga.ga_len += nlen - olen;
- } else {
- STRMOVE(buf + i + nlen, buf + i + olen);
- len += nlen - olen;
- }
+ int c = utf_ptr2char(STR_PTR(i));
+ int olen = utf_ptr2len(STR_PTR(i));
+ int lc = mb_tolower(c);
+
+ // Only replace the character when it is not an invalid
+ // sequence (ASCII character or more than one byte) and
+ // mb_tolower() doesn't return the original character.
+ if (((c < 0x80) || (olen > 1)) && (c != lc)) {
+ int nlen = utf_char2len(lc);
+
+ // If the byte length changes need to shift the following
+ // characters forward or backward.
+ if (olen != nlen) {
+ if (nlen > olen) {
+ if (buf == NULL) {
+ ga_grow(&ga, nlen - olen + 1);
+ } else {
+ if (len + nlen - olen >= buflen) {
+ // out of memory, keep old char
+ lc = c;
+ nlen = olen;
}
}
- (void)utf_char2bytes(lc, STR_PTR(i));
}
- }
- // skip to next multi-byte char
- i += (*mb_ptr2len)(STR_PTR(i));
- } else {
- if (buf == NULL) {
- GA_CHAR(i) = (char_u)TOLOWER_LOC(GA_CHAR(i));
- } else {
- buf[i] = (char_u)TOLOWER_LOC(buf[i]);
+ if (olen != nlen) {
+ if (buf == NULL) {
+ STRMOVE(GA_PTR(i) + nlen, GA_PTR(i) + olen);
+ ga.ga_len += nlen - olen;
+ } else {
+ STRMOVE(buf + i + nlen, buf + i + olen);
+ len += nlen - olen;
+ }
+ }
}
- ++i;
+ (void)utf_char2bytes(lc, STR_PTR(i));
}
+
+ // skip to next multi-byte char
+ i += (*mb_ptr2len)(STR_PTR(i));
}
+
if (buf == NULL) {
return (char_u *)ga.ga_data;
}
return buf;
}
-// Catch 22: chartab[] can't be initialized before the options are
+// Catch 22: g_chartab[] can't be initialized before the options are
// initialized, and initializing options may cause transchar() to be called!
-// When chartab_initialized == false don't use chartab[].
+// When chartab_initialized == false don't use g_chartab[].
// Does NOT work for multi-byte characters, c must be <= 255.
// Also doesn't work for the first byte of a multi-byte, "c" must be a
// character!
-static char_u transchar_buf[7];
+static char_u transchar_buf[11];
-/// Translates a character
+/// Translate a character into a printable one, leaving printable ASCII intact
///
-/// @param c
+/// All unicode characters are considered non-printable in this function.
///
-/// @return translated character.
-char_u* transchar(int c)
+/// @param[in] c Character to translate.
+///
+/// @return translated character into a static buffer.
+char_u *transchar(int c)
{
int i = 0;
if (IS_SPECIAL(c)) {
@@ -516,38 +540,45 @@ char_u* transchar(int c)
c = K_SECOND(c);
}
- if ((!chartab_initialized && (((c >= ' ') && (c <= '~')) || F_ischar(c)))
- || ((c < 256) && vim_isprintc_strict(c))) {
+ if ((!chartab_initialized && (((c >= ' ') && (c <= '~'))
+ || (p_altkeymap && F_ischar(c))))
+ || ((c <= 0xFF) && vim_isprintc_strict(c))) {
// printable character
transchar_buf[i] = (char_u)c;
transchar_buf[i + 1] = NUL;
- } else {
+ } else if (c <= 0xFF) {
transchar_nonprint(transchar_buf + i, c);
+ } else {
+ transchar_hex((char *)transchar_buf + i, c);
}
return transchar_buf;
}
-/// Like transchar(), but called with a byte instead of a character. Checks
-/// for an illegal UTF-8 byte.
+/// Like transchar(), but called with a byte instead of a character
///
-/// @param c
+/// Checks for an illegal UTF-8 byte.
+///
+/// @param[in] c Byte to translate.
///
/// @return pointer to translated character in transchar_buf.
-char_u* transchar_byte(int c)
+char_u *transchar_byte(const int c)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (enc_utf8 && (c >= 0x80)) {
+ if (c >= 0x80) {
transchar_nonprint(transchar_buf, c);
return transchar_buf;
}
return transchar(c);
}
-/// Convert non-printable character to two or more printable characters in
-/// "buf[]". "buf" needs to be able to hold five bytes.
-/// Does NOT work for multi-byte characters, c must be <= 255.
+/// Convert non-printable characters to 2..4 printable ones
///
-/// @param buf
-/// @param c
+/// @warning Does not work for multi-byte characters, c must be <= 255.
+///
+/// @param[out] buf Buffer to store result in, must be able to hold at least
+/// 5 bytes (conversion result + NUL).
+/// @param[in] c Character to convert. NUL is assumed to be NL according to
+/// `:h NL-used-for-NUL`.
void transchar_nonprint(char_u *buf, int c)
{
if (c == NL) {
@@ -557,64 +588,63 @@ void transchar_nonprint(char_u *buf, int c)
// we use CR in place of NL in this case
c = NL;
}
+ assert(c <= 0xff);
- if (dy_flags & DY_UHEX) {
+ if (dy_flags & DY_UHEX || c > 0x7f) {
// 'display' has "uhex"
- transchar_hex(buf, c);
- } else if (c <= 0x7f) {
+ transchar_hex((char *)buf, c);
+ } else {
// 0x00 - 0x1f and 0x7f
buf[0] = '^';
// DEL displayed as ^?
buf[1] = (char_u)(c ^ 0x40);
buf[2] = NUL;
- } else if (enc_utf8 && (c >= 0x80)) {
- transchar_hex(buf, c);
- } else if ((c >= ' ' + 0x80) && (c <= '~' + 0x80)) {
- // 0xa0 - 0xfe
- buf[0] = '|';
- buf[1] = (char_u)(c - 0x80);
- buf[2] = NUL;
- } else {
- // 0x80 - 0x9f and 0xff
- buf[0] = '~';
- buf[1] = (char_u)((c - 0x80) ^ 0x40);
- buf[2] = NUL;
}
}
-/// Convert a non-printable character to hex.
+/// Convert a non-printable character to hex C string like "<FFFF>"
///
-/// @param buf
-/// @param c
-void transchar_hex(char_u *buf, int c)
+/// @param[out] buf Buffer to store result in.
+/// @param[in] c Character to convert.
+///
+/// @return Number of bytes stored in buffer, excluding trailing NUL byte.
+size_t transchar_hex(char *const buf, const int c)
+ FUNC_ATTR_NONNULL_ALL
{
- int i = 0;
+ size_t i = 0;
- buf[0] = '<';
+ buf[i++] = '<';
if (c > 255) {
- buf[++i] = (char_u)nr2hex((unsigned)c >> 12);
- buf[++i] = (char_u)nr2hex((unsigned)c >> 8);
- }
- buf[++i] = (char_u)(nr2hex((unsigned)c >> 4));
- buf[++i] = (char_u)(nr2hex((unsigned)c));
- buf[++i] = '>';
- buf[++i] = NUL;
+ if (c > 255 * 256) {
+ buf[i++] = (char)nr2hex((unsigned)c >> 20);
+ buf[i++] = (char)nr2hex((unsigned)c >> 16);
+ }
+ buf[i++] = (char)nr2hex((unsigned)c >> 12);
+ buf[i++] = (char)nr2hex((unsigned)c >> 8);
+ }
+ buf[i++] = (char)(nr2hex((unsigned)c >> 4));
+ buf[i++] = (char)(nr2hex((unsigned)c));
+ buf[i++] = '>';
+ buf[i] = NUL;
+ return i;
}
-/// Convert the lower 4 bits of byte "c" to its hex character.
+/// Convert the lower 4 bits of byte "c" to its hex character
+///
/// Lower case letters are used to avoid the confusion of <F1> being 0xf1 or
/// function key 1.
///
-/// @param c
+/// @param[in] n Number to convert.
///
/// @return the hex character.
-static unsigned nr2hex(unsigned c)
+static inline unsigned nr2hex(unsigned n)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
{
- if ((c & 0xf) <= 9) {
- return (c & 0xf) + '0';
+ if ((n & 0xf) <= 9) {
+ return (n & 0xf) + '0';
}
- return (c & 0xf) - 10 + 'a';
+ return (n & 0xf) - 10 + 'a';
}
/// Return number of display cells occupied by byte "b".
@@ -622,18 +652,18 @@ static unsigned nr2hex(unsigned c)
/// Caller must make sure 0 <= b <= 255.
/// For multi-byte mode "b" must be the first byte of a character.
/// A TAB is counted as two cells: "^I".
-/// For UTF-8 mode this will return 0 for bytes >= 0x80, because the number of
-/// cells depends on further bytes.
+/// This will return 0 for bytes >= 0x80, because the number of
+/// cells depends on further bytes in UTF-8.
///
/// @param b
///
/// @reeturn Number of display cells.
int byte2cells(int b)
{
- if (enc_utf8 && (b >= 0x80)) {
+ if (b >= 0x80) {
return 0;
}
- return chartab[b] & CT_CELL_MASK;
+ return g_chartab[b] & CT_CELL_MASK;
}
/// Return number of display cells occupied by character "c".
@@ -652,20 +682,9 @@ int char2cells(int c)
if (c >= 0x80) {
// UTF-8: above 0x80 need to check the value
- if (enc_utf8) {
- return utf_char2cells(c);
- }
-
- // DBCS: double-byte means double-width, except for euc-jp with first
- // byte 0x8e
- if ((enc_dbcs != 0) && (c >= 0x100)) {
- if ((enc_dbcs == DBCS_JPNU) && (((unsigned)c >> 8) == 0x8e)) {
- return 1;
- }
- return 2;
- }
+ return utf_char2cells(c);
}
- return chartab[c & 0xff] & CT_CELL_MASK;
+ return g_chartab[c & 0xff] & CT_CELL_MASK;
}
/// Return number of display cells occupied by character at "*p".
@@ -674,15 +693,15 @@ int char2cells(int c)
/// @param p
///
/// @return number of display cells.
-int ptr2cells(char_u *p)
+int ptr2cells(const char_u *p)
{
// For UTF-8 we need to look at more bytes if the first byte is >= 0x80.
- if (enc_utf8 && (*p >= 0x80)) {
+ if (*p >= 0x80) {
return utf_ptr2cells(p);
}
// For DBCS we can tell the cell count from the first byte.
- return chartab[*p] & CT_CELL_MASK;
+ return g_chartab[*p] & CT_CELL_MASK;
}
/// Return the number of character cells string "s" will take on the screen,
@@ -712,14 +731,10 @@ int vim_strnsize(char_u *s, int len)
assert(s != NULL);
int size = 0;
while (*s != NUL && --len >= 0) {
- if (has_mbyte) {
- int l = (*mb_ptr2len)(s);
- size += ptr2cells(s);
- s += l;
- len -= l - 1;
- } else {
- size += byte2cells(*s++);
- }
+ int l = (*mb_ptr2len)(s);
+ size += ptr2cells(s);
+ s += l;
+ len -= l - 1;
}
return size;
}
@@ -792,7 +807,7 @@ unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len)
for (char_u *s = line;
*s != NUL && (len == MAXCOL || s < line + len);
- mb_ptr_adv(s)) {
+ MB_PTR_ADV(s)) {
col += win_lbr_chartabsize(wp, line, s, col, NULL);
}
@@ -806,39 +821,44 @@ unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len)
bool vim_isIDc(int c)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return c > 0 && c < 0x100 && (chartab[c] & CT_ID_CHAR);
+ return c > 0 && c < 0x100 && (g_chartab[c] & CT_ID_CHAR);
}
/// Check that "c" is a keyword character:
-/// Letters and characters from 'iskeyword' option for current buffer.
+/// Letters and characters from 'iskeyword' option for the current buffer.
/// For multi-byte characters mb_get_class() is used (builtin rules).
///
/// @param c character to check
-bool vim_iswordc(int c)
+bool vim_iswordc(const int c)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return vim_iswordc_buf(c, curbuf);
}
+/// Check that "c" is a keyword character
+/// Letters and characters from 'iskeyword' option for given buffer.
+/// For multi-byte characters mb_get_class() is used (builtin rules).
+///
+/// @param[in] c Character to check.
+/// @param[in] chartab Buffer chartab.
+bool vim_iswordc_tab(const int c, const uint64_t *const chartab)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ return (c >= 0x100
+ ? (utf_class_tab(c, chartab) >= 2)
+ : (c > 0 && GET_CHARTAB_TAB(chartab, c) != 0));
+}
+
/// Check that "c" is a keyword character:
/// Letters and characters from 'iskeyword' option for given buffer.
/// For multi-byte characters mb_get_class() is used (builtin rules).
///
/// @param c character to check
/// @param buf buffer whose keywords to use
-bool vim_iswordc_buf(int c, buf_T *buf)
+bool vim_iswordc_buf(const int c, buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2)
{
- if (c >= 0x100) {
- if (enc_dbcs != 0) {
- return dbcs_class((unsigned)c >> 8, (unsigned)(c & 0xff)) >= 2;
- }
-
- if (enc_utf8) {
- return utf_class(c) >= 2;
- }
- }
- return c > 0 && c < 0x100 && GET_CHARTAB(buf, c) != 0;
+ return vim_iswordc_tab(c, buf->b_chartab);
}
/// Just like vim_iswordc() but uses a pointer to the (multi-byte) character.
@@ -846,13 +866,10 @@ bool vim_iswordc_buf(int c, buf_T *buf)
/// @param p pointer to the multi-byte character
///
/// @return true if "p" points to a keyword character.
-bool vim_iswordp(char_u *p)
+bool vim_iswordp(const char_u *const p)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- if (has_mbyte && (MB_BYTE2LEN(*p) > 1)) {
- return mb_get_class(p) >= 2;
- }
- return GET_CHARTAB(curbuf, *p) != 0;
+ return vim_iswordp_buf(p, curbuf);
}
/// Just like vim_iswordc_buf() but uses a pointer to the (multi-byte)
@@ -862,13 +879,15 @@ bool vim_iswordp(char_u *p)
/// @param buf buffer whose keywords to use
///
/// @return true if "p" points to a keyword character.
-bool vim_iswordp_buf(char_u *p, buf_T *buf)
+bool vim_iswordp_buf(const char_u *const p, buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- if (has_mbyte && (MB_BYTE2LEN(*p) > 1)) {
- return mb_get_class(p) >= 2;
+ int c = *p;
+
+ if (MB_BYTE2LEN(c) > 1) {
+ c = utf_ptr2char(p);
}
- return GET_CHARTAB(buf, *p) != 0;
+ return vim_iswordc_buf(c, buf);
}
/// Check that "c" is a valid file-name character.
@@ -878,7 +897,7 @@ bool vim_iswordp_buf(char_u *p, buf_T *buf)
bool vim_isfilec(int c)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return c >= 0x100 || (c > 0 && (chartab[c] & CT_FNAME_CHAR));
+ return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_FNAME_CHAR));
}
/// Check that "c" is a valid file-name character or a wildcard character
@@ -903,10 +922,10 @@ bool vim_isfilec_or_wc(int c)
bool vim_isprintc(int c)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (enc_utf8 && (c >= 0x100)) {
+ if (c >= 0x100) {
return utf_printable(c);
}
- return c >= 0x100 || (c > 0 && (chartab[c] & CT_PRINT_CHAR));
+ return c > 0 && (g_chartab[c] & CT_PRINT_CHAR);
}
/// Strict version of vim_isprintc(c), don't return true if "c" is the head
@@ -918,14 +937,10 @@ bool vim_isprintc(int c)
bool vim_isprintc_strict(int c)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if ((enc_dbcs != 0) && (c < 0x100) && (MB_BYTE2LEN(c) > 1)) {
- return false;
- }
-
- if (enc_utf8 && (c >= 0x100)) {
+ if (c >= 0x100) {
return utf_printable(c);
}
- return c >= 0x100 || (c > 0 && (chartab[c] & CT_PRINT_CHAR));
+ return c > 0 && (g_chartab[c] & CT_PRINT_CHAR);
}
/// like chartabsize(), but also check for line breaks on the screen
@@ -958,7 +973,7 @@ 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);
+ MB_PTR_ADV(*s);
return retval;
}
@@ -1005,14 +1020,14 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he
// needs a break here
if (wp->w_p_lbr
&& vim_isbreak(c)
- && !vim_isbreak(s[1])
+ && !vim_isbreak((int)s[1])
&& wp->w_p_wrap
- && (wp->w_width != 0)) {
+ && (wp->w_grid.Columns != 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 - numberextra - col_adj);
+ colmax = (colnr_T)(wp->w_grid.Columns - numberextra - col_adj);
if (col >= colmax) {
colmax += col_adj;
@@ -1025,13 +1040,11 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he
for (;;) {
ps = s;
- mb_ptr_adv(s);
+ MB_PTR_ADV(s);
c = *s;
- if (!((c != NUL)
- && (vim_isbreak(c)
- || (!vim_isbreak(c)
- && ((col2 == col) || !vim_isbreak(*ps)))))) {
+ if (!(c != NUL
+ && (vim_isbreak(c) || col2 == col || !vim_isbreak((int)(*ps))))) {
break;
}
@@ -1042,8 +1055,7 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he
break;
}
}
- } else if (has_mbyte
- && (size == 2)
+ } else if ((size == 2)
&& (MB_BYTE2LEN(*s) > 1)
&& wp->w_p_wrap
&& in_win_border(wp, col)) {
@@ -1064,9 +1076,9 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he
numberextra = numberwidth;
col += numberextra + mb_added;
- if (col >= (colnr_T)wp->w_width) {
- col -= wp->w_width;
- numberextra = wp->w_width - (numberextra - win_col_off2(wp));
+ if (col >= (colnr_T)wp->w_grid.Columns) {
+ col -= wp->w_grid.Columns;
+ numberextra = wp->w_grid.Columns - (numberextra - win_col_off2(wp));
if (col >= numberextra && numberextra > 0) {
col %= numberextra;
}
@@ -1085,16 +1097,17 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he
numberwidth -= win_col_off2(wp);
}
- if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width)) {
+ if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_grid.Columns)) {
added = 0;
if (*p_sbr != NUL) {
- if (size + sbrlen + numberwidth > (colnr_T)wp->w_width) {
+ if (size + sbrlen + numberwidth > (colnr_T)wp->w_grid.Columns) {
// Calculate effective window width.
- int width = (colnr_T)wp->w_width - sbrlen - numberwidth;
- int prev_width = col ? ((colnr_T)wp->w_width - (sbrlen + col)) : 0;
+ int width = (colnr_T)wp->w_grid.Columns - sbrlen - numberwidth;
+ int prev_width = col ? ((colnr_T)wp->w_grid.Columns - (sbrlen + col))
+ : 0;
if (width == 0) {
- width = (colnr_T)wp->w_width;
+ width = (colnr_T)wp->w_grid.Columns;
}
added += ((size - prev_width) / width) * vim_strsize(p_sbr);
if ((size - prev_width) % width) {
@@ -1163,11 +1176,11 @@ bool in_win_border(win_T *wp, colnr_T vcol)
int width1; // width of first line (after line number)
int width2; // width of further lines
- if (wp->w_width == 0) {
+ if (wp->w_grid.Columns == 0) {
// there is no border
return false;
}
- width1 = wp->w_width - win_col_off(wp);
+ width1 = wp->w_grid.Columns - win_col_off(wp);
if ((int)vcol < width1 - 1) {
return false;
@@ -1215,7 +1228,13 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor,
// continue until the NUL
posptr = NULL;
} else {
+ // Special check for an empty line, which can happen on exit, when
+ // ml_get_buf() always returns an empty string.
+ if (*ptr == NUL) {
+ pos->col = 0;
+ }
posptr = ptr + pos->col;
+ posptr -= utf_head_off(line, posptr);
}
// This function is used very often, do some speed optimizations.
@@ -1241,27 +1260,23 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor,
if (c == TAB) {
incr = ts - (vcol % ts);
} else {
- if (has_mbyte) {
- // For utf-8, if the byte is >= 0x80, need to look at
- // further bytes to find the cell width.
- if (enc_utf8 && (c >= 0x80)) {
- incr = utf_ptr2cells(ptr);
- } else {
- incr = CHARSIZE(c);
- }
-
- // If a double-cell char doesn't fit at the end of a line
- // it wraps to the next line, it's like this char is three
- // cells wide.
- if ((incr == 2)
- && wp->w_p_wrap
- && (MB_BYTE2LEN(*ptr) > 1)
- && in_win_border(wp, vcol)) {
- ++incr;
- head = 1;
- }
+ // For utf-8, if the byte is >= 0x80, need to look at
+ // further bytes to find the cell width.
+ if (c >= 0x80) {
+ incr = utf_ptr2cells(ptr);
} else {
- incr = CHARSIZE(c);
+ incr = g_chartab[c] & CT_CELL_MASK;
+ }
+
+ // If a double-cell char doesn't fit at the end of a line
+ // it wraps to the next line, it's like this char is three
+ // cells wide.
+ if ((incr == 2)
+ && wp->w_p_wrap
+ && (MB_BYTE2LEN(*ptr) > 1)
+ && in_win_border(wp, vcol)) {
+ incr++;
+ head = 1;
}
}
@@ -1271,7 +1286,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor,
}
vcol += incr;
- mb_ptr_adv(ptr);
+ MB_PTR_ADV(ptr);
}
} else {
for (;;) {
@@ -1292,7 +1307,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor,
}
vcol += incr;
- mb_ptr_adv(ptr);
+ MB_PTR_ADV(ptr);
}
}
@@ -1330,7 +1345,11 @@ colnr_T getvcol_nolist(pos_T *posp)
colnr_T vcol;
curwin->w_p_list = false;
- getvcol(curwin, posp, NULL, &vcol, NULL);
+ if (posp->coladd) {
+ getvvcol(curwin, posp, NULL, &vcol, NULL);
+ } else {
+ getvcol(curwin, posp, NULL, &vcol, NULL);
+ }
curwin->w_p_list = list_save;
return vcol;
}
@@ -1361,7 +1380,7 @@ void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor,
ptr = ml_get_buf(wp->w_buffer, pos->lnum, false);
if (pos->col < (colnr_T)STRLEN(ptr)) {
- int c = (*mb_ptr2char)(ptr + pos->col);
+ int c = utf_ptr2char(ptr + pos->col);
if ((c != TAB) && vim_isprintc(c)) {
endadd = (colnr_T)(char2cells(c) - 1);
if (coladd > endadd) {
@@ -1406,7 +1425,7 @@ void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left,
colnr_T to1;
colnr_T to2;
- if (ltp(pos1, pos2)) {
+ if (lt(*pos1, *pos2)) {
getvvcol(wp, pos1, &from1, NULL, &to1);
getvvcol(wp, pos2, &from2, NULL, &to2);
} else {
@@ -1433,32 +1452,47 @@ void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left,
/// skipwhite: skip over ' ' and '\t'.
///
-/// @param q
+/// @param[in] q String to skip in.
///
/// @return Pointer to character after the skipped whitespace.
-char_u* skipwhite(char_u *q)
+char_u *skipwhite(const char_u *q)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_RET
{
- char_u *p = q;
+ const char_u *p = q;
while (ascii_iswhite(*p)) {
- // skip to next non-white
p++;
}
- return p;
+ return (char_u *)p;
+}
+
+// getwhitecols: return the number of whitespace
+// columns (bytes) at the start of a given line
+intptr_t getwhitecols_curline(void)
+{
+ return getwhitecols(get_cursor_line_ptr());
+}
+
+intptr_t getwhitecols(const char_u *p)
+{
+ return skipwhite(p) - p;
}
-/// skip over digits
+/// Skip over digits
///
-/// @param q
+/// @param[in] q String to skip digits in.
///
/// @return Pointer to the character after the skipped digits.
-char_u* skipdigits(char_u *q)
+char_u *skipdigits(const char_u *q)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_RET
{
- char_u *p = q;
+ const char_u *p = q;
while (ascii_isdigit(*p)) {
// skip to next non-digit
p++;
}
- return p;
+ return (char_u *)p;
}
/// skip over binary digits
@@ -1543,168 +1577,17 @@ char_u* skiptohex(char_u *q)
return p;
}
-// Vim's own character class functions. These exist because many library
-// islower()/toupper() etc. do not work properly: they crash when used with
-// invalid values or can't handle latin1 when the locale is C.
-// Speed is most important here.
-#define LATIN1LOWER 'l'
-#define LATIN1UPPER 'U'
-
-static char_u latin1flags[257] =
- " "
- " UUUUUUUUUUUUUUUUUUUUUUUUUU llllllllllllllllllllllllll "
- " "
- "UUUUUUUUUUUUUUUUUUUUUUU UUUUUUUllllllllllllllllllllllll llllllll";
-static char_u latin1upper[257] =
- " !\"#$%&'()*+,-./0123456789:;<=>"
- "?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~"
- "\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
- "\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e"
- "\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae"
- "\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe"
- "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce"
- "\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde"
- "\xdf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce"
- "\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xf7\xd8\xd9\xda\xdb\xdc\xdd\xde\xff";
-static char_u latin1lower[257] =
- " !\"#$%&'()*+,-./0123456789:;<=>"
- "?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
- "\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
- "\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e"
- "\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae"
- "\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe"
- "\xbf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee"
- "\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xd7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"
- "\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee"
- "\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
-
-/// Check that the character is lower-case
-///
-/// @param c character to check
-bool vim_islower(int c)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (c <= '@') {
- return false;
- }
-
- if (c >= 0x80) {
- if (enc_utf8) {
- return utf_islower(c);
- }
-
- if (c >= 0x100) {
- if (has_mbyte) {
- return iswlower((wint_t)c);
- }
-
- // islower() can't handle these chars and may crash
- return false;
- }
-
- if (enc_latin1like) {
- return (latin1flags[c] & LATIN1LOWER) == LATIN1LOWER;
- }
- }
- return islower(c);
-}
-
-/// Check that the character is upper-case
-///
-/// @param c character to check
-bool vim_isupper(int c)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (c <= '@') {
- return false;
- }
-
- if (c >= 0x80) {
- if (enc_utf8) {
- return utf_isupper(c);
- }
-
- if (c >= 0x100) {
- if (has_mbyte) {
- return iswupper((wint_t)c);
- }
-
- // isupper() can't handle these chars and may crash
- return false;
- }
-
- if (enc_latin1like) {
- return (latin1flags[c] & LATIN1UPPER) == LATIN1UPPER;
- }
- }
- return isupper(c);
-}
-
-int vim_toupper(int c)
-{
- if (c <= '@') {
- return c;
- }
-
- if (c >= 0x80) {
- if (enc_utf8) {
- return utf_toupper(c);
- }
-
- if (c >= 0x100) {
- if (has_mbyte) {
- return (int)towupper((wint_t)c);
- }
-
- // toupper() can't handle these chars and may crash
- return c;
- }
-
- if (enc_latin1like) {
- return latin1upper[c];
- }
- }
- return TOUPPER_LOC(c);
-}
-
-int vim_tolower(int c)
-{
- if (c <= '@') {
- return c;
- }
-
- if (c >= 0x80) {
- if (enc_utf8) {
- return utf_tolower(c);
- }
-
- if (c >= 0x100) {
- if (has_mbyte) {
- return (int)towlower((wint_t)c);
- }
-
- // tolower() can't handle these chars and may crash
- return c;
- }
-
- if (enc_latin1like) {
- return latin1lower[c];
- }
- }
- return TOLOWER_LOC(c);
-}
-
-/// skiptowhite: skip over text until ' ' or '\t' or NUL.
+/// Skip over text until ' ' or '\t' or NUL
///
-/// @param p
+/// @param[in] p Text to skip over.
///
/// @return Pointer to the next whitespace or NUL character.
-char_u* skiptowhite(char_u *p)
+char_u *skiptowhite(const char_u *p)
{
while (*p != ' ' && *p != '\t' && *p != NUL) {
p++;
}
- return p;
+ return (char_u *)p;
}
/// skiptowhite_esc: Like skiptowhite(), but also skip escaped chars
@@ -1722,6 +1605,26 @@ char_u* skiptowhite_esc(char_u *p) {
return p;
}
+/// Get a number from a string and skip over it, signalling overflows
+///
+/// @param[out] pp A pointer to a pointer to char_u.
+/// It will be advanced past the read number.
+/// @param[out] nr Number read from the string.
+///
+/// @return OK on success, FAIL on error/overflow
+int getdigits_safe(char_u **pp, intmax_t *nr)
+{
+ errno = 0;
+ *nr = strtoimax((char *)(*pp), (char **)pp, 10);
+
+ if ((*nr == INTMAX_MIN || *nr == INTMAX_MAX)
+ && errno == ERANGE) {
+ return FAIL;
+ }
+
+ return OK;
+}
+
/// Get a number from a string and skip over it.
///
/// @param[out] pp A pointer to a pointer to char_u.
@@ -1730,14 +1633,16 @@ char_u* skiptowhite_esc(char_u *p) {
/// @return Number read from the string.
intmax_t getdigits(char_u **pp)
{
- intmax_t number = strtoimax((char *)*pp, (char **)pp, 10);
- assert(errno != ERANGE);
+ intmax_t number;
+ int ret = getdigits_safe(pp, &number);
+
+ (void)ret; // Avoid "unused variable" warning in Release build
+ assert(ret == OK);
+
return number;
}
-/// Get an int number from a string.
-///
-/// A getdigits wrapper restricted to int values.
+/// Get an int number from a string. Like getdigits(), but restricted to `int`.
int getdigits_int(char_u **pp)
{
intmax_t number = getdigits(pp);
@@ -1747,9 +1652,7 @@ int getdigits_int(char_u **pp)
return (int)number;
}
-/// Get a long number from a string.
-///
-/// A getdigits wrapper restricted to long values.
+/// Get a long number from a string. Like getdigits(), but restricted to `long`.
long getdigits_long(char_u **pp)
{
intmax_t number = getdigits(pp);
@@ -1787,135 +1690,156 @@ bool vim_isblankline(char_u *lbuf)
/// If maxlen > 0, check at a maximum maxlen chars.
///
/// @param start
-/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex,
-/// '0' = octal, 'b' or 'B' is bin
+/// @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.
/// @param len Returns the detected length of number.
-/// @param what Recognizes what number passed.
+/// @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, long *const nptr, unsigned long *const unptr,
- const int maxlen)
+ const int what, varnumber_T *const nptr,
+ uvarnumber_T *const unptr, const int maxlen)
+ FUNC_ATTR_NONNULL_ARG(1)
{
- const char_u *ptr = start;
+ const char *ptr = (const char *)start;
+#define STRING_ENDED(ptr) \
+ (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen))
int pre = 0; // default is decimal
- bool negative = false;
- unsigned long un = 0;
+ const bool negative = (ptr[0] == '-');
+ uvarnumber_T un = 0;
- if (ptr[0] == '-') {
- negative = true;
+ if (negative) {
ptr++;
}
- // Recognize hex, octal and bin.
- if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')
- && (maxlen == 0 || maxlen > 1)) {
- pre = ptr[1];
-
- if ((what & STR2NR_HEX)
- && ((pre == 'X') || (pre == 'x'))
- && ascii_isxdigit(ptr[2])
- && (maxlen == 0 || maxlen > 2)) {
- // hexadecimal
- ptr += 2;
- } else if ((what & STR2NR_BIN)
- && ((pre == 'B') || (pre == 'b'))
- && ascii_isbdigit(ptr[2])
- && (maxlen == 0 || maxlen > 2)) {
- // binary
- ptr += 2;
- } else {
- // decimal or octal, default is decimal
- pre = 0;
-
- if (what & STR2NR_OCT) {
- // Don't interpret "0", "08" or "0129" as octal.
- for (int n = 1; ascii_isdigit(ptr[n]); ++n) {
- if (ptr[n] > '7') {
- // can't be octal
- pre = 0;
- break;
- }
- if (ptr[n] >= '0') {
- // assume octal
- pre = '0';
- }
- if (n == maxlen) {
- break;
- }
+ 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;
}
- }
- }
-
- // Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
- int n = 1;
- if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) {
- // bin
- if (pre != 0) {
- n += 2; // skip over "0b"
- }
- while ('0' <= *ptr && *ptr <= '1') {
- un = 2 * un + (unsigned long)(*ptr - '0');
- ptr++;
- if (n++ == maxlen) {
- break;
+ 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;
}
- }
- } else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) {
- // octal
- while ('0' <= *ptr && *ptr <= '7') {
- un = 8 * un + (unsigned long)(*ptr - '0');
- ptr++;
- if (n++ == maxlen) {
- break;
+ case STR2NR_OCT: {
+ goto vim_str2nr_oct;
+ }
+ case 0: {
+ goto vim_str2nr_dec;
+ }
+ default: {
+ assert(false);
}
}
- } else if ((pre == 'X') || (pre == 'x')
- || what == STR2NR_HEX + STR2NR_FORCE) {
- // hex
- if (pre != 0) {
- n += 2; // skip over "0x"
+ } else if ((what & (STR2NR_HEX|STR2NR_OCT|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)
+ && !STRING_ENDED(ptr + 2)
+ && (pre == 'X' || pre == 'x')
+ && ascii_isxdigit(ptr[2])) {
+ ptr += 2;
+ goto vim_str2nr_hex;
}
- while (ascii_isxdigit(*ptr)) {
- un = 16 * un + (unsigned long)hex2nr(*ptr);
- ptr++;
- if (n++ == maxlen) {
- break;
- }
+ // Detect binary: 0b or 0B followed by 0 or 1.
+ if ((what & STR2NR_BIN)
+ && !STRING_ENDED(ptr + 2)
+ && (pre == 'B' || pre == 'b')
+ && ascii_isbdigit(ptr[2])) {
+ ptr += 2;
+ goto vim_str2nr_bin;
}
- } else {
- // decimal
- while (ascii_isdigit(*ptr)) {
- un = 10 * un + (unsigned long)(*ptr - '0');
- ptr++;
- if (n++ == maxlen) {
- break;
+ // Detect octal number: zero followed by octal digits without '8' or '9'.
+ pre = 0;
+ if (!(what & STR2NR_OCT)
+ || !('0' <= ptr[1] && ptr[1] <= '7')) {
+ goto vim_str2nr_dec;
+ }
+ for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) {
+ if (ptr[i] > '7') {
+ goto vim_str2nr_dec;
}
}
+ pre = '0';
+ goto vim_str2nr_oct;
+ } else {
+ goto vim_str2nr_dec;
}
+ // Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
+ assert(false); // Should’ve used goto earlier.
+#define PARSE_NUMBER(base, cond, conv) \
+ do { \
+ while (!STRING_ENDED(ptr) && (cond)) { \
+ /* avoid ubsan error for overflow */ \
+ if (un < UVARNUMBER_MAX / base) { \
+ un = base * un + (uvarnumber_T)(conv); \
+ } else { \
+ un = UVARNUMBER_MAX; \
+ } \
+ ptr++; \
+ } \
+ } while (0)
+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'));
+ goto vim_str2nr_proceed;
+vim_str2nr_dec:
+ PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0'));
+ goto vim_str2nr_proceed;
+vim_str2nr_hex:
+ PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr)));
+ goto vim_str2nr_proceed;
+#undef PARSE_NUMBER
+
+vim_str2nr_proceed:
if (prep != NULL) {
*prep = pre;
}
if (len != NULL) {
- *len = (int)(ptr - start);
+ *len = (int)(ptr - (const char *)start);
}
if (nptr != NULL) {
- if (negative) {
- // account for leading '-' for decimal numbers
- *nptr = -(long)un;
+ if (negative) { // account for leading '-' for decimal numbers
+ // avoid ubsan error for overflow
+ if (un > VARNUMBER_MAX) {
+ *nptr = VARNUMBER_MIN;
+ } else {
+ *nptr = -(varnumber_T)un;
+ }
} else {
- *nptr = (long)un;
+ if (un > VARNUMBER_MAX) {
+ un = VARNUMBER_MAX;
+ }
+ *nptr = (varnumber_T)un;
}
}
if (unptr != NULL) {
*unptr = un;
}
+#undef STRING_ENDED
}
/// Return the value of a single hex character.