using Gee; namespace Dino.Ui.CodeSyntax { public enum TokenStyle { COMMENT, CONSTANT, FUNCTION, KEYWORD, NUMBER, PREPROCESSOR, STRING, TYPE } public class HighlightSpan : Object { public int start; public int end; public TokenStyle style; public HighlightSpan(int start, int end, TokenStyle style) { this.start = start; this.end = end; this.style = style; } } public class HighlightRule : Object { public TokenStyle style; private Regex regex; public HighlightRule(TokenStyle style, string pattern) { this.style = style; try { regex = new Regex(pattern, RegexCompileFlags.OPTIMIZE); } catch (RegexError error) { GLib.error("Invalid syntax highlighting rule: %s", error.message); } } public bool find(string text, int offset, out int start, out int end) { MatchInfo match_info; try { regex.match_full(text, -1, offset, 0, out match_info); } catch (RegexError error) { start = -1; end = -1; return false; } if (!match_info.matches()) { start = -1; end = -1; return false; } match_info.fetch_pos(0, out start, out end); return true; } } public class LanguageDefinition : Object { public string id; public string name; public string extension; public string[] aliases; public HighlightRule[] rules; public LanguageDefinition(string id, string name, string extension, string[] aliases, HighlightRule[] rules) { this.id = id; this.name = name; this.extension = extension; this.aliases = aliases; this.rules = rules; } public bool matches(string language) { string normalized = language.down().strip(); if (normalized == id) return true; foreach (string alias in aliases) { if (normalized == alias) return true; } return false; } public ArrayList highlight(string text) { var spans = new ArrayList(); int offset = 0; while (offset < text.length) { HighlightRule? selected_rule = null; int selected_start = text.length; int selected_end = text.length; foreach (HighlightRule rule in rules) { int start; int end; if (rule.find(text, offset, out start, out end) && start < selected_start) { selected_rule = rule; selected_start = start; selected_end = end; } } if (selected_rule == null || selected_end <= selected_start) break; spans.add(new HighlightSpan(selected_start, selected_end, selected_rule.style)); offset = selected_end; } return spans; } } private static LanguageDefinition[]? definitions; public static LanguageDefinition? find(string language) { if (definitions == null) definitions = create_definitions(); foreach (LanguageDefinition definition in definitions) { if (definition.matches(language)) return definition; } return null; } private static LanguageDefinition[] create_definitions() { return { create_c_definition() }; } private static LanguageDefinition create_c_definition() { HighlightRule[] rules = { new HighlightRule(TokenStyle.COMMENT, "//[^\\r\\n]*|/\\*[\\s\\S]*?\\*/"), new HighlightRule(TokenStyle.STRING, "\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'"), new HighlightRule(TokenStyle.PREPROCESSOR, "(?m)^[\\t ]*#[\\t ]*[A-Za-z_][A-Za-z0-9_]*"), new HighlightRule(TokenStyle.KEYWORD, "\\b(?:alignas|alignof|asm|auto|break|case|const|constexpr|continue|default|do|else|enum|extern|for|goto|if|inline|register|restrict|return|sizeof|static|static_assert|struct|switch|thread_local|typedef|typeof|typeof_unqual|union|volatile|while|_Alignas|_Alignof|_Atomic|_Generic|_Noreturn|_Static_assert|_Thread_local)\\b"), new HighlightRule(TokenStyle.TYPE, "\\b(?:bool|char|char8_t|double|float|int|long|short|signed|unsigned|void|_Bool|_Complex|_Decimal32|_Decimal64|_Decimal128|_Imaginary|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|intptr_t|uintptr_t|ptrdiff_t|size_t|ssize_t|wchar_t)\\b"), new HighlightRule(TokenStyle.CONSTANT, "\\b(?:false|NULL|nullptr|true)\\b"), new HighlightRule(TokenStyle.NUMBER, "\\b(?:0[xX][0-9A-Fa-f]+|0[bB][01]+|(?:[0-9]+\\.[0-9]*|[0-9]+)(?:[eEpP][+-]?[0-9]+)?)[fFlLuU]*\\b"), new HighlightRule(TokenStyle.FUNCTION, "\\b[A-Za-z_][A-Za-z0-9_]*(?=[\\t ]*\\()") }; return new LanguageDefinition("c", "C", "c", { "h", "c89", "c90", "c99", "c11", "c17", "c18", "c23" }, rules); } }