aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui/conversation_content_view/syntax_highlighting.vala
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-08-17 16:32:52 -0400
committerfrosty <gabriel@bwaaa.monster>2026-08-17 16:32:52 -0400
commitf9e3b8ba7da220746151179ed10a90c2ac215ffc (patch)
tree0d18a6617fefc2b41d9db75f5c79335607954874 /main/src/ui/conversation_content_view/syntax_highlighting.vala
parent6d0706bcaa6d54354e416174e3d2654a2bff81fe (diff)
Begin implementing codeblocks feature
Diffstat (limited to 'main/src/ui/conversation_content_view/syntax_highlighting.vala')
-rw-r--r--main/src/ui/conversation_content_view/syntax_highlighting.vala136
1 files changed, 136 insertions, 0 deletions
diff --git a/main/src/ui/conversation_content_view/syntax_highlighting.vala b/main/src/ui/conversation_content_view/syntax_highlighting.vala
new file mode 100644
index 00000000..b45ba81c
--- /dev/null
+++ b/main/src/ui/conversation_content_view/syntax_highlighting.vala
@@ -0,0 +1,136 @@
+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<HighlightSpan> highlight(string text) {
+ var spans = new ArrayList<HighlightSpan>();
+ 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);
+}
+
+}