diff options
author | Peter Lönnebring <peter_l> | 2020-01-17 09:56:29 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2020-01-17 10:01:18 +0300 |
commit | 61072f08193c44ef345d2513a787e9614ba7c507 (patch) | |
tree | 820b3764f68e130c03b8fd4a79562d97a1be2a9d /source/blender/editors | |
parent | 4aca02064f2bd581346f1e47b9ca7f641433955c (diff) |
Fix T72964: Text editor Python syntax highlighting for numerals
Less common notation for numbers wasn't highlighted eg:
0b0, 0o0, 0x0, 1.0e0, 1.0E-1, 100_000j
Diffstat (limited to 'source/blender/editors')
-rw-r--r-- | source/blender/editors/space_text/text_format_py.c | 145 |
1 files changed, 141 insertions, 4 deletions
diff --git a/source/blender/editors/space_text/text_format_py.c b/source/blender/editors/space_text/text_format_py.c index d84beb79be6..243e9908fc8 100644 --- a/source/blender/editors/space_text/text_format_py.c +++ b/source/blender/editors/space_text/text_format_py.c @@ -170,6 +170,144 @@ static int txtfmt_py_find_bool(const char *string) return i; } +/* Numeral character matching. */ +#define TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_fn) \ + { \ + uint count = 0; \ + for (; txtfmt_py_numeral_char_is_fn(*string); string += 1) { \ + count += 1; \ + } \ + return count; \ + } \ + ((void)0) + +/* Binary. */ +static bool txtfmt_py_numeral_char_is_binary(const char c) +{ + return ELEM(c, '0', '1') || (c == '_'); +} +static uint txtfmt_py_numeral_string_count_binary(const char *string) +{ + TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_binary); +} + +/* Octal. */ +static bool txtfmt_py_numeral_char_is_octal(const char c) +{ + return (c >= '0' && c <= '7') || (c == '_'); +} +static uint txtfmt_py_numeral_string_count_octal(const char *string) +{ + TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_octal); +} + +/* Decimal. */ +static bool txtfmt_py_numeral_char_is_decimal(const char c) +{ + return (c >= '0' && c <= '9') || (c == '_'); +} +static uint txtfmt_py_numeral_string_count_decimal(const char *string) +{ + TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_decimal); +} + +/* Hexadecimal. */ +static bool txtfmt_py_numeral_char_is_hexadecimal(const char c) +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c == '_'); +} +static uint txtfmt_py_numeral_string_count_hexadecimal(const char *string) +{ + TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_hexadecimal); +} + +/* Zeros. */ +static bool txtfmt_py_numeral_char_is_zero(const char c) +{ + return (c == '0') || (c == '_'); +} +static uint txtfmt_py_numeral_string_count_zeros(const char *string) +{ + TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_zero); +} + +#undef TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL + +static int txtfmt_py_find_numeral_inner(const char *string) +{ + if (string == NULL || *string == '\0') { + return -1; + } + + const char first = *string, second = *(string + 1); + + /* Decimal dot must be followed by a digit, any decimal digit. + * Note that the there can be any number of leading zeros after + * the decimal point (leading zeros are not allowed in integers) */ + if (first == '.') { + if (text_check_digit(second)) { + return 1 + txtfmt_py_numeral_string_count_decimal(string + 1); + } + } + else if (first == '0') { + /* Numerals starting with '0x' or '0X' is followed by hexadecimal digits. */ + if (ELEM(second, 'x', 'X')) { + return 2 + txtfmt_py_numeral_string_count_hexadecimal(string + 2); + } + /* Numerals starting with '0o' or '0O' is followed by octal digits. */ + if (ELEM(second, 'o', 'O')) { + return 2 + txtfmt_py_numeral_string_count_octal(string + 2); + } + /* Numerals starting with '0b' or '0B' is followed by binary digits. */ + if (ELEM(second, 'b', 'B')) { + return 2 + txtfmt_py_numeral_string_count_binary(string + 2); + } + /* Other numerals starting with '0' can be followed by any number of '0' characters. */ + if (ELEM(second, '0', '_')) { + return 2 + txtfmt_py_numeral_string_count_zeros(string + 2); + } + } + /* Any non-zero digit is the start of a decimal number. */ + else if (first > '0' && first <= '9') { + return 1 + txtfmt_py_numeral_string_count_decimal(string + 1); + } + /* A single zero is also allowed. */ + return (first == '0') ? 1 : 0; +} + +static int txtfmt_py_literal_numeral(const char *string, char prev_fmt) +{ + if (string == NULL || *string == '\0') { + return -1; + } + + const char first = *string, second = *(string + 1); + + if (prev_fmt == FMT_TYPE_NUMERAL) { + /* Previous was a number; if immediately followed by 'e' or 'E' and a digit, + * it's a base 10 exponent (scientific notation). */ + if (ELEM(first, 'e', 'E') && (text_check_digit(second) || second == '-')) { + return 1 + txtfmt_py_find_numeral_inner(string + 1); + } + /* Previous was a number; if immediately followed by '.' it's a floating point decimal number. + * Note: keep the decimal point, it's needed to allow leading zeros. */ + if ((prev_fmt == FMT_TYPE_NUMERAL) && (first == '.')) { + return txtfmt_py_find_numeral_inner(string); + } + /* "Imaginary" part of a complex number ends with 'j' */ + if (ELEM(first, 'j', 'J') && !text_check_digit(second)) { + return 1; + } + } + else if ((prev_fmt != FMT_TYPE_DEFAULT) && + (text_check_digit(first) || (first == '.' && text_check_digit(second)))) { + /* New numeral, starting with a digit or a decimal point followed by a digit. */ + return txtfmt_py_find_numeral_inner(string); + } + /* Not a literal numeral. */ + return 0; +} + static char txtfmt_py_format_identifier(const char *str) { char fmt; @@ -289,10 +427,9 @@ static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_n else if (*str == ' ') { *fmt = FMT_TYPE_WHITESPACE; } - /* Numbers (digits not part of an identifier and periods followed by digits) */ - else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) || - (*str == '.' && text_check_digit(*(str + 1)))) { - *fmt = FMT_TYPE_NUMERAL; + /* Literal numerals, "numbers". */ + else if ((i = txtfmt_py_literal_numeral(str, prev)) > 0) { + text_format_fill(&str, &fmt, FMT_TYPE_NUMERAL, i); } /* Booleans */ else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_py_find_bool(str)) != -1) { |