/*
* lang_html.hpp
* HTML highlighting, with embedded CSS and JS
* Shared by the MontaukOS text editor (GUI) and edit.elf (CLI)
* Copyright (c) 2026 Daniel Hammer
*/
// Included by ; not standalone.
#pragma once
// ============================================================================
// JavaScript keyword / builtin classification
// ============================================================================
inline SynToken syn_classify_js_word(const char* buf, int len) {
static const char* keywords[] = {
"async", "await", "break", "case", "catch", "class", "const",
"continue", "debugger", "default", "delete", "do", "else",
"export", "extends", "finally", "for", "function", "if",
"import", "in", "instanceof", "let", "new", "of", "return",
"static", "super", "switch", "this", "throw", "try", "typeof",
"var", "void", "while", "with", "yield",
"true", "false", "null", "undefined",
};
static const char* builtins[] = {
"Array", "Boolean", "Date", "Error", "Infinity", "JSON", "Map",
"Math", "NaN", "Number", "Object", "Promise", "Proxy", "RegExp",
"Set", "String", "Symbol", "WeakMap", "WeakSet",
"console", "document", "globalThis", "localStorage", "navigator",
"window", "fetch", "setTimeout", "setInterval",
};
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(builtins) / sizeof(builtins[0])); i++) {
if (syn_streq(buf, len, builtins[i])) return SYN_TYPE;
}
return SYN_NORMAL;
}
// ============================================================================
// JavaScript (also used for begins.
inline void syn_highlight_js_range(const char* line, int limit, int& i,
SynToken* out, int out_len, SynState& state) {
while (i < limit) {
// ---- Block comment continuation ----
if (state.in_block_comment) {
while (i < limit) {
if (i + 1 < limit && line[i] == '*' && line[i + 1] == '/') {
syn_fill_tokens(out, out_len, i, i + 2, SYN_COMMENT);
i += 2;
state.in_block_comment = false;
break;
}
syn_set_token(out, out_len, i, SYN_COMMENT);
i++;
}
continue;
}
char c = line[i];
if (c == '/' && i + 1 < limit && line[i + 1] == '/') {
syn_fill_tokens(out, out_len, i, limit, SYN_COMMENT);
i = limit;
break;
}
if (c == '/' && i + 1 < limit && line[i + 1] == '*') {
state.in_block_comment = true;
syn_fill_tokens(out, out_len, i, i + 2, SYN_COMMENT);
i += 2;
continue;
}
// ---- String / template literal (single line only) ----
if (c == '"' || c == '\'' || c == '`') {
char quote = c;
syn_set_token(out, out_len, i, SYN_STRING);
i++;
while (i < limit) {
if (line[i] == '\\' && i + 1 < limit) {
syn_fill_tokens(out, out_len, i, i + 2, SYN_STRING);
i += 2;
continue;
}
syn_set_token(out, out_len, i, SYN_STRING);
if (line[i] == quote) {
i++;
break;
}
i++;
}
continue;
}
if (syn_is_digit(c) || (c == '.' && i + 1 < limit && syn_is_digit(line[i + 1]))) {
syn_consume_number(line, limit, i, out, out_len, false);
continue;
}
if (syn_is_alpha(c) || c == '$') {
int start = i;
while (i < limit && (syn_is_alnum(line[i]) || line[i] == '$')) i++;
syn_fill_tokens(out, out_len, start, i,
syn_classify_js_word(line + start, i - start));
continue;
}
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' ||
c == '=' || c == '!' || c == '<' || c == '>' || c == '&' ||
c == '|' || c == '^' || c == '~' || c == '?' || c == ':') {
syn_set_token(out, out_len, i, SYN_OPERATOR);
i++;
continue;
}
syn_set_token(out, out_len, i, SYN_NORMAL);
i++;
}
}
// ============================================================================
// CSS (also used for ') {
syn_set_token(out, out_len, i, SYN_OPERATOR);
i++;
state.html_ctx = state.html_pending ? (SynHtmlCtx)state.html_pending
: SYN_HTML_TEXT;
state.html_pending = SYN_HTML_TEXT;
continue;
}
if (c == '/' && i + 1 < len && line[i + 1] == '>') {
syn_fill_tokens(out, out_len, i, i + 2, SYN_OPERATOR);
i += 2;
state.html_ctx = SYN_HTML_TEXT;
state.html_pending = SYN_HTML_TEXT;
continue;
}
if (c == '=') {
syn_set_token(out, out_len, i, SYN_OPERATOR);
i++;
continue;
}
if (c == '"' || c == '\'') {
char quote = c;
syn_set_token(out, out_len, i, SYN_STRING);
i++;
bool closed = false;
while (i < len) {
syn_set_token(out, out_len, i, SYN_STRING);
if (line[i] == quote) {
i++;
closed = true;
break;
}
i++;
}
if (!closed) state.html_quote = quote;
continue;
}
if (syn_is_alpha(c) || c == '-' || c == ':' || c == '@') {
int start = i;
i++;
while (i < len && (syn_is_alnum(line[i]) || line[i] == '-' ||
line[i] == ':' || line[i] == '.'))
i++;
syn_fill_tokens(out, out_len, start, i, SYN_TYPE);
continue;
}
// Unquoted attribute value or stray punctuation
if (c != ' ' && c != '\t') {
int start = i;
while (i < len && line[i] != ' ' && line[i] != '\t' &&
line[i] != '>' && line[i] != '"' && line[i] != '\'')
i++;
if (i == start) i++;
syn_fill_tokens(out, out_len, start, i, SYN_STRING);
continue;
}
syn_set_token(out, out_len, i, SYN_NORMAL);
i++;
continue;
}
// ---- Text content ----
char c = line[i];
if (c == '<') {
if (syn_match_ci(line, len, i, "