diff --git a/programs/include/edit/syntax_ansi.hpp b/programs/include/edit/syntax_ansi.hpp index a2f39d8..a249958 100644 --- a/programs/include/edit/syntax_ansi.hpp +++ b/programs/include/edit/syntax_ansi.hpp @@ -1,6 +1,6 @@ /* * syntax_ansi.hpp - * ANSI terminal syntax highlighting for C and Lua + * ANSI terminal syntax highlighting for C, C++ and Lua * Used by the MontaukOS CLI text editor * Copyright (c) 2026 Daniel Hammer */ @@ -16,6 +16,7 @@ enum SynLanguage : uint8_t { SYN_LANG_NONE, SYN_LANG_C, + SYN_LANG_CPP, SYN_LANG_LUA, }; @@ -31,10 +32,15 @@ enum SynToken : uint8_t { SYN_OPERATOR, }; +#define SYN_RAW_DELIM_MAX 16 + struct SynState { int in_block_comment; // 0 or 1 (bool stored as int for freestanding) SynToken long_token; int long_bracket_eqs; + bool in_raw_string; + char raw_delim[SYN_RAW_DELIM_MAX]; + int raw_delim_len; }; // ============================================================================ @@ -133,9 +139,45 @@ inline SynToken syn_classify_lua_word(const char* buf, int len) { return SYN_NORMAL; } +inline SynToken syn_classify_cpp_word(const char* buf, int len) { + // C++ keywords (on top of the C set, which is checked first) + static const char* keywords[] = { + "alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor", + "catch", "class", "compl", "concept", "consteval", "constexpr", + "constinit", "const_cast", "co_await", "co_return", "co_yield", + "decltype", "delete", "dynamic_cast", "explicit", "export", + "final", "friend", "mutable", "namespace", "new", "noexcept", + "not", "not_eq", "operator", "or", "or_eq", "override", + "private", "protected", "public", "reinterpret_cast", "requires", + "static_assert", "static_cast", "template", "this", "thread_local", + "throw", "try", "typeid", "typename", "using", "virtual", + "xor", "xor_eq", + }; + // C++ library / builtin types + static const char* types[] = { + "char8_t", "char16_t", "char32_t", "wchar_t", "nullptr_t", + "std", "string", "string_view", "vector", "array", "span", + "map", "set", "unordered_map", "unordered_set", "deque", "list", + "pair", "tuple", "optional", "variant", "function", + "unique_ptr", "shared_ptr", "weak_ptr", "initializer_list", + }; + + SynToken c = syn_classify_c_word(buf, len); + if (c != SYN_NORMAL) return c; + + for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { + if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; + } + for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) { + if (syn_streq(buf, len, types[i])) return SYN_TYPE; + } + return SYN_NORMAL; +} + inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) { switch (lang) { case SYN_LANG_C: return syn_classify_c_word(buf, len); + case SYN_LANG_CPP: return syn_classify_cpp_word(buf, len); case SYN_LANG_LUA: return syn_classify_lua_word(buf, len); default: return SYN_NORMAL; } @@ -146,6 +188,8 @@ inline SynState syn_make_state() { state.in_block_comment = 0; state.long_token = SYN_NORMAL; state.long_bracket_eqs = -1; + state.in_raw_string = false; + state.raw_delim_len = 0; return state; } @@ -185,64 +229,157 @@ inline bool syn_match_lua_long_bracket_close(const char* line, int len, int i, // Number consumption // ============================================================================ +inline void syn_scan_digits(const char* line, int len, int& i, bool sep, bool hex) { + while (i < len) { + if (hex ? syn_is_hex(line[i]) : syn_is_digit(line[i])) { i++; continue; } + // C++14 digit separator: a quote wedged between two digits + if (sep && line[i] == '\'' && i + 1 < len && + (hex ? syn_is_hex(line[i + 1]) : syn_is_digit(line[i + 1]))) { + i += 2; + continue; + } + break; + } +} + inline void syn_consume_number(const char* line, int len, int& i, - SynToken* out, int out_len, int c_style_suffixes) { + SynToken* out, int out_len, bool c_style_suffixes, + bool digit_sep = false) { int start = i; + if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) { i += 2; - while (i < len && syn_is_hex(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, true); if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { i++; - while (i < len && syn_is_hex(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, true); } if (i < len && (line[i] == 'p' || line[i] == 'P')) { int exp = i + 1; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && syn_is_digit(line[exp])) { - i = exp + 1; - while (i < len && syn_is_digit(line[i])) i++; + i = exp; + syn_scan_digits(line, len, i, digit_sep, false); } } } else { if (line[i] == '.') i++; - while (i < len && syn_is_digit(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, false); if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { i++; - while (i < len && syn_is_digit(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, false); } if (i < len && (line[i] == 'e' || line[i] == 'E')) { int exp = i + 1; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && syn_is_digit(line[exp])) { - i = exp + 1; - while (i < len && syn_is_digit(line[i])) i++; + i = exp; + syn_scan_digits(line, len, i, digit_sep, false); } } } + if (c_style_suffixes) { while (i < len && (line[i] == 'u' || line[i] == 'U' || line[i] == 'l' || line[i] == 'L' || - line[i] == 'f' || line[i] == 'F')) + line[i] == 'f' || line[i] == 'F' || + line[i] == 'z' || line[i] == 'Z')) i++; } + syn_fill_tokens(out, out_len, start, i, SYN_NUMBER); } // ============================================================================ // C highlighting // ============================================================================ +// C / C++ raw string helpers +// ============================================================================ + +inline bool syn_is_raw_string_prefix(const char* buf, int len) { + return syn_streq(buf, len, "R") || syn_streq(buf, len, "LR") || + syn_streq(buf, len, "uR") || syn_streq(buf, len, "UR") || + syn_streq(buf, len, "u8R"); +} + +inline bool syn_is_string_prefix(const char* buf, int len) { + return syn_streq(buf, len, "L") || syn_streq(buf, len, "u") || + syn_streq(buf, len, "U") || syn_streq(buf, len, "u8"); +} + +// Colors a raw-string body from `i` up to and including the closing )delim". +// If the terminator is not on this line, leaves state.in_raw_string set so the +// next line continues the literal. +inline void syn_consume_raw_body(const char* line, int len, int& i, + SynToken* out, int out_len, SynState& state) { + while (i < len) { + if (line[i] == ')') { + int j = i + 1; + int k = 0; + while (k < state.raw_delim_len && j < len && line[j] == state.raw_delim[k]) { + j++; + k++; + } + if (k == state.raw_delim_len && j < len && line[j] == '"') { + syn_fill_tokens(out, out_len, i, j + 1, SYN_STRING); + i = j + 1; + state.in_raw_string = false; + state.raw_delim_len = 0; + return; + } + } + syn_set_token(out, out_len, i, SYN_STRING); + i++; + } +} + +// Enters a raw string; `line[i]` must be the opening quote. +inline void syn_consume_raw_string(const char* line, int len, int& i, + SynToken* out, int out_len, SynState& state) { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + state.raw_delim_len = 0; + while (i < len && line[i] != '(' && state.raw_delim_len < SYN_RAW_DELIM_MAX) { + state.raw_delim[state.raw_delim_len++] = line[i]; + syn_set_token(out, out_len, i, SYN_STRING); + i++; + } + if (i < len && line[i] == '(') { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + state.in_raw_string = true; + syn_consume_raw_body(line, len, i, out, out_len, state); + return; + } + // Malformed (or an over-long delimiter): treat the rest of the line as string + syn_fill_tokens(out, out_len, i, len, SYN_STRING); + i = len; +} + +// ============================================================================ +// C / C++ highlighting +// ============================================================================ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len, - SynState& state) { + SynState& state, SynLanguage lang = SYN_LANG_C) { + const bool cpp = (lang == SYN_LANG_CPP); int i = 0; + while (i < len) { + // ---- Raw string continuation ---- + if (state.in_raw_string) { + syn_consume_raw_body(line, len, i, out, out_len, state); + continue; + } + + // ---- Block comment continuation ---- if (state.in_block_comment) { while (i < len) { if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT); i += 2; - state.in_block_comment = 0; + state.in_block_comment = false; break; } syn_set_token(out, out_len, i, SYN_COMMENT); @@ -250,40 +387,51 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o } continue; } + char c = line[i]; + + // ---- Line comment ---- if (c == '/' && i + 1 < len && line[i + 1] == '/') { syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); break; } + + // ---- Block comment start ---- if (c == '/' && i + 1 < len && line[i + 1] == '*') { - state.in_block_comment = 1; + state.in_block_comment = true; syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT); i += 2; continue; } + + // ---- Preprocessor directive ---- if (c == '#') { - int is_pp = 1; + // Check that only whitespace precedes the # + bool is_pp = true; for (int j = 0; j < i; j++) { - if (line[j] != ' ' && line[j] != '\t') { is_pp = 0; break; } + if (line[j] != ' ' && line[j] != '\t') { is_pp = false; break; } } if (is_pp) { while (i < len) { + // Handle line-comment inside preprocessor if (i + 1 < len && line[i] == '/' && line[i + 1] == '/') { syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); break; } + // Handle block comment start inside preprocessor if (i + 1 < len && line[i] == '/' && line[i + 1] == '*') { - state.in_block_comment = 1; + state.in_block_comment = true; syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT); i += 2; + // Continue consuming as comment while (i < len) { if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT); i += 2; - state.in_block_comment = 0; + state.in_block_comment = false; break; } syn_set_token(out, out_len, i, SYN_COMMENT); @@ -297,6 +445,8 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o continue; } } + + // ---- String literal ---- if (c == '"') { syn_set_token(out, out_len, i, SYN_STRING); i++; @@ -317,6 +467,8 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o } continue; } + + // ---- Character literal ---- if (c == '\'') { syn_set_token(out, out_len, i, SYN_CHAR); i++; @@ -337,17 +489,40 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o } continue; } + + // ---- Numbers ---- if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) { - syn_consume_number(line, len, i, out, out_len, 1); + syn_consume_number(line, len, i, out, out_len, true, cpp); continue; } + + // ---- Identifiers / keywords / types / literal prefixes ---- if (syn_is_alpha(c)) { int start = i; while (i < len && syn_is_alnum(line[i])) i++; - SynToken tok = syn_classify_word(SYN_LANG_C, line + start, i - start); + if (cpp && i < len && line[i] == '"' && + syn_is_raw_string_prefix(line + start, i - start)) { + syn_fill_tokens(out, out_len, start, i, SYN_STRING); + syn_consume_raw_string(line, len, i, out, out_len, state); + continue; + } + if (cpp && i < len && line[i] == '"' && + syn_is_string_prefix(line + start, i - start)) { + // The quote itself is handled on the next iteration + syn_fill_tokens(out, out_len, start, i, SYN_STRING); + continue; + } + if (cpp && i < len && line[i] == '\'' && + syn_is_string_prefix(line + start, i - start)) { + syn_fill_tokens(out, out_len, start, i, SYN_CHAR); + continue; + } + SynToken tok = syn_classify_word(lang, line + start, i - start); syn_fill_tokens(out, out_len, start, i, tok); continue; } + + // ---- Operators ---- if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '=' || c == '!' || c == '<' || c == '>' || c == '&' || c == '|' || c == '^' || c == '~' || c == '?' || c == ':') { @@ -355,13 +530,13 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o i++; continue; } + + // ---- Everything else (whitespace, braces, parens, etc.) ---- syn_set_token(out, out_len, i, SYN_NORMAL); i++; } } -// ============================================================================ -// Lua highlighting // ============================================================================ inline void syn_highlight_line_lua(const char* line, int len, SynToken* out, int out_len, @@ -458,7 +633,8 @@ inline void syn_highlight_line(const char* line, int len, SynToken* out, int out if (!line || len <= 0) return; switch (lang) { case SYN_LANG_C: - syn_highlight_line_c(line, len, out, out_len, state); + case SYN_LANG_CPP: + syn_highlight_line_c(line, len, out, out_len, state, lang); break; case SYN_LANG_LUA: syn_highlight_line_lua(line, len, out, out_len, state); @@ -489,6 +665,13 @@ inline bool syn_path_ends_with(const char* path, const char* suffix) { inline SynLanguage syn_detect_language(const char* filepath) { if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE; + if (syn_path_ends_with(filepath, ".cpp") || syn_path_ends_with(filepath, ".cc") || + syn_path_ends_with(filepath, ".cxx") || syn_path_ends_with(filepath, ".c++") || + syn_path_ends_with(filepath, ".hpp") || syn_path_ends_with(filepath, ".hh") || + syn_path_ends_with(filepath, ".hxx") || syn_path_ends_with(filepath, ".h++") || + syn_path_ends_with(filepath, ".ipp") || syn_path_ends_with(filepath, ".tpp") || + syn_path_ends_with(filepath, ".inl")) + return SYN_LANG_CPP; if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h")) return SYN_LANG_C; if (syn_path_ends_with(filepath, ".lua")) diff --git a/programs/src/texteditor/main.cpp b/programs/src/texteditor/main.cpp index 3774bd3..7130106 100644 --- a/programs/src/texteditor/main.cpp +++ b/programs/src/texteditor/main.cpp @@ -2,7 +2,7 @@ * main.cpp * MontaukOS Text Editor - standalone Window Server app * Single-buffer text editor with line numbers, cursor, scrolling, file I/O, - * syntax highlighting for C and Lua files + * syntax highlighting for C, C++ and Lua files * Copyright (c) 2026 Daniel Hammer */ diff --git a/programs/src/texteditor/syntax_highlight.hpp b/programs/src/texteditor/syntax_highlight.hpp index 702808e..38e1586 100644 --- a/programs/src/texteditor/syntax_highlight.hpp +++ b/programs/src/texteditor/syntax_highlight.hpp @@ -1,7 +1,7 @@ /* * syntax_highlight.hpp - * C and Lua syntax highlighting for the MontaukOS text editor - * Activated for .c, .h, and .lua files + * C, C++ and Lua syntax highlighting for the MontaukOS text editor + * Activated for .c/.h, C++ (.cpp/.hpp/...), and .lua files * Copyright (c) 2026 Daniel Hammer */ @@ -14,6 +14,7 @@ using namespace gui; enum SynLanguage : uint8_t { SYN_LANG_NONE, SYN_LANG_C, + SYN_LANG_CPP, SYN_LANG_LUA, }; @@ -33,10 +34,15 @@ enum SynToken : uint8_t { SYN_OPERATOR, }; +#define SYN_RAW_DELIM_MAX 16 + struct SynState { bool in_block_comment; SynToken long_token; int long_bracket_eqs; + bool in_raw_string; + char raw_delim[SYN_RAW_DELIM_MAX]; + int raw_delim_len; }; // ============================================================================ @@ -153,9 +159,45 @@ inline SynToken syn_classify_lua_word(const char* buf, int len) { return SYN_NORMAL; } +inline SynToken syn_classify_cpp_word(const char* buf, int len) { + // C++ keywords (on top of the C set, which is checked first) + static const char* keywords[] = { + "alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor", + "catch", "class", "compl", "concept", "consteval", "constexpr", + "constinit", "const_cast", "co_await", "co_return", "co_yield", + "decltype", "delete", "dynamic_cast", "explicit", "export", + "final", "friend", "mutable", "namespace", "new", "noexcept", + "not", "not_eq", "operator", "or", "or_eq", "override", + "private", "protected", "public", "reinterpret_cast", "requires", + "static_assert", "static_cast", "template", "this", "thread_local", + "throw", "try", "typeid", "typename", "using", "virtual", + "xor", "xor_eq", + }; + // C++ library / builtin types + static const char* types[] = { + "char8_t", "char16_t", "char32_t", "wchar_t", "nullptr_t", + "std", "string", "string_view", "vector", "array", "span", + "map", "set", "unordered_map", "unordered_set", "deque", "list", + "pair", "tuple", "optional", "variant", "function", + "unique_ptr", "shared_ptr", "weak_ptr", "initializer_list", + }; + + SynToken c = syn_classify_c_word(buf, len); + if (c != SYN_NORMAL) return c; + + for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { + if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; + } + for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) { + if (syn_streq(buf, len, types[i])) return SYN_TYPE; + } + return SYN_NORMAL; +} + inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) { switch (lang) { case SYN_LANG_C: return syn_classify_c_word(buf, len); + case SYN_LANG_CPP: return syn_classify_cpp_word(buf, len); case SYN_LANG_LUA: return syn_classify_lua_word(buf, len); default: return SYN_NORMAL; } @@ -166,6 +208,8 @@ inline SynState syn_make_state() { state.in_block_comment = false; state.long_token = SYN_NORMAL; state.long_bracket_eqs = -1; + state.in_raw_string = false; + state.raw_delim_len = 0; return state; } @@ -197,38 +241,52 @@ inline bool syn_match_lua_long_bracket_close(const char* line, int len, int i, return false; } +inline void syn_scan_digits(const char* line, int len, int& i, bool sep, bool hex) { + while (i < len) { + if (hex ? syn_is_hex(line[i]) : syn_is_digit(line[i])) { i++; continue; } + // C++14 digit separator: a quote wedged between two digits + if (sep && line[i] == '\'' && i + 1 < len && + (hex ? syn_is_hex(line[i + 1]) : syn_is_digit(line[i + 1]))) { + i += 2; + continue; + } + break; + } +} + inline void syn_consume_number(const char* line, int len, int& i, - SynToken* out, int out_len, bool c_style_suffixes) { + SynToken* out, int out_len, bool c_style_suffixes, + bool digit_sep = false) { int start = i; if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) { i += 2; - while (i < len && syn_is_hex(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, true); if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { i++; - while (i < len && syn_is_hex(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, true); } if (i < len && (line[i] == 'p' || line[i] == 'P')) { int exp = i + 1; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && syn_is_digit(line[exp])) { - i = exp + 1; - while (i < len && syn_is_digit(line[i])) i++; + i = exp; + syn_scan_digits(line, len, i, digit_sep, false); } } } else { if (line[i] == '.') i++; - while (i < len && syn_is_digit(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, false); if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { i++; - while (i < len && syn_is_digit(line[i])) i++; + syn_scan_digits(line, len, i, digit_sep, false); } if (i < len && (line[i] == 'e' || line[i] == 'E')) { int exp = i + 1; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && syn_is_digit(line[exp])) { - i = exp + 1; - while (i < len && syn_is_digit(line[i])) i++; + i = exp; + syn_scan_digits(line, len, i, digit_sep, false); } } } @@ -236,7 +294,8 @@ inline void syn_consume_number(const char* line, int len, int& i, if (c_style_suffixes) { while (i < len && (line[i] == 'u' || line[i] == 'U' || line[i] == 'l' || line[i] == 'L' || - line[i] == 'f' || line[i] == 'F')) + line[i] == 'f' || line[i] == 'F' || + line[i] == 'z' || line[i] == 'Z')) i++; } @@ -255,11 +314,86 @@ inline void syn_consume_number(const char* line, int len, int& i, // `line` points to the first char of the line, `len` is its length // (excluding the newline). `out` can be null if only state tracking is needed. +// ============================================================================ +// C / C++ raw string helpers +// ============================================================================ + +inline bool syn_is_raw_string_prefix(const char* buf, int len) { + return syn_streq(buf, len, "R") || syn_streq(buf, len, "LR") || + syn_streq(buf, len, "uR") || syn_streq(buf, len, "UR") || + syn_streq(buf, len, "u8R"); +} + +inline bool syn_is_string_prefix(const char* buf, int len) { + return syn_streq(buf, len, "L") || syn_streq(buf, len, "u") || + syn_streq(buf, len, "U") || syn_streq(buf, len, "u8"); +} + +// Colors a raw-string body from `i` up to and including the closing )delim". +// If the terminator is not on this line, leaves state.in_raw_string set so the +// next line continues the literal. +inline void syn_consume_raw_body(const char* line, int len, int& i, + SynToken* out, int out_len, SynState& state) { + while (i < len) { + if (line[i] == ')') { + int j = i + 1; + int k = 0; + while (k < state.raw_delim_len && j < len && line[j] == state.raw_delim[k]) { + j++; + k++; + } + if (k == state.raw_delim_len && j < len && line[j] == '"') { + syn_fill_tokens(out, out_len, i, j + 1, SYN_STRING); + i = j + 1; + state.in_raw_string = false; + state.raw_delim_len = 0; + return; + } + } + syn_set_token(out, out_len, i, SYN_STRING); + i++; + } +} + +// Enters a raw string; `line[i]` must be the opening quote. +inline void syn_consume_raw_string(const char* line, int len, int& i, + SynToken* out, int out_len, SynState& state) { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + state.raw_delim_len = 0; + while (i < len && line[i] != '(' && state.raw_delim_len < SYN_RAW_DELIM_MAX) { + state.raw_delim[state.raw_delim_len++] = line[i]; + syn_set_token(out, out_len, i, SYN_STRING); + i++; + } + if (i < len && line[i] == '(') { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + state.in_raw_string = true; + syn_consume_raw_body(line, len, i, out, out_len, state); + return; + } + // Malformed (or an over-long delimiter): treat the rest of the line as string + syn_fill_tokens(out, out_len, i, len, SYN_STRING); + i = len; +} + +// ============================================================================ +// C / C++ highlighting +// ============================================================================ + inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len, - SynState& state) { + SynState& state, SynLanguage lang = SYN_LANG_C) { + const bool cpp = (lang == SYN_LANG_CPP); int i = 0; while (i < len) { + // ---- Raw string continuation ---- + if (state.in_raw_string) { + syn_consume_raw_body(line, len, i, out, out_len, state); + continue; + } + // ---- Block comment continuation ---- if (state.in_block_comment) { while (i < len) { @@ -380,15 +514,32 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o // ---- Numbers ---- if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) { - syn_consume_number(line, len, i, out, out_len, true); + syn_consume_number(line, len, i, out, out_len, true, cpp); continue; } - // ---- Identifiers / keywords / types ---- + // ---- Identifiers / keywords / types / literal prefixes ---- if (syn_is_alpha(c)) { int start = i; while (i < len && syn_is_alnum(line[i])) i++; - SynToken tok = syn_classify_word(SYN_LANG_C, line + start, i - start); + if (cpp && i < len && line[i] == '"' && + syn_is_raw_string_prefix(line + start, i - start)) { + syn_fill_tokens(out, out_len, start, i, SYN_STRING); + syn_consume_raw_string(line, len, i, out, out_len, state); + continue; + } + if (cpp && i < len && line[i] == '"' && + syn_is_string_prefix(line + start, i - start)) { + // The quote itself is handled on the next iteration + syn_fill_tokens(out, out_len, start, i, SYN_STRING); + continue; + } + if (cpp && i < len && line[i] == '\'' && + syn_is_string_prefix(line + start, i - start)) { + syn_fill_tokens(out, out_len, start, i, SYN_CHAR); + continue; + } + SynToken tok = syn_classify_word(lang, line + start, i - start); syn_fill_tokens(out, out_len, start, i, tok); continue; } @@ -508,7 +659,8 @@ inline void syn_highlight_line(const char* line, int len, SynToken* out, int out switch (lang) { case SYN_LANG_C: - syn_highlight_line_c(line, len, out, out_len, state); + case SYN_LANG_CPP: + syn_highlight_line_c(line, len, out, out_len, state, lang); break; case SYN_LANG_LUA: syn_highlight_line_lua(line, len, out, out_len, state); @@ -542,6 +694,13 @@ inline bool syn_path_ends_with(const char* path, const char* suffix) { inline SynLanguage syn_detect_language(const char* filepath) { if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE; + if (syn_path_ends_with(filepath, ".cpp") || syn_path_ends_with(filepath, ".cc") || + syn_path_ends_with(filepath, ".cxx") || syn_path_ends_with(filepath, ".c++") || + syn_path_ends_with(filepath, ".hpp") || syn_path_ends_with(filepath, ".hh") || + syn_path_ends_with(filepath, ".hxx") || syn_path_ends_with(filepath, ".h++") || + syn_path_ends_with(filepath, ".ipp") || syn_path_ends_with(filepath, ".tpp") || + syn_path_ends_with(filepath, ".inl")) + return SYN_LANG_CPP; if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h")) return SYN_LANG_C; if (syn_path_ends_with(filepath, ".lua"))