feat: add C++ syntax highlighting to edit and Text Editor
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* syntax_ansi.hpp
|
* 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
|
* Used by the MontaukOS CLI text editor
|
||||||
* Copyright (c) 2026 Daniel Hammer
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
enum SynLanguage : uint8_t {
|
enum SynLanguage : uint8_t {
|
||||||
SYN_LANG_NONE,
|
SYN_LANG_NONE,
|
||||||
SYN_LANG_C,
|
SYN_LANG_C,
|
||||||
|
SYN_LANG_CPP,
|
||||||
SYN_LANG_LUA,
|
SYN_LANG_LUA,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,10 +32,15 @@ enum SynToken : uint8_t {
|
|||||||
SYN_OPERATOR,
|
SYN_OPERATOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define SYN_RAW_DELIM_MAX 16
|
||||||
|
|
||||||
struct SynState {
|
struct SynState {
|
||||||
int in_block_comment; // 0 or 1 (bool stored as int for freestanding)
|
int in_block_comment; // 0 or 1 (bool stored as int for freestanding)
|
||||||
SynToken long_token;
|
SynToken long_token;
|
||||||
int long_bracket_eqs;
|
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;
|
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) {
|
inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case SYN_LANG_C: return syn_classify_c_word(buf, len);
|
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);
|
case SYN_LANG_LUA: return syn_classify_lua_word(buf, len);
|
||||||
default: return SYN_NORMAL;
|
default: return SYN_NORMAL;
|
||||||
}
|
}
|
||||||
@@ -146,6 +188,8 @@ inline SynState syn_make_state() {
|
|||||||
state.in_block_comment = 0;
|
state.in_block_comment = 0;
|
||||||
state.long_token = SYN_NORMAL;
|
state.long_token = SYN_NORMAL;
|
||||||
state.long_bracket_eqs = -1;
|
state.long_bracket_eqs = -1;
|
||||||
|
state.in_raw_string = false;
|
||||||
|
state.raw_delim_len = 0;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,64 +229,157 @@ inline bool syn_match_lua_long_bracket_close(const char* line, int len, int i,
|
|||||||
// Number consumption
|
// 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,
|
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;
|
int start = i;
|
||||||
|
|
||||||
if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) {
|
if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) {
|
||||||
i += 2;
|
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] == '.')) {
|
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
|
||||||
i++;
|
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')) {
|
if (i < len && (line[i] == 'p' || line[i] == 'P')) {
|
||||||
int exp = i + 1;
|
int exp = i + 1;
|
||||||
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
||||||
if (exp < len && syn_is_digit(line[exp])) {
|
if (exp < len && syn_is_digit(line[exp])) {
|
||||||
i = exp + 1;
|
i = exp;
|
||||||
while (i < len && syn_is_digit(line[i])) i++;
|
syn_scan_digits(line, len, i, digit_sep, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (line[i] == '.') i++;
|
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] == '.')) {
|
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
|
||||||
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] == 'e' || line[i] == 'E')) {
|
if (i < len && (line[i] == 'e' || line[i] == 'E')) {
|
||||||
int exp = i + 1;
|
int exp = i + 1;
|
||||||
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
||||||
if (exp < len && syn_is_digit(line[exp])) {
|
if (exp < len && syn_is_digit(line[exp])) {
|
||||||
i = exp + 1;
|
i = exp;
|
||||||
while (i < len && syn_is_digit(line[i])) i++;
|
syn_scan_digits(line, len, i, digit_sep, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c_style_suffixes) {
|
if (c_style_suffixes) {
|
||||||
while (i < len && (line[i] == 'u' || line[i] == 'U' ||
|
while (i < len && (line[i] == 'u' || line[i] == 'U' ||
|
||||||
line[i] == 'l' || line[i] == 'L' ||
|
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++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
syn_fill_tokens(out, out_len, start, i, SYN_NUMBER);
|
syn_fill_tokens(out, out_len, start, i, SYN_NUMBER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// C highlighting
|
// 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,
|
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;
|
int i = 0;
|
||||||
|
|
||||||
while (i < len) {
|
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) {
|
if (state.in_block_comment) {
|
||||||
while (i < len) {
|
while (i < len) {
|
||||||
if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') {
|
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, SYN_COMMENT);
|
||||||
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
||||||
i += 2;
|
i += 2;
|
||||||
state.in_block_comment = 0;
|
state.in_block_comment = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
syn_set_token(out, out_len, i, SYN_COMMENT);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
char c = line[i];
|
char c = line[i];
|
||||||
|
|
||||||
|
// ---- Line comment ----
|
||||||
if (c == '/' && i + 1 < len && line[i + 1] == '/') {
|
if (c == '/' && i + 1 < len && line[i + 1] == '/') {
|
||||||
syn_fill_tokens(out, out_len, i, len, SYN_COMMENT);
|
syn_fill_tokens(out, out_len, i, len, SYN_COMMENT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Block comment start ----
|
||||||
if (c == '/' && i + 1 < len && line[i + 1] == '*') {
|
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, SYN_COMMENT);
|
||||||
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
||||||
i += 2;
|
i += 2;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Preprocessor directive ----
|
||||||
if (c == '#') {
|
if (c == '#') {
|
||||||
int is_pp = 1;
|
// Check that only whitespace precedes the #
|
||||||
|
bool is_pp = true;
|
||||||
for (int j = 0; j < i; j++) {
|
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) {
|
if (is_pp) {
|
||||||
while (i < len) {
|
while (i < len) {
|
||||||
|
// Handle line-comment inside preprocessor
|
||||||
if (i + 1 < len && line[i] == '/' && line[i + 1] == '/') {
|
if (i + 1 < len && line[i] == '/' && line[i + 1] == '/') {
|
||||||
syn_fill_tokens(out, out_len, i, len, SYN_COMMENT);
|
syn_fill_tokens(out, out_len, i, len, SYN_COMMENT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Handle block comment start inside preprocessor
|
||||||
if (i + 1 < len && line[i] == '/' && line[i + 1] == '*') {
|
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, SYN_COMMENT);
|
||||||
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
||||||
i += 2;
|
i += 2;
|
||||||
|
// Continue consuming as comment
|
||||||
while (i < len) {
|
while (i < len) {
|
||||||
if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') {
|
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, SYN_COMMENT);
|
||||||
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
syn_set_token(out, out_len, i + 1, SYN_COMMENT);
|
||||||
i += 2;
|
i += 2;
|
||||||
state.in_block_comment = 0;
|
state.in_block_comment = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
syn_set_token(out, out_len, i, SYN_COMMENT);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- String literal ----
|
||||||
if (c == '"') {
|
if (c == '"') {
|
||||||
syn_set_token(out, out_len, i, SYN_STRING);
|
syn_set_token(out, out_len, i, SYN_STRING);
|
||||||
i++;
|
i++;
|
||||||
@@ -317,6 +467,8 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Character literal ----
|
||||||
if (c == '\'') {
|
if (c == '\'') {
|
||||||
syn_set_token(out, out_len, i, SYN_CHAR);
|
syn_set_token(out, out_len, i, SYN_CHAR);
|
||||||
i++;
|
i++;
|
||||||
@@ -337,17 +489,40 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Numbers ----
|
||||||
if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Identifiers / keywords / types / literal prefixes ----
|
||||||
if (syn_is_alpha(c)) {
|
if (syn_is_alpha(c)) {
|
||||||
int start = i;
|
int start = i;
|
||||||
while (i < len && syn_is_alnum(line[i])) 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);
|
syn_fill_tokens(out, out_len, start, i, tok);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Operators ----
|
||||||
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' ||
|
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' ||
|
||||||
c == '=' || c == '!' || c == '<' || c == '>' || c == '&' ||
|
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++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Everything else (whitespace, braces, parens, etc.) ----
|
||||||
syn_set_token(out, out_len, i, SYN_NORMAL);
|
syn_set_token(out, out_len, i, SYN_NORMAL);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Lua highlighting
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
inline void syn_highlight_line_lua(const char* line, int len, SynToken* out, int out_len,
|
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;
|
if (!line || len <= 0) return;
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case SYN_LANG_C:
|
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;
|
break;
|
||||||
case SYN_LANG_LUA:
|
case SYN_LANG_LUA:
|
||||||
syn_highlight_line_lua(line, len, out, out_len, state);
|
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) {
|
inline SynLanguage syn_detect_language(const char* filepath) {
|
||||||
if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE;
|
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"))
|
if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h"))
|
||||||
return SYN_LANG_C;
|
return SYN_LANG_C;
|
||||||
if (syn_path_ends_with(filepath, ".lua"))
|
if (syn_path_ends_with(filepath, ".lua"))
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* main.cpp
|
* main.cpp
|
||||||
* MontaukOS Text Editor - standalone Window Server app
|
* MontaukOS Text Editor - standalone Window Server app
|
||||||
* Single-buffer text editor with line numbers, cursor, scrolling, file I/O,
|
* 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
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* syntax_highlight.hpp
|
* syntax_highlight.hpp
|
||||||
* C and Lua syntax highlighting for the MontaukOS text editor
|
* C, C++ and Lua syntax highlighting for the MontaukOS text editor
|
||||||
* Activated for .c, .h, and .lua files
|
* Activated for .c/.h, C++ (.cpp/.hpp/...), and .lua files
|
||||||
* Copyright (c) 2026 Daniel Hammer
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ using namespace gui;
|
|||||||
enum SynLanguage : uint8_t {
|
enum SynLanguage : uint8_t {
|
||||||
SYN_LANG_NONE,
|
SYN_LANG_NONE,
|
||||||
SYN_LANG_C,
|
SYN_LANG_C,
|
||||||
|
SYN_LANG_CPP,
|
||||||
SYN_LANG_LUA,
|
SYN_LANG_LUA,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,10 +34,15 @@ enum SynToken : uint8_t {
|
|||||||
SYN_OPERATOR,
|
SYN_OPERATOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define SYN_RAW_DELIM_MAX 16
|
||||||
|
|
||||||
struct SynState {
|
struct SynState {
|
||||||
bool in_block_comment;
|
bool in_block_comment;
|
||||||
SynToken long_token;
|
SynToken long_token;
|
||||||
int long_bracket_eqs;
|
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;
|
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) {
|
inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case SYN_LANG_C: return syn_classify_c_word(buf, len);
|
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);
|
case SYN_LANG_LUA: return syn_classify_lua_word(buf, len);
|
||||||
default: return SYN_NORMAL;
|
default: return SYN_NORMAL;
|
||||||
}
|
}
|
||||||
@@ -166,6 +208,8 @@ inline SynState syn_make_state() {
|
|||||||
state.in_block_comment = false;
|
state.in_block_comment = false;
|
||||||
state.long_token = SYN_NORMAL;
|
state.long_token = SYN_NORMAL;
|
||||||
state.long_bracket_eqs = -1;
|
state.long_bracket_eqs = -1;
|
||||||
|
state.in_raw_string = false;
|
||||||
|
state.raw_delim_len = 0;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,38 +241,52 @@ inline bool syn_match_lua_long_bracket_close(const char* line, int len, int i,
|
|||||||
return false;
|
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,
|
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;
|
int start = i;
|
||||||
|
|
||||||
if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) {
|
if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) {
|
||||||
i += 2;
|
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] == '.')) {
|
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
|
||||||
i++;
|
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')) {
|
if (i < len && (line[i] == 'p' || line[i] == 'P')) {
|
||||||
int exp = i + 1;
|
int exp = i + 1;
|
||||||
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
||||||
if (exp < len && syn_is_digit(line[exp])) {
|
if (exp < len && syn_is_digit(line[exp])) {
|
||||||
i = exp + 1;
|
i = exp;
|
||||||
while (i < len && syn_is_digit(line[i])) i++;
|
syn_scan_digits(line, len, i, digit_sep, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (line[i] == '.') i++;
|
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] == '.')) {
|
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
|
||||||
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] == 'e' || line[i] == 'E')) {
|
if (i < len && (line[i] == 'e' || line[i] == 'E')) {
|
||||||
int exp = i + 1;
|
int exp = i + 1;
|
||||||
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
|
||||||
if (exp < len && syn_is_digit(line[exp])) {
|
if (exp < len && syn_is_digit(line[exp])) {
|
||||||
i = exp + 1;
|
i = exp;
|
||||||
while (i < len && syn_is_digit(line[i])) i++;
|
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) {
|
if (c_style_suffixes) {
|
||||||
while (i < len && (line[i] == 'u' || line[i] == 'U' ||
|
while (i < len && (line[i] == 'u' || line[i] == 'U' ||
|
||||||
line[i] == 'l' || line[i] == 'L' ||
|
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++;
|
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
|
// `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.
|
// (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,
|
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;
|
int i = 0;
|
||||||
|
|
||||||
while (i < len) {
|
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 ----
|
// ---- Block comment continuation ----
|
||||||
if (state.in_block_comment) {
|
if (state.in_block_comment) {
|
||||||
while (i < len) {
|
while (i < len) {
|
||||||
@@ -380,15 +514,32 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
|
|||||||
|
|
||||||
// ---- Numbers ----
|
// ---- Numbers ----
|
||||||
if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Identifiers / keywords / types ----
|
// ---- Identifiers / keywords / types / literal prefixes ----
|
||||||
if (syn_is_alpha(c)) {
|
if (syn_is_alpha(c)) {
|
||||||
int start = i;
|
int start = i;
|
||||||
while (i < len && syn_is_alnum(line[i])) 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);
|
syn_fill_tokens(out, out_len, start, i, tok);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -508,7 +659,8 @@ inline void syn_highlight_line(const char* line, int len, SynToken* out, int out
|
|||||||
|
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case SYN_LANG_C:
|
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;
|
break;
|
||||||
case SYN_LANG_LUA:
|
case SYN_LANG_LUA:
|
||||||
syn_highlight_line_lua(line, len, out, out_len, state);
|
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) {
|
inline SynLanguage syn_detect_language(const char* filepath) {
|
||||||
if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE;
|
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"))
|
if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h"))
|
||||||
return SYN_LANG_C;
|
return SYN_LANG_C;
|
||||||
if (syn_path_ends_with(filepath, ".lua"))
|
if (syn_path_ends_with(filepath, ".lua"))
|
||||||
|
|||||||
Reference in New Issue
Block a user