16#include <ascii-chat/log/grep.h>
17#include <ascii-chat/common.h>
18#include <ascii-chat/log/logging.h>
19#include <ascii-chat/util/pcre2.h>
20#include <ascii-chat/util/utf8.h>
21#include <ascii-chat/video/ansi_fast.h>
22#include <ascii-chat/platform/terminal.h>
23#include <ascii-chat/platform/thread.h>
24#include <ascii-chat/log/interactive_grep.h>
39#define HIGHLIGHT_DARK_BG 70
40#define HIGHLIGHT_LIGHT_BG 200
46#define MIN_HIGHLIGHT_DISTANCE 40
52static inline float calculate_luminance(uint8_t r, uint8_t g, uint8_t b) {
53 return (0.2126f * r + 0.7152f * g + 0.0722f * b) / 255.0f;
101 .pattern_capacity = 0,
103 .match_data_initialized = 0,
107 .lines_after_match = 0,
108 .max_context_after = 0,
109 .saved_patterns = NULL,
110 .saved_pattern_count = 0,
111 .saved_pattern_capacity = 0,
112 .saved_enabled =
false,
113 .cached_highlight_r = 70,
114 .cached_highlight_g = 70,
115 .cached_highlight_b = 70,
116 .last_color_query_us = 0,
122static void destroy_match_data(
void *data) {
124 pcre2_match_data_free((pcre2_match_data *)data);
131static void create_match_data_key(
void) {
141static void get_highlight_color(uint8_t *r, uint8_t *g, uint8_t *b) {
144 uint64_t cache_age_us = now_us - g_filter_state.last_color_query_us;
147 if (cache_age_us < 2 * US_PER_SEC_INT) {
148 *r = g_filter_state.cached_highlight_r;
149 *g = g_filter_state.cached_highlight_g;
150 *b = g_filter_state.cached_highlight_b;
157 uint8_t bg_r = 0, bg_g = 0, bg_b = 0;
163 float luminance = calculate_luminance(bg_r, bg_g, bg_b);
164 is_dark = (luminance < 0.5f);
175 uint8_t bg_grey = (uint8_t)((bg_r + bg_g + bg_b) / 3);
176 int distance = abs((
int)bg_grey - (
int)grey);
180 grey = is_dark ? 0 : 255;
185 g_filter_state.cached_highlight_r = grey;
186 g_filter_state.cached_highlight_g = grey;
187 g_filter_state.cached_highlight_b = grey;
188 g_filter_state.last_color_query_us = now_us;
202static size_t skip_ansi_codes(
const char *colored_text,
size_t byte_pos) {
203 while (colored_text[byte_pos] !=
'\0' && colored_text[byte_pos] ==
'\x1b') {
206 if (colored_text[byte_pos] ==
'\0') {
209 unsigned char next = (
unsigned char)colored_text[byte_pos];
213 while (colored_text[byte_pos] !=
'\0') {
214 unsigned char c = (
unsigned char)colored_text[byte_pos];
217 if (c >= 0x40 && c <= 0x7E) {
221 }
else if (next >= 0x40 && next <= 0x7E) {
224 }
else if (next ==
'(' || next ==
')' || next ==
'*' || next ==
'+') {
227 if (colored_text[byte_pos] !=
'\0') {
232 if (colored_text[byte_pos] !=
'\0') {
247static size_t map_plain_to_colored_pos(
const char *colored_text,
size_t char_pos) {
249 size_t chars_seen = 0;
251 while (colored_text[byte_pos] !=
'\0' && chars_seen < char_pos) {
253 if (colored_text[byte_pos] ==
'\x1b') {
256 if (colored_text[byte_pos] ==
'\0') {
260 unsigned char next = (
unsigned char)colored_text[byte_pos];
264 while (colored_text[byte_pos] !=
'\0') {
265 unsigned char c = (
unsigned char)colored_text[byte_pos];
268 if (c >= 0x40 && c <= 0x7E) {
272 }
else if (next >= 0x40 && next <= 0x7E) {
275 }
else if (next ==
'(' || next ==
')' || next ==
'*' || next ==
'+') {
278 if (colored_text[byte_pos] !=
'\0') {
283 if (colored_text[byte_pos] !=
'\0') {
290 int utf8_len =
utf8_decode((
const uint8_t *)(colored_text + byte_pos), &codepoint);
296 byte_pos += utf8_len;
309static void ensure_match_data_key_initialized(
void) {
312 if (g_filter_state.match_data_initialized == 2) {
318 if (InterlockedCompareExchange((
volatile LONG *)&g_filter_state.match_data_initialized, 1, 0) == 0) {
319 create_match_data_key();
320 InterlockedExchange((
volatile LONG *)&g_filter_state.match_data_initialized, 2);
323 while (g_filter_state.match_data_initialized != 2) {
330 if (__sync_bool_compare_and_swap(&g_filter_state.match_data_initialized, 0, 1)) {
331 create_match_data_key();
332 __sync_synchronize();
333 g_filter_state.match_data_initialized = 2;
336 while (g_filter_state.match_data_initialized != 2) {
347static pcre2_match_data *get_thread_match_data(
void) {
348 ensure_match_data_key_initialized();
350 pcre2_match_data *data =
ascii_tls_get(g_filter_state.match_data_key);
353 for (
int i = 0; i < g_filter_state.pattern_count; i++) {
354 if (g_filter_state.patterns[i].singleton) {
357 data = pcre2_match_data_create_from_pattern(code, NULL);
416 result.pcre2_options = PCRE2_UTF | PCRE2_UCP;
418 if (!input || strlen(input) == 0) {
422 size_t len = strlen(input);
423 const char *closing_slash = NULL;
424 const char *pattern_start = input;
427 if (input[0] ==
'/') {
435 closing_slash = strchr(input + 1,
'/');
436 if (!closing_slash) {
440 pattern_start = input + 1;
444 closing_slash = strrchr(input,
'/');
446 if (closing_slash && closing_slash > input) {
449 pattern_start = input;
452 closing_slash = NULL;
458 size_t pattern_len = (size_t)(closing_slash - pattern_start);
459 if (pattern_len == 0) {
462 if (pattern_len >=
sizeof(result.pattern)) {
463 pattern_len =
sizeof(result.pattern) - 1;
465 memcpy(result.pattern, pattern_start, pattern_len);
466 result.pattern[pattern_len] =
'\0';
470 const char *flags = closing_slash + 1;
471 bool has_F_flag = (strchr(flags,
'F') != NULL);
474 for (
const char *p = flags; *p; p++) {
479 result.pcre2_options |= PCRE2_CASELESS;
480 result.case_insensitive =
true;
481 }
else if (c ==
'm') {
482 result.pcre2_options |= PCRE2_MULTILINE;
483 }
else if (c ==
's') {
484 result.pcre2_options |= PCRE2_DOTALL;
485 }
else if (c ==
'x') {
486 result.pcre2_options |= PCRE2_EXTENDED;
487 }
else if (c ==
'g') {
488 result.global_flag =
true;
489 }
else if (c ==
'I') {
490 result.invert =
true;
491 }
else if (c ==
'F') {
492 result.is_fixed_string =
true;
498 while (*p >=
'0' && *p <=
'9') {
499 num = num * 10 + (*p -
'0');
503 result.context_after = (num > 0) ? num : 1;
504 }
else if (c ==
'B') {
507 while (*p >=
'0' && *p <=
'9') {
508 num = num * 10 + (*p -
'0');
512 result.context_before = (num > 0) ? num : 1;
513 }
else if (c ==
'C') {
516 while (*p >=
'0' && *p <=
'9') {
517 num = num * 10 + (*p -
'0');
521 int ctx = (num > 0) ? num : 1;
522 result.context_before = ctx;
523 result.context_after = ctx;
534 size_t pattern_len = len;
535 if (pattern_len >=
sizeof(result.pattern)) {
536 pattern_len =
sizeof(result.pattern) - 1;
538 memcpy(result.pattern, input, pattern_len);
539 result.pattern[pattern_len] =
'\0';
552 return SET_ERRNO(ERROR_INVALID_PARAM,
"Pattern cannot be NULL");
554 if (strlen(pattern) == 0) {
555 return SET_ERRNO(ERROR_INVALID_PARAM,
"Pattern cannot be empty");
561 log_error(
"Invalid --grep pattern format: \"%s\"", pattern);
562 log_error(
"Use /pattern/flags format (e.g., \"/query/ig\") or plain regex (e.g., \"query\")");
563 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid --grep pattern format");
567 if (g_filter_state.pattern_count >= g_filter_state.pattern_capacity) {
568 int new_capacity = (g_filter_state.pattern_capacity == 0) ? 4 : g_filter_state.pattern_capacity * 2;
572 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate pattern array");
574 g_filter_state.patterns = new_patterns;
575 g_filter_state.pattern_capacity = new_capacity;
579 grep_pattern_t *new_pat = &g_filter_state.patterns[g_filter_state.pattern_count];
580 memset(new_pat, 0,
sizeof(*new_pat));
583 char *original_copy = SAFE_MALLOC(strlen(pattern) + 1,
char *);
584 if (!original_copy) {
585 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate original pattern string");
587 memcpy(original_copy, pattern, strlen(pattern) + 1);
590 new_pat->
parsed_pattern = SAFE_MALLOC(strlen(parsed.pattern) + 1,
char *);
592 SAFE_FREE(original_copy);
593 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate pattern string");
595 memcpy(new_pat->
parsed_pattern, parsed.pattern, strlen(parsed.pattern) + 1);
599 new_pat->
invert = parsed.invert;
605 if (!parsed.is_fixed_string) {
606 new_pat->
singleton = asciichat_pcre2_singleton_compile(parsed.pattern, parsed.pcre2_options);
609 return SET_ERRNO(ERROR_INVALID_PARAM,
"Failed to compile regex pattern");
616 return SET_ERRNO(ERROR_INVALID_PARAM,
"Regex compilation failed");
620 g_filter_state.pattern_count++;
623 if (parsed.context_before > g_filter_state.buffer_size) {
625 char **new_buffer = SAFE_REALLOC(g_filter_state.line_buffer, parsed.context_before *
sizeof(
char *),
char **);
627 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate context line buffer");
630 for (
int i = g_filter_state.buffer_size; i < parsed.context_before; i++) {
631 new_buffer[i] = NULL;
633 g_filter_state.line_buffer = new_buffer;
634 g_filter_state.buffer_size = parsed.context_before;
638 if (parsed.context_after > g_filter_state.max_context_after) {
639 g_filter_state.max_context_after = parsed.context_after;
642 g_filter_state.enabled =
true;
643 log_info(
"Added --grep pattern #%d: %s", g_filter_state.pattern_count, pattern);
659 if (!g_filter_state.enabled) {
664 if (g_filter_state.lines_after_match > 0) {
665 g_filter_state.lines_after_match--;
670 if (g_filter_state.buffer_size > 0) {
671 SAFE_FREE(g_filter_state.line_buffer[g_filter_state.buffer_pos]);
672 size_t line_len = strlen(log_line) + 1;
673 g_filter_state.line_buffer[g_filter_state.buffer_pos] = SAFE_MALLOC(line_len,
char *);
674 if (g_filter_state.line_buffer[g_filter_state.buffer_pos]) {
675 memcpy(g_filter_state.line_buffer[g_filter_state.buffer_pos], log_line, line_len);
677 g_filter_state.buffer_pos = (g_filter_state.buffer_pos + 1) % g_filter_state.buffer_size;
683 pcre2_match_data *match_data = get_thread_match_data();
684 size_t line_len = strlen(log_line);
687 bool any_match =
false;
690 for (
int i = 0; i < g_filter_state.pattern_count; i++) {
692 bool this_match =
false;
707 if (!matched_pattern) {
708 *match_start = (size_t)(found - log_line);
710 matched_pattern = pat;
724 int rc = pcre2_jit_match(code, (PCRE2_SPTR)log_line, line_len, 0, 0, match_data, NULL);
727 if (!matched_pattern) {
728 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
729 *match_start = (size_t)ovector[0];
730 *match_len = (size_t)(ovector[1] - ovector[0]);
731 matched_pattern = pat;
738 this_match = !this_match;
746 matched_pattern = pat;
752 if (any_match && matched_pattern) {
754 if (matched_pattern->
context_before > 0 && g_filter_state.buffer_size > 0) {
756 : g_filter_state.buffer_size;
759 int start_pos = (g_filter_state.buffer_pos - num_lines + g_filter_state.buffer_size) % g_filter_state.buffer_size;
762 for (
int i = 0; i < num_lines; i++) {
763 int pos = (start_pos + i) % g_filter_state.buffer_size;
764 if (g_filter_state.line_buffer[pos]) {
765 fprintf(stderr,
"%s\n", g_filter_state.line_buffer[pos]);
772 g_filter_state.lines_after_match = matched_pattern->
context_after;
779 if (g_filter_state.buffer_size > 0) {
780 SAFE_FREE(g_filter_state.line_buffer[g_filter_state.buffer_pos]);
781 size_t line_len_copy = strlen(log_line) + 1;
782 g_filter_state.line_buffer[g_filter_state.buffer_pos] = SAFE_MALLOC(line_len_copy,
char *);
783 if (g_filter_state.line_buffer[g_filter_state.buffer_pos]) {
784 memcpy(g_filter_state.line_buffer[g_filter_state.buffer_pos], log_line, line_len_copy);
786 g_filter_state.buffer_pos = (g_filter_state.buffer_pos + 1) % g_filter_state.buffer_size;
796 size_t result_len = strlen(result) + 1;
797 char *copy = SAFE_MALLOC(result_len,
char *);
799 memcpy(copy, result, result_len);
808 static __thread
char highlight_buffer[16384];
810 if (!colored_text || !plain_text || match_len == 0) {
816 bool should_use_global_pattern =
false;
819 if (!is_interactive_grep) {
822 for (
int i = 0; i < g_filter_state.pattern_count; i++) {
823 if (g_filter_state.patterns[i].global_flag && !g_filter_state.patterns[i].is_fixed_string) {
824 global_pat = &g_filter_state.patterns[i];
825 should_use_global_pattern =
true;
833 should_use_global_pattern =
true;
837 if (should_use_global_pattern) {
838 pcre2_code *code = NULL;
839 pcre2_match_data *match_data = NULL;
840 char fixed_string_pattern[256] = {0};
841 bool is_fixed_string_pattern =
false;
842 bool case_insensitive_fixed =
false;
845 if (global_pat && global_pat->
singleton) {
847 match_data = get_thread_match_data();
848 }
else if (is_interactive_grep) {
851 if (grep_singleton_void) {
856 match_data = pcre2_match_data_create_from_pattern(code, NULL);
862 if (pattern_len > 0 && pattern && pattern_len < (
int)
sizeof(fixed_string_pattern)) {
863 SAFE_STRNCPY(fixed_string_pattern, pattern,
sizeof(fixed_string_pattern) - 1);
864 is_fixed_string_pattern =
true;
870 if (!code && !is_fixed_string_pattern) {
871 if (match_data && is_interactive_grep) {
872 pcre2_match_data_free(match_data);
877 size_t plain_len = strlen(plain_text);
878 size_t colored_len = strlen(colored_text);
879 char *dst = highlight_buffer;
880 size_t plain_offset = 0;
881 size_t colored_pos = 0;
884 if (is_fixed_string_pattern) {
885 while (plain_offset < plain_len) {
887 const char *found = NULL;
888 if (case_insensitive_fixed) {
889 found =
utf8_strcasestr(plain_text + plain_offset, fixed_string_pattern);
891 found = strstr(plain_text + plain_offset, fixed_string_pattern);
898 size_t plain_match_start = (size_t)(found - plain_text);
899 size_t plain_match_end = plain_match_start + strlen(fixed_string_pattern);
902 size_t colored_match_start = map_plain_to_colored_pos(colored_text, plain_match_start);
903 size_t colored_match_end = map_plain_to_colored_pos(colored_text, plain_match_end);
906 if (colored_match_start > colored_pos) {
907 memcpy(dst, colored_text + colored_pos, colored_match_start - colored_pos);
908 dst += (colored_match_start - colored_pos);
913 get_highlight_color(&r, &g, &b);
917 size_t match_byte_len = colored_match_end - colored_match_start;
918 memcpy(dst, colored_text + colored_match_start, match_byte_len);
919 dst += match_byte_len;
922 memcpy(dst,
"\x1b[49m", 5);
925 colored_pos = colored_match_end;
926 plain_offset = plain_match_end;
930 if (colored_pos < colored_len) {
931 memcpy(dst, colored_text + colored_pos, colored_len - colored_pos);
932 dst += (colored_len - colored_pos);
936 memcpy(dst,
"\x1b[0m", 4);
940 return highlight_buffer;
944 while (plain_offset < plain_len) {
945 int rc = pcre2_jit_match(code, (PCRE2_SPTR)plain_text, plain_len, plain_offset, 0, match_data, NULL);
950 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
951 size_t plain_match_start = (size_t)ovector[0];
952 size_t plain_match_end = (size_t)ovector[1];
955 size_t colored_match_start = map_plain_to_colored_pos(colored_text, plain_match_start);
956 size_t colored_match_end = map_plain_to_colored_pos(colored_text, plain_match_end);
959 if (colored_match_start > colored_pos) {
960 memcpy(dst, colored_text + colored_pos, colored_match_start - colored_pos);
961 dst += (colored_match_start - colored_pos);
966 get_highlight_color(&r, &g, &b);
970 size_t match_byte_len = colored_match_end - colored_match_start;
971 const char *match_src = colored_text + colored_match_start;
972 char *dst_end = highlight_buffer +
sizeof(highlight_buffer) - 1;
974 while (i < match_byte_len) {
976 if (i + 4 <= match_byte_len && match_src[i] ==
'\x1b' && match_src[i + 1] ==
'[' && match_src[i + 2] ==
'0' &&
977 match_src[i + 3] ==
'm') {
979 if (dst + 4 >= dst_end)
981 *dst++ = match_src[i++];
982 *dst++ = match_src[i++];
983 *dst++ = match_src[i++];
984 *dst++ = match_src[i++];
986 if (dst + 20 < dst_end) {
988 get_highlight_color(&r2, &g2, &b2);
991 }
else if (i + 5 <= match_byte_len && match_src[i] ==
'\x1b' && match_src[i + 1] ==
'[' &&
992 match_src[i + 2] ==
'0' && match_src[i + 3] ==
'0' && match_src[i + 4] ==
'm') {
994 if (dst + 5 >= dst_end)
996 *dst++ = match_src[i++];
997 *dst++ = match_src[i++];
998 *dst++ = match_src[i++];
999 *dst++ = match_src[i++];
1000 *dst++ = match_src[i++];
1002 if (dst + 20 < dst_end) {
1004 get_highlight_color(&r2, &g2, &b2);
1009 if (dst + 1 >= dst_end)
1011 *dst++ = match_src[i++];
1016 memcpy(dst,
"\x1b[49m", 5);
1019 colored_pos = colored_match_end;
1020 plain_offset = plain_match_end;
1023 if (plain_match_end == plain_match_start) {
1029 if (colored_pos < colored_len) {
1030 memcpy(dst, colored_text + colored_pos, colored_len - colored_pos);
1031 dst += (colored_len - colored_pos);
1035 memcpy(dst,
"\x1b[0m", 4);
1041 if (is_interactive_grep && match_data && !global_pat) {
1042 pcre2_match_data_free(match_data);
1045 return highlight_buffer;
1050 size_t colored_start = map_plain_to_colored_pos(colored_text, match_start);
1051 size_t colored_end = map_plain_to_colored_pos(colored_text, match_start + match_len);
1053 size_t colored_len = strlen(colored_text);
1054 char *dst = highlight_buffer;
1057 if (colored_start > 0) {
1058 memcpy(dst, colored_text, colored_start);
1059 dst += colored_start;
1064 get_highlight_color(&r, &g, &b);
1068 size_t match_byte_len = colored_end - colored_start;
1069 const char *match_src = colored_text + colored_start;
1070 char *dst_end = highlight_buffer +
sizeof(highlight_buffer) - 1;
1073 while (i < match_byte_len) {
1075 if (i + 4 <= match_byte_len && match_src[i] ==
'\x1b' && match_src[i + 1] ==
'[' && match_src[i + 2] ==
'0' &&
1076 match_src[i + 3] ==
'm') {
1078 if (dst + 4 >= dst_end)
1080 *dst++ = match_src[i++];
1081 *dst++ = match_src[i++];
1082 *dst++ = match_src[i++];
1083 *dst++ = match_src[i++];
1085 if (dst + 20 < dst_end) {
1087 get_highlight_color(&r2, &g2, &b2);
1090 }
else if (i + 5 <= match_byte_len && match_src[i] ==
'\x1b' && match_src[i + 1] ==
'[' &&
1091 match_src[i + 2] ==
'0' && match_src[i + 3] ==
'0' && match_src[i + 4] ==
'm') {
1093 if (dst + 5 >= dst_end)
1095 *dst++ = match_src[i++];
1096 *dst++ = match_src[i++];
1097 *dst++ = match_src[i++];
1098 *dst++ = match_src[i++];
1099 *dst++ = match_src[i++];
1101 if (dst + 20 < dst_end) {
1103 get_highlight_color(&r3, &g3, &b3);
1108 if (dst + 1 >= dst_end) {
1111 *dst++ = match_src[i++];
1117 memcpy(dst,
"\x1b[49m", 5);
1121 size_t remaining = colored_len - colored_end;
1122 if (remaining > 0) {
1123 memcpy(dst, colored_text + colored_end, remaining);
1128 return highlight_buffer;
1131const char *
grep_highlight(
const char *log_line,
size_t match_start,
size_t match_len) {
1132 static __thread
char highlight_buffer[16384];
1134 if (!log_line || match_len == 0) {
1138 size_t line_len = strlen(log_line);
1139 if (match_start + match_len > line_len) {
1143 char *dst = highlight_buffer;
1144 const char *src = log_line;
1149 for (
int i = 0; i < g_filter_state.pattern_count; i++) {
1150 if (g_filter_state.patterns[i].global_flag && !g_filter_state.patterns[i].is_fixed_string) {
1151 global_pat = &g_filter_state.patterns[i];
1156 if (global_pat && global_pat->
singleton) {
1158 pcre2_match_data *match_data = get_thread_match_data();
1160 if (!code || !match_data) {
1166 while (offset < line_len) {
1167 int rc = pcre2_jit_match(code, (PCRE2_SPTR)log_line, line_len, offset, 0, match_data, NULL);
1172 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
1173 size_t match_pos = (size_t)ovector[0];
1174 size_t match_end = (size_t)ovector[1];
1175 size_t len = match_end - match_pos;
1178 if (match_pos > pos) {
1179 memcpy(dst, src + pos, match_pos - pos);
1180 dst += (match_pos - pos);
1185 get_highlight_color(&r, &g, &b);
1187 memcpy(dst, src + match_pos, len);
1189 memcpy(dst,
"\x1b[0m", 4);
1202 if (pos < line_len) {
1203 memcpy(dst, src + pos, line_len - pos);
1204 dst += (line_len - pos);
1210 if (match_start > 0) {
1211 memcpy(dst, log_line, match_start);
1217 get_highlight_color(&r, &g, &b);
1221 memcpy(dst, log_line + match_start, match_len);
1225 memcpy(dst,
"\x1b[0m", 4);
1229 size_t remaining = line_len - (match_start + match_len);
1230 if (remaining > 0) {
1231 memcpy(dst, log_line + match_start + match_len, remaining);
1237 return highlight_buffer;
1242 for (
int i = 0; i < g_filter_state.pattern_count; i++) {
1245 char *original_to_free = (
char *)pat->
original;
1246 SAFE_FREE(original_to_free);
1251 SAFE_FREE(g_filter_state.patterns);
1252 g_filter_state.patterns = NULL;
1255 if (g_filter_state.line_buffer) {
1256 for (
int i = 0; i < g_filter_state.buffer_size; i++) {
1257 SAFE_FREE(g_filter_state.line_buffer[i]);
1259 SAFE_FREE(g_filter_state.line_buffer);
1260 g_filter_state.line_buffer = NULL;
1263 g_filter_state.pattern_count = 0;
1264 g_filter_state.pattern_capacity = 0;
1265 g_filter_state.buffer_size = 0;
1266 g_filter_state.buffer_pos = 0;
1267 g_filter_state.lines_after_match = 0;
1268 g_filter_state.max_context_after = 0;
1269 g_filter_state.enabled =
false;
1280 if (g_filter_state.saved_patterns) {
1281 for (
int i = 0; i < g_filter_state.saved_pattern_count; i++) {
1282 SAFE_FREE(g_filter_state.saved_patterns[i].parsed_pattern);
1284 SAFE_FREE(g_filter_state.saved_patterns);
1288 if (g_filter_state.pattern_count > 0) {
1289 g_filter_state.saved_patterns =
1291 if (!g_filter_state.saved_patterns) {
1292 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate saved pattern array");
1296 for (
int i = 0; i < g_filter_state.pattern_count; i++) {
1307 for (
int j = 0; j < i; j++) {
1308 SAFE_FREE(g_filter_state.saved_patterns[j].parsed_pattern);
1310 SAFE_FREE(g_filter_state.saved_patterns);
1311 g_filter_state.saved_patterns = NULL;
1312 return SET_ERRNO(ERROR_MEMORY,
"Failed to save pattern string");
1321 g_filter_state.saved_pattern_count = g_filter_state.pattern_count;
1322 g_filter_state.saved_pattern_capacity = g_filter_state.pattern_count;
1323 g_filter_state.saved_enabled = g_filter_state.enabled;
1325 return ASCIICHAT_OK;
1330 for (
int i = 0; i < g_filter_state.pattern_count; i++) {
1331 SAFE_FREE(g_filter_state.patterns[i].parsed_pattern);
1332 g_filter_state.patterns[i].singleton = NULL;
1336 if (g_filter_state.saved_pattern_count > g_filter_state.pattern_capacity) {
1339 if (!new_patterns) {
1340 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate pattern array for restore");
1342 g_filter_state.patterns = new_patterns;
1343 g_filter_state.pattern_capacity = g_filter_state.saved_pattern_count;
1347 if (g_filter_state.saved_patterns) {
1348 for (
int i = 0; i < g_filter_state.saved_pattern_count; i++) {
1359 for (
int j = 0; j < i; j++) {
1360 SAFE_FREE(g_filter_state.patterns[j].parsed_pattern);
1362 g_filter_state.pattern_count = 0;
1363 return SET_ERRNO(ERROR_MEMORY,
"Failed to restore pattern string");
1372 g_filter_state.pattern_count = g_filter_state.saved_pattern_count;
1373 g_filter_state.enabled = g_filter_state.saved_enabled;
1375 return ASCIICHAT_OK;
1381 g_filter_state.pattern_count = 0;
1382 g_filter_state.enabled =
false;
1386 return g_filter_state.pattern_count;
1390 if (g_filter_state.pattern_count == 0) {
1395 return g_filter_state.patterns[g_filter_state.pattern_count - 1].original;
char * append_truecolor_bg(char *dst, uint8_t r, uint8_t g, uint8_t b)
asciichat_error_t grep_restore_patterns(void)
#define HIGHLIGHT_LIGHT_BG
asciichat_error_t grep_save_patterns(void)
uint8_t cached_highlight_b
char * grep_highlight_colored_copy(const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
int pattern_count
Number of active patterns.
int saved_pattern_count
Number of saved patterns.
int lines_after_match
Counter for context_after lines.
const char * grep_get_last_pattern(void)
const char * grep_highlight(const char *log_line, size_t match_start, size_t match_len)
void grep_clear_patterns(void)
bool saved_enabled
Saved enabled state.
int grep_get_pattern_count(void)
asciichat_error_t grep_init(const char *pattern)
tls_key_t match_data_key
Thread-local match_data key.
uint8_t cached_highlight_r
bool grep_should_output(const char *log_line, size_t *match_start, size_t *match_len)
grep_parse_result_t grep_parse_pattern(const char *input)
Parse pattern in /pattern/flags or pattern/flags format.
#define HIGHLIGHT_DARK_BG
Default highlight colors (grey) Dark grey (70) for dark backgrounds, light grey (200) for light backg...
bool enabled
Is filtering active?
int buffer_pos
Current position in buffer.
grep_parse_result_t parse_result_t
volatile int match_data_initialized
Once flag for key initialization.
uint8_t cached_highlight_g
int pattern_capacity
Allocated capacity.
uint64_t last_color_query_us
#define MIN_HIGHLIGHT_DISTANCE
Minimum color difference threshold (0-255 scale) If background is within this distance of the highlig...
int saved_pattern_capacity
Allocated capacity for saved.
grep_pattern_t * saved_patterns
Backup of patterns for restore.
int buffer_size
Size of circular buffer.
grep_pattern_t * patterns
Array of patterns.
char ** line_buffer
Circular buffer for context_before.
const char * grep_highlight_colored(const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
int max_context_after
Maximum context_after across all patterns.
bool interactive_grep_get_case_insensitive(void)
const char * interactive_grep_get_input_buffer(void)
bool interactive_grep_is_active(void)
bool interactive_grep_get_global_highlight(void)
int interactive_grep_get_input_len(void)
void * interactive_grep_get_pattern_singleton(void)
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Single filter pattern with all its settings.
int context_before
Lines before match (B flag)
int context_after
Lines after match (A flag)
bool case_insensitive
Case-insensitive matching (i flag)
bool global_flag
Highlight all matches (g flag)
bool is_fixed_string
True for fixed string matching (no regex)
const char * original
Original pattern string (for display)
bool invert
Invert match (I flag)
pcre2_singleton_t * singleton
PCRE2 singleton (NULL if fixed string)
char * parsed_pattern
Parsed pattern (without delimiters/flags)
Represents a thread-safe compiled PCRE2 regex singleton.
int ascii_tls_key_create(tls_key_t *key, void(*destructor)(void *))
int ascii_tls_set(tls_key_t key, void *value)
void * ascii_tls_get(tls_key_t key)
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
const char * utf8_strcasestr(const char *haystack, const char *needle)
Case-insensitive substring search with full Unicode support.