ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
grep.h File Reference

Go to the source code of this file.

Data Structures

struct  grep_parse_result_t
 Parsed pattern result from /pattern/flags syntax. More...
 

Functions

grep_parse_result_t grep_parse_pattern (const char *input)
 Parse pattern in /pattern/flags format or plain regex format.
 
asciichat_error_t grep_init (const char *pattern)
 Initialize log filtering with PCRE2 regex pattern.
 
bool grep_should_output (const char *log_line, size_t *match_start, size_t *match_len)
 Check if log line should be output (matches filter)
 
const char * grep_highlight (const char *log_line, size_t match_start, size_t match_len)
 Highlight matched portion with background color.
 
const char * grep_highlight_colored (const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
 Highlight matches in colored text while preserving original colors.
 
char * grep_highlight_colored_copy (const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
 Highlight matches in colored text, returning a new allocated copy.
 
void grep_destroy (void)
 Clean up filter resources.
 
asciichat_error_t grep_save_patterns (void)
 Save current filter patterns for later restoration.
 
asciichat_error_t grep_restore_patterns (void)
 Restore previously saved filter patterns.
 
void grep_clear_patterns (void)
 Clear all filter patterns.
 
int grep_get_pattern_count (void)
 Get number of active filter patterns.
 
const char * grep_get_last_pattern (void)
 Get the last (most recent) filter pattern string.
 

Function Documentation

◆ grep_clear_patterns()

void grep_clear_patterns ( void  )

Clear all filter patterns.

Temporarily disables filtering by setting pattern count to 0. Patterns remain allocated and can be restored later. Used by interactive grep when user types first character.

Definition at line 1315 of file grep.c.

1315 {
1316 // Don't free patterns, just set count to 0
1317 // This allows quick restore without reallocation
1318 g_filter_state.pattern_count = 0;
1319 g_filter_state.enabled = false;
1320}

◆ grep_destroy()

void grep_destroy ( void  )

Clean up filter resources.

Frees compiled regex. Thread-local match_data is cleaned up via pthread_key destructor.

Definition at line 1177 of file grep.c.

1177 {
1178 // Free all patterns
1179 for (int i = 0; i < g_filter_state.pattern_count; i++) {
1180 grep_pattern_t *pat = &g_filter_state.patterns[i];
1181 // Free the copied original string (need temp variable due to const qualifier)
1182 char *original_to_free = (char *)pat->original;
1183 SAFE_FREE(original_to_free);
1184 SAFE_FREE(pat->parsed_pattern);
1185 // Singletons are auto-cleaned by asciichat_pcre2_cleanup_all()
1186 pat->singleton = NULL;
1187 }
1188 SAFE_FREE(g_filter_state.patterns);
1189 g_filter_state.patterns = NULL; // Make idempotent
1190
1191 // Free context buffer
1192 if (g_filter_state.line_buffer) {
1193 for (int i = 0; i < g_filter_state.buffer_size; i++) {
1194 SAFE_FREE(g_filter_state.line_buffer[i]);
1195 }
1196 SAFE_FREE(g_filter_state.line_buffer);
1197 g_filter_state.line_buffer = NULL; // Make idempotent
1198 }
1199
1200 g_filter_state.pattern_count = 0;
1201 g_filter_state.pattern_capacity = 0;
1202 g_filter_state.buffer_size = 0;
1203 g_filter_state.buffer_pos = 0;
1204 g_filter_state.lines_after_match = 0;
1205 g_filter_state.max_context_after = 0;
1206 g_filter_state.enabled = false;
1207
1208 // Note: Thread-local match_data is cleaned up via pthread_key destructor
1209}
#define SAFE_FREE(ptr)
Definition common.h:376
Single filter pattern with all its settings.
Definition grep.c:65
const char * original
Original pattern string (for display)
Definition grep.c:66

References grep_pattern_t::original, grep_pattern_t::parsed_pattern, SAFE_FREE, and grep_pattern_t::singleton.

Referenced by log_destroy().

◆ grep_get_last_pattern()

const char * grep_get_last_pattern ( void  )

Get the last (most recent) filter pattern string.

Returns the original pattern string (with /pattern/flags syntax if provided) of the last –grep argument. Used by interactive grep to populate its input buffer when starting with CLI –grep patterns.

Returns
Pattern string of last CLI –grep, or NULL if no patterns

Definition at line 1326 of file grep.c.

1326 {
1327 if (g_filter_state.pattern_count == 0) {
1328 return NULL;
1329 }
1330
1331 // Return the original pattern string of the last pattern
1332 return g_filter_state.patterns[g_filter_state.pattern_count - 1].original;
1333}

Referenced by log_search_enter_mode(), log_search_init(), ui_status_display(), and ui_status_display_interactive().

◆ grep_get_pattern_count()

int grep_get_pattern_count ( void  )

Get number of active filter patterns.

Returns
Number of currently active patterns

Definition at line 1322 of file grep.c.

1322 {
1323 return g_filter_state.pattern_count;
1324}

◆ grep_highlight()

const char * grep_highlight ( const char *  log_line,
size_t  match_start,
size_t  match_len 
)

Highlight matched portion with background color.

Inserts ANSI escape codes for yellow background (255, 200, 0) with black foreground (0, 0, 0) around the matched text.

Parameters
log_lineOriginal log line
match_startStart offset of match
match_lenLength of match
Returns
Highlighted string (pointer to static thread-local buffer)

Definition at line 1074 of file grep.c.

1074 {
1075 static __thread char highlight_buffer[16384];
1076
1077 if (!log_line || match_len == 0) {
1078 return log_line; // No highlighting needed
1079 }
1080
1081 size_t line_len = strlen(log_line);
1082 if (match_start + match_len > line_len) {
1083 return log_line; // Invalid range
1084 }
1085
1086 char *dst = highlight_buffer;
1087 const char *src = log_line;
1088 size_t pos = 0;
1089
1090 // If any pattern has global flag, highlight ALL matches
1091 grep_pattern_t *global_pat = NULL;
1092 for (int i = 0; i < g_filter_state.pattern_count; i++) {
1093 if (g_filter_state.patterns[i].global_flag && !g_filter_state.patterns[i].is_fixed_string) {
1094 global_pat = &g_filter_state.patterns[i];
1095 break;
1096 }
1097 }
1098
1099 if (global_pat && global_pat->singleton) {
1100 pcre2_code *code = asciichat_pcre2_singleton_get_code(global_pat->singleton);
1101 pcre2_match_data *match_data = get_thread_match_data();
1102
1103 if (!code || !match_data) {
1104 return log_line; // Fall back to no highlighting
1105 }
1106
1107 // Find all matches in the line
1108 size_t offset = 0;
1109 while (offset < line_len) {
1110 int rc = pcre2_jit_match(code, (PCRE2_SPTR)log_line, line_len, offset, 0, match_data, NULL);
1111 if (rc < 0) {
1112 break; // No more matches
1113 }
1114
1115 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
1116 size_t match_pos = (size_t)ovector[0];
1117 size_t match_end = (size_t)ovector[1];
1118 size_t len = match_end - match_pos;
1119
1120 // Copy text before match
1121 if (match_pos > pos) {
1122 memcpy(dst, src + pos, match_pos - pos);
1123 dst += (match_pos - pos);
1124 }
1125
1126 // Highlight the match (background only, preserve foreground color)
1127 uint8_t r, g, b;
1128 get_highlight_color(&r, &g, &b);
1129 dst = append_truecolor_bg(dst, r, g, b);
1130 memcpy(dst, src + match_pos, len);
1131 dst += len;
1132
1133 pos = match_end;
1134 offset = match_end;
1135
1136 // Prevent infinite loop on zero-length matches
1137 if (len == 0) {
1138 offset++;
1139 }
1140 }
1141
1142 // Copy remaining text after last match
1143 if (pos < line_len) {
1144 memcpy(dst, src + pos, line_len - pos);
1145 dst += (line_len - pos);
1146 }
1147
1148 } else {
1149 // Single match highlighting (original behavior)
1150 // Copy text before match
1151 if (match_start > 0) {
1152 memcpy(dst, log_line, match_start);
1153 dst += match_start;
1154 }
1155
1156 // Add background color for match (preserve foreground color)
1157 uint8_t r, g, b;
1158 get_highlight_color(&r, &g, &b);
1159 dst = append_truecolor_bg(dst, r, g, b);
1160
1161 // Copy matched text
1162 memcpy(dst, log_line + match_start, match_len);
1163 dst += match_len;
1164
1165 // Copy text after match
1166 size_t remaining = line_len - (match_start + match_len);
1167 if (remaining > 0) {
1168 memcpy(dst, log_line + match_start + match_len, remaining);
1169 dst += remaining;
1170 }
1171 }
1172
1173 *dst = '\0';
1174 return highlight_buffer;
1175}
char * append_truecolor_bg(char *dst, uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:173
unsigned char uint8_t
Definition common.h:56
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:98
pcre2_singleton_t * singleton
PCRE2 singleton (NULL if fixed string)
Definition grep.c:68

References append_truecolor_bg(), asciichat_pcre2_singleton_get_code(), and grep_pattern_t::singleton.

◆ grep_highlight_colored()

const char * grep_highlight_colored ( const char *  colored_text,
const char *  plain_text,
size_t  match_start,
size_t  match_len 
)

Highlight matches in colored text while preserving original colors.

Parameters
colored_textOriginal text with ANSI color codes
plain_textStripped plain text (for match positions)
match_startMatch start position in plain text
match_lenMatch length in plain text
Returns
Highlighted text with yellow background added to matches (pointer to static thread-local buffer)

Definition at line 755 of file grep.c.

756 {
757 static __thread char highlight_buffer[16384];
758
759 if (!colored_text || !plain_text || match_len == 0) {
760 return colored_text;
761 }
762
763 // When interactive grep is active, check if it has global flag
764 bool is_log_search = log_search_is_active();
765 bool should_use_global_pattern = false;
766 grep_pattern_t *global_pat = NULL;
767
768 if (!is_log_search) {
769 // If any pattern has global flag, highlight ALL matches for that pattern
770 // Find first pattern with global flag
771 for (int i = 0; i < g_filter_state.pattern_count; i++) {
772 if (g_filter_state.patterns[i].global_flag && !g_filter_state.patterns[i].is_fixed_string) {
773 global_pat = &g_filter_state.patterns[i];
774 should_use_global_pattern = true;
775 break;
776 }
777 }
778 } else if (log_search_get_global_highlight()) {
779 // Interactive grep is active and has /g flag - do global matching on the provided pattern
780 // We'll do all-match highlighting using the match position to extract the pattern
781 // and re-match for all occurrences
782 should_use_global_pattern = true;
783 // global_pat stays NULL, but we set the flag to enable global matching below
784 }
785
786 if (should_use_global_pattern) {
787 pcre2_code *code = NULL;
788 pcre2_match_data *match_data = NULL;
789 char fixed_string_pattern[256] = {0};
790 bool is_fixed_string_pattern = false;
791 bool case_insensitive_fixed = false;
792
793 // Get code from CLI pattern or interactive grep pattern
794 if (global_pat && global_pat->singleton) {
796 match_data = get_thread_match_data();
797 } else if (is_log_search) {
798 // Interactive grep global highlighting - get pattern from grep state
799 void *grep_singleton_void = log_search_get_pattern_singleton();
800 if (grep_singleton_void) {
801 pcre2_singleton_t *grep_singleton = (pcre2_singleton_t *)grep_singleton_void;
802 code = asciichat_pcre2_singleton_get_code(grep_singleton);
803 // Create match data for this pattern
804 if (code) {
805 match_data = pcre2_match_data_create_from_pattern(code, NULL);
806 }
807 } else {
808 // No regex pattern - check if it's a fixed string pattern in interactive grep
809 int pattern_len = log_search_get_input_len();
810 const char *pattern = log_search_get_input_buffer();
811 if (pattern_len > 0 && pattern && pattern_len < (int)sizeof(fixed_string_pattern)) {
812 SAFE_STRNCPY(fixed_string_pattern, pattern, sizeof(fixed_string_pattern) - 1);
813 is_fixed_string_pattern = true;
814 case_insensitive_fixed = log_search_get_case_insensitive();
815 }
816 }
817 }
818
819 if (!code && !is_fixed_string_pattern) {
820 if (match_data && is_log_search) {
821 pcre2_match_data_free(match_data); // Free if we created it
822 }
823 return colored_text; // Fall back to no highlighting
824 }
825
826 size_t plain_len = strlen(plain_text);
827 size_t colored_len = strlen(colored_text);
828 char *dst = highlight_buffer;
829 size_t plain_offset = 0;
830 size_t colored_pos = 0;
831
832 // Handle fixed string global matching
833 if (is_fixed_string_pattern) {
834 while (plain_offset < plain_len) {
835 // Find next occurrence of fixed string
836 const char *found = NULL;
837 if (case_insensitive_fixed) {
838 found = utf8_strcasestr(plain_text + plain_offset, fixed_string_pattern);
839 } else {
840 found = strstr(plain_text + plain_offset, fixed_string_pattern);
841 }
842
843 if (!found) {
844 break; // No more matches
845 }
846
847 size_t plain_match_start = (size_t)(found - plain_text);
848 size_t plain_match_end = plain_match_start + strlen(fixed_string_pattern);
849
850 // Map to colored text positions (same as single-match path)
851 size_t colored_match_start = map_plain_to_colored_pos(colored_text, plain_match_start);
852 size_t colored_match_end = map_plain_to_colored_pos(colored_text, plain_match_end);
853
854 // Copy text before match
855 if (colored_match_start > colored_pos) {
856 memcpy(dst, colored_text + colored_pos, colored_match_start - colored_pos);
857 dst += (colored_match_start - colored_pos);
858 }
859
860 // Add highlight background
861 uint8_t r, g, b;
862 get_highlight_color(&r, &g, &b);
863 dst = append_truecolor_bg(dst, r, g, b);
864
865 // Copy matched text with background
866 size_t match_byte_len = colored_match_end - colored_match_start;
867 memcpy(dst, colored_text + colored_match_start, match_byte_len);
868 dst += match_byte_len;
869
870 // Reset background
871 memcpy(dst, "\x1b[49m", 5);
872 dst += 5;
873
874 colored_pos = colored_match_end;
875 plain_offset = plain_match_end;
876 }
877
878 // Copy remaining text
879 if (colored_pos < colored_len) {
880 memcpy(dst, colored_text + colored_pos, colored_len - colored_pos);
881 dst += (colored_len - colored_pos);
882 }
883
884 // Reset both background and foreground at end
885 *dst = '\0';
886 return highlight_buffer;
887 }
888
889 // Find all matches in plain text and highlight in colored text (regex matching)
890 while (plain_offset < plain_len) {
891 int rc = pcre2_jit_match(code, (PCRE2_SPTR)plain_text, plain_len, plain_offset, 0, match_data, NULL);
892 if (rc < 0) {
893 break; // No more matches
894 }
895
896 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
897 size_t plain_match_start = (size_t)ovector[0];
898 size_t plain_match_end = (size_t)ovector[1];
899
900 // Map to colored text positions
901 size_t colored_match_start = map_plain_to_colored_pos(colored_text, plain_match_start);
902 size_t colored_match_end = map_plain_to_colored_pos(colored_text, plain_match_end);
903
904 // Copy text before match
905 if (colored_match_start > colored_pos) {
906 memcpy(dst, colored_text + colored_pos, colored_match_start - colored_pos);
907 dst += (colored_match_start - colored_pos);
908 }
909
910 // Add highlight background
911 uint8_t r, g, b;
912 get_highlight_color(&r, &g, &b);
913 dst = append_truecolor_bg(dst, r, g, b);
914
915 // Copy matched text, re-applying background after any [0m or [00m reset codes
916 size_t match_byte_len = colored_match_end - colored_match_start;
917 const char *match_src = colored_text + colored_match_start;
918 char *dst_end = highlight_buffer + sizeof(highlight_buffer) - 1; // Absolute buffer end
919 size_t i = 0;
920 while (i < match_byte_len) {
921 // Check for [0m or [00m reset codes
922 if (i + 4 <= match_byte_len && match_src[i] == '\x1b' && match_src[i + 1] == '[' && match_src[i + 2] == '0' &&
923 match_src[i + 3] == 'm') {
924 // Found [0m - copy it and re-apply background (if room)
925 if (dst + 4 >= dst_end)
926 break; // Not enough space for the reset code itself
927 *dst++ = match_src[i++]; // ESC
928 *dst++ = match_src[i++]; // '['
929 *dst++ = match_src[i++]; // '0'
930 *dst++ = match_src[i++]; // 'm'
931 // Re-apply highlight background after reset (only if room)
932 if (dst + 20 < dst_end) {
933 uint8_t r2, g2, b2;
934 get_highlight_color(&r2, &g2, &b2);
935 dst = append_truecolor_bg(dst, r2, g2, b2);
936 }
937 } else if (i + 5 <= match_byte_len && match_src[i] == '\x1b' && match_src[i + 1] == '[' &&
938 match_src[i + 2] == '0' && match_src[i + 3] == '0' && match_src[i + 4] == 'm') {
939 // Found [00m - copy it and re-apply background (if room)
940 if (dst + 5 >= dst_end)
941 break; // Not enough space for the reset code itself
942 *dst++ = match_src[i++]; // ESC
943 *dst++ = match_src[i++]; // '['
944 *dst++ = match_src[i++]; // '0'
945 *dst++ = match_src[i++]; // '0'
946 *dst++ = match_src[i++]; // 'm'
947 // Re-apply highlight background after reset (only if room)
948 if (dst + 20 < dst_end) {
949 uint8_t r2, g2, b2;
950 get_highlight_color(&r2, &g2, &b2);
951 dst = append_truecolor_bg(dst, r2, g2, b2);
952 }
953 } else {
954 // Regular character - just copy
955 if (dst + 1 >= dst_end)
956 break;
957 *dst++ = match_src[i++];
958 }
959 }
960
961 // Reset background only
962 memcpy(dst, "\x1b[49m", 5);
963 dst += 5;
964
965 colored_pos = colored_match_end;
966 plain_offset = plain_match_end;
967
968 // Prevent infinite loop on zero-length matches
969 if (plain_match_end == plain_match_start) {
970 plain_offset++;
971 }
972 }
973
974 // Copy remaining text
975 if (colored_pos < colored_len) {
976 memcpy(dst, colored_text + colored_pos, colored_len - colored_pos);
977 dst += (colored_len - colored_pos);
978 }
979
980 // Reset both background and foreground at end to prevent color bleeding
981 *dst = '\0';
982
983 // Clean up match data if we created it for interactive grep
984 if (is_log_search && match_data && !global_pat) {
985 pcre2_match_data_free(match_data);
986 }
987
988 return highlight_buffer;
989 }
990
991 // Single match highlighting (same logic as global path)
992 // map_plain_to_colored_pos(N) returns byte position after counting N characters
993 size_t colored_start = map_plain_to_colored_pos(colored_text, match_start);
994 size_t colored_end = map_plain_to_colored_pos(colored_text, match_start + match_len);
995
996 size_t colored_len = strlen(colored_text);
997 char *dst = highlight_buffer;
998
999 // Copy everything before the match
1000 if (colored_start > 0) {
1001 memcpy(dst, colored_text, colored_start);
1002 dst += colored_start;
1003 }
1004
1005 // Add highlight background
1006 uint8_t r, g, b;
1007 get_highlight_color(&r, &g, &b);
1008 dst = append_truecolor_bg(dst, r, g, b);
1009
1010 // Copy matched text, re-applying background after any [0m or [00m reset codes
1011 size_t match_byte_len = colored_end - colored_start;
1012 const char *match_src = colored_text + colored_start;
1013 char *dst_end = highlight_buffer + sizeof(highlight_buffer) - 1; // Absolute buffer end
1014 size_t i = 0;
1015
1016 while (i < match_byte_len) {
1017 // Check for [0m or [00m reset codes
1018 if (i + 4 <= match_byte_len && match_src[i] == '\x1b' && match_src[i + 1] == '[' && match_src[i + 2] == '0' &&
1019 match_src[i + 3] == 'm') {
1020 // Found [0m - copy it and re-apply background (if room)
1021 if (dst + 4 >= dst_end)
1022 break; // Not enough space for even the reset code
1023 *dst++ = match_src[i++]; // ESC
1024 *dst++ = match_src[i++]; // '['
1025 *dst++ = match_src[i++]; // '0'
1026 *dst++ = match_src[i++]; // 'm'
1027 // Re-apply highlight background after reset (only if room)
1028 if (dst + 20 < dst_end) {
1029 uint8_t r2, g2, b2;
1030 get_highlight_color(&r2, &g2, &b2);
1031 dst = append_truecolor_bg(dst, r2, g2, b2);
1032 }
1033 } else if (i + 5 <= match_byte_len && match_src[i] == '\x1b' && match_src[i + 1] == '[' &&
1034 match_src[i + 2] == '0' && match_src[i + 3] == '0' && match_src[i + 4] == 'm') {
1035 // Found [00m - copy it and re-apply background (if room)
1036 if (dst + 5 >= dst_end)
1037 break; // Not enough space for even the reset code
1038 *dst++ = match_src[i++]; // ESC
1039 *dst++ = match_src[i++]; // '['
1040 *dst++ = match_src[i++]; // '0'
1041 *dst++ = match_src[i++]; // '0'
1042 *dst++ = match_src[i++]; // 'm'
1043 // Re-apply highlight background after reset (only if room)
1044 if (dst + 20 < dst_end) {
1045 uint8_t r3, g3, b3;
1046 get_highlight_color(&r3, &g3, &b3);
1047 dst = append_truecolor_bg(dst, r3, g3, b3);
1048 }
1049 } else {
1050 // Regular character - just copy
1051 if (dst + 1 >= dst_end) {
1052 break;
1053 }
1054 *dst++ = match_src[i++];
1055 }
1056 }
1057
1058 // Reset only the background color to preserve foreground colors from headers
1059 // Using \x1b[49m (reset background only) instead of \x1b[0m (reset all)
1060 memcpy(dst, "\x1b[49m", 5);
1061 dst += 5;
1062
1063 // Copy remaining text
1064 size_t remaining = colored_len - colored_end;
1065 if (remaining > 0) {
1066 memcpy(dst, colored_text + colored_end, remaining);
1067 dst += remaining;
1068 }
1069
1070 *dst = '\0';
1071 return highlight_buffer;
1072}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
const char * utf8_strcasestr(const char *haystack, const char *needle)
Case-insensitive substring search with full Unicode support.
Definition utf8.c:272
bool log_search_get_global_highlight(void)
Check if global highlighting (/g flag) is enabled in current pattern.
Definition search.c:896
int log_search_get_input_len(void)
Get current input buffer length (must hold mutex)
Definition search.c:940
const char * log_search_get_input_buffer(void)
Get current input buffer content (must hold mutex)
Definition search.c:948
bool log_search_is_active(void)
Check if filtering is active.
Definition search.c:390
bool log_search_get_case_insensitive(void)
Check if case-insensitive flag is set (must hold mutex)
Definition search.c:952
void * log_search_get_pattern_singleton(void)
Get the compiled regex pattern for interactive grep (internal use)
Definition search.c:911
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:22

References append_truecolor_bg(), asciichat_pcre2_singleton_get_code(), log_search_get_case_insensitive(), log_search_get_global_highlight(), log_search_get_input_buffer(), log_search_get_input_len(), log_search_get_pattern_singleton(), log_search_is_active(), SAFE_STRNCPY, grep_pattern_t::singleton, and utf8_strcasestr().

Referenced by grep_highlight_colored_copy(), and terminal_screen_render().

◆ grep_highlight_colored_copy()

char * grep_highlight_colored_copy ( const char *  colored_text,
const char *  plain_text,
size_t  match_start,
size_t  match_len 
)

Highlight matches in colored text, returning a new allocated copy.

Like grep_highlight_colored() but returns a newly allocated string instead of a static buffer. Caller must free the returned string with SAFE_FREE().

Parameters
colored_textOriginal text with ANSI color codes
plain_textStripped plain text (for match positions)
match_startMatch start position in plain text
match_lenMatch length in plain text
Returns
Newly allocated highlighted text (caller must free), or NULL on error

Definition at line 741 of file grep.c.

742 {
743 const char *result = grep_highlight_colored(colored_text, plain_text, match_start, match_len);
744 if (result) {
745 size_t result_len = strlen(result) + 1;
746 char *copy = SAFE_MALLOC(result_len, char *);
747 if (copy) {
748 memcpy(copy, result, result_len);
749 return copy;
750 }
751 }
752 return NULL;
753}
const char * grep_highlight_colored(const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
Highlight matches in colored text while preserving original colors.
Definition grep.c:755
#define SAFE_MALLOC(size, cast)
Definition common.h:264

References grep_highlight_colored(), and SAFE_MALLOC.

◆ grep_init()

asciichat_error_t grep_init ( const char *  pattern)

Initialize log filtering with PCRE2 regex pattern.

Compiles the pattern eagerly at startup. If compilation fails, logs a warning and disables filtering.

Parameters
patternPCRE2 regex pattern (NULL or empty = no filtering)
Returns
ASCIICHAT_OK on success, ERROR_INVALID_PARAM on bad regex

Definition at line 498 of file grep.c.

498 {
499 // Reject NULL or empty patterns
500 if (!pattern) {
501 return SET_ERRNO(ERROR_INVALID_PARAM, "Pattern cannot be NULL");
502 }
503 if (strlen(pattern) == 0) {
504 return SET_ERRNO(ERROR_INVALID_PARAM, "Pattern cannot be empty");
505 }
506
507 // Parse pattern
508 parse_result_t parsed = grep_parse_pattern(pattern);
509 if (!parsed.valid) {
510 log_error("Invalid --grep pattern format: \"%s\"", pattern);
511 log_error("Use /pattern/flags format (e.g., \"/query/ig\") or plain regex (e.g., \"query\")");
512 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid --grep pattern format");
513 }
514
515 // Allocate or expand pattern array
516 if (g_filter_state.pattern_count >= g_filter_state.pattern_capacity) {
517 int new_capacity = (g_filter_state.pattern_capacity == 0) ? 4 : g_filter_state.pattern_capacity * 2;
518 grep_pattern_t *new_patterns =
519 SAFE_REALLOC(g_filter_state.patterns, new_capacity * sizeof(grep_pattern_t), grep_pattern_t *);
520 if (!new_patterns) {
521 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate pattern array");
522 }
523 g_filter_state.patterns = new_patterns;
524 g_filter_state.pattern_capacity = new_capacity;
525 }
526
527 // Add new pattern
528 grep_pattern_t *new_pat = &g_filter_state.patterns[g_filter_state.pattern_count];
529 memset(new_pat, 0, sizeof(*new_pat));
530
531 // Make a copy of the original pattern string (argv pointers may be deallocated)
532 char *original_copy = SAFE_MALLOC(strlen(pattern) + 1, char *);
533 if (!original_copy) {
534 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate original pattern string");
535 }
536 memcpy(original_copy, pattern, strlen(pattern) + 1);
537 new_pat->original = original_copy;
538
539 new_pat->parsed_pattern = SAFE_MALLOC(strlen(parsed.pattern) + 1, char *);
540 if (!new_pat->parsed_pattern) {
541 SAFE_FREE(original_copy);
542 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate pattern string");
543 }
544 memcpy(new_pat->parsed_pattern, parsed.pattern, strlen(parsed.pattern) + 1);
545
546 new_pat->is_fixed_string = parsed.is_fixed_string;
547 new_pat->case_insensitive = parsed.case_insensitive;
548 new_pat->invert = parsed.invert;
549 new_pat->global_flag = parsed.global_flag;
550 new_pat->context_before = parsed.context_before;
551 new_pat->context_after = parsed.context_after;
552
553 // Compile regex (skip for fixed strings)
554 if (!parsed.is_fixed_string) {
556 if (!new_pat->singleton) {
557 SAFE_FREE(new_pat->parsed_pattern);
558 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to compile regex pattern");
559 }
560
561 // Validate compilation
562 pcre2_code *code = asciichat_pcre2_singleton_get_code(new_pat->singleton);
563 if (!code) {
564 SAFE_FREE(new_pat->parsed_pattern);
565 return SET_ERRNO(ERROR_INVALID_PARAM, "Regex compilation failed");
566 }
567 }
568
569 g_filter_state.pattern_count++;
570
571 // Update context buffer size if needed
572 if (parsed.context_before > g_filter_state.buffer_size) {
573 // Reallocate line buffer
574 char **new_buffer = SAFE_REALLOC(g_filter_state.line_buffer, parsed.context_before * sizeof(char *), char **);
575 if (!new_buffer) {
576 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate context line buffer");
577 }
578 // Initialize new slots
579 for (int i = g_filter_state.buffer_size; i < parsed.context_before; i++) {
580 new_buffer[i] = NULL;
581 }
582 g_filter_state.line_buffer = new_buffer;
583 g_filter_state.buffer_size = parsed.context_before;
584 }
585
586 // Update max context_after
587 if (parsed.context_after > g_filter_state.max_context_after) {
588 g_filter_state.max_context_after = parsed.context_after;
589 }
590
591 g_filter_state.enabled = true;
592 log_info("Added --grep pattern #%d: %s", g_filter_state.pattern_count, pattern);
593
594 return ASCIICHAT_OK;
595}
grep_parse_result_t grep_parse_pattern(const char *input)
Parse pattern in /pattern/flags or pattern/flags format.
Definition grep.c:351
int pattern_capacity
Allocated capacity.
Definition grep.c:83
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:284
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
pcre2_singleton_t * asciichat_pcre2_singleton_compile(const char *pattern, uint32_t flags)
Compile and cache a PCRE2 regex pattern with thread-safe singleton semantics.
Definition pcre2.c:47
Parsed pattern result from /pattern/flags syntax.
Definition grep.h:18
int context_before
Lines before match (B<n> flag)
Definition grep.h:25
bool global_flag
Highlight all matches (g flag)
Definition grep.h:24
bool is_fixed_string
True if fixed string (not regex)
Definition grep.h:21
bool invert
Invert match (I flag)
Definition grep.h:23
char pattern[4096]
Parsed pattern string.
Definition grep.h:19
bool case_insensitive
Case-insensitive (i flag)
Definition grep.h:22
bool valid
True if parsing succeeded.
Definition grep.h:27
int context_after
Lines after match (A<n> flag)
Definition grep.h:26
uint32_t pcre2_options
PCRE2 compile options.
Definition grep.h:20
int context_before
Lines before match (B flag)
Definition grep.c:73
int context_after
Lines after match (A flag)
Definition grep.c:74
bool case_insensitive
Case-insensitive matching (i flag)
Definition grep.c:70
bool global_flag
Highlight all matches (g flag)
Definition grep.c:72
bool is_fixed_string
True for fixed string matching (no regex)
Definition grep.c:69
bool invert
Invert match (I flag)
Definition grep.c:71
char * parsed_pattern
Parsed pattern (without delimiters/flags)
Definition grep.c:67

References ASCIICHAT_OK, asciichat_pcre2_singleton_compile(), asciichat_pcre2_singleton_get_code(), grep_parse_result_t::case_insensitive, grep_pattern_t::case_insensitive, grep_parse_result_t::context_after, grep_pattern_t::context_after, grep_parse_result_t::context_before, grep_pattern_t::context_before, ERROR_INVALID_PARAM, ERROR_MEMORY, grep_parse_result_t::global_flag, grep_pattern_t::global_flag, grep_parse_pattern(), grep_parse_result_t::invert, grep_pattern_t::invert, grep_parse_result_t::is_fixed_string, grep_pattern_t::is_fixed_string, log_error, log_info, grep_pattern_t::original, grep_pattern_t::parsed_pattern, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, SAFE_FREE, SAFE_MALLOC, SAFE_REALLOC, SET_ERRNO, grep_pattern_t::singleton, and grep_parse_result_t::valid.

Referenced by main().

◆ grep_parse_pattern()

grep_parse_result_t grep_parse_pattern ( const char *  input)

Parse pattern in /pattern/flags format or plain regex format.

Supports two formats:

  • Format 1: /pattern/flags (e.g., "/error/igC2")
  • Format 2: plain pattern (e.g., "error")

Flags: i(case-insensitive), m(multiline), s(dotall), x(extended), g(global highlight), I(invert), F(fixed string), A<n>(after context), B<n>(before context), C<n>(both context)

Parameters
inputInput pattern string
Returns
Parsed result with pattern, flags, and validity

Parse pattern in /pattern/flags format or plain regex format.

Parameters
inputInput pattern string (either /pattern/flags, pattern/flags, or plain pattern)
Returns
Parse result with all extracted settings

Format 1 (with flags, explicit): /pattern/flags

  • Pattern enclosed in forward slashes with optional flags
  • Use F flag for fixed string (literal) matching

Format 2 (with flags, implicit): pattern/flags

  • Interactive grep format (no leading slash, since / starts the grep command)
  • Treats last slash as delimiter

Format 3 (plain regex): pattern

  • Plain regex pattern without slashes or flags
  • Treated as regex with default options (no flags)

Flags (formats 1 & 2):

  • i: case-insensitive
  • m: multiline mode (regex only)
  • s: dotall mode (regex only)
  • x: extended mode (regex only)
  • g: global (highlight all matches)
  • I: invert match (show non-matching lines)
  • F: fixed string (literal match, no regex)
  • A<n>: show n lines after match (e.g., A3)
  • B<n>: show n lines before match (e.g., B2)
  • C<n>: show n lines before and after match (e.g., C5)

Examples:

  • "/test/" - Simple regex with slashes
  • "test" - Plain regex without slashes
  • "/query/i" - Case-insensitive regex
  • "/test/F" - Fixed string match for "test"
  • "/api/v1/users/F" - Fixed string match for "api/v1/users"
  • "/ERROR/IA3" - Invert match + 3 lines after
  • "/FATAL/B2A5F" - Fixed string, 2 before, 5 after
  • "error|warn" - Plain regex with alternation
  • "search/i" - Interactive grep: case-insensitive "search"
  • "api/v1/users/F" - Interactive grep: fixed string match

Definition at line 351 of file grep.c.

351 {
352 parse_result_t result = {0};
353 result.pcre2_options = PCRE2_UTF | PCRE2_UCP; // Default: UTF-8 mode
354
355 if (!input || strlen(input) == 0) {
356 return result; // Invalid: empty pattern
357 }
358
359 size_t len = strlen(input);
360 const char *closing_slash = NULL;
361 const char *pattern_start = input;
362
363 // Check if pattern uses /pattern/flags format (explicit)
364 if (input[0] == '/') {
365 // Format 1: /pattern/flags
366 if (len < 3) {
367 return result; // Invalid: too short for /pattern/ format
368 }
369
370 // Find closing slash - don't treat backslash as an escape character
371 // (backslashes are regex escapes, not delimiter escapes)
372 closing_slash = strchr(input + 1, '/');
373 if (!closing_slash) {
374 return result; // Invalid: missing closing /
375 }
376
377 pattern_start = input + 1;
378 } else {
379 // Check for pattern/flags format (implicit, no leading slash)
380 // Find the last forward slash to use as delimiter
381 closing_slash = strrchr(input, '/');
382
383 const char *implicit_flags = closing_slash ? closing_slash + 1 : NULL;
384 bool implicit_flags_valid = closing_slash && closing_slash > input && *implicit_flags != '\0';
385 if (implicit_flags_valid) {
386 for (const char *flag = implicit_flags; *flag; flag++) {
387 if (!strchr("imsxgIF", *flag) && *flag != 'A' && *flag != 'B' && *flag != 'C' &&
388 !(*flag >= '0' && *flag <= '9')) {
389 implicit_flags_valid = false;
390 break;
391 }
392 }
393 }
394
395 if (implicit_flags_valid) {
396 // There's at least one slash (not at the beginning)
397 // Try to parse as pattern/flags format
398 pattern_start = input;
399 } else {
400 // No slash or slash at beginning - treat as plain pattern
401 closing_slash = NULL;
402 }
403 }
404
405 if (closing_slash) {
406 // Extract pattern between start and closing slash
407 size_t pattern_len = (size_t)(closing_slash - pattern_start);
408 if (pattern_len == 0) {
409 return result; // Invalid: empty pattern
410 }
411 if (pattern_len >= sizeof(result.pattern)) {
412 pattern_len = sizeof(result.pattern) - 1;
413 }
414 memcpy(result.pattern, pattern_start, pattern_len);
415 result.pattern[pattern_len] = '\0';
416
417 // Parse flags after closing slash
418 // First pass: check for F flag
419 const char *flags = closing_slash + 1;
420 bool has_F_flag = (strchr(flags, 'F') != NULL);
421
422 // Parse all flags
423 for (const char *p = flags; *p; p++) {
424 char c = *p;
425
426 // Single-character flags
427 if (c == 'i') {
428 result.pcre2_options |= PCRE2_CASELESS;
429 result.case_insensitive = true;
430 } else if (c == 'm') {
431 result.pcre2_options |= PCRE2_MULTILINE;
432 } else if (c == 's') {
433 result.pcre2_options |= PCRE2_DOTALL;
434 } else if (c == 'x') {
435 result.pcre2_options |= PCRE2_EXTENDED;
436 } else if (c == 'g') {
437 result.global_flag = true;
438 } else if (c == 'I') {
439 result.invert = true;
440 } else if (c == 'F') {
441 result.is_fixed_string = true;
442 }
443 // Multi-character flags with integers
444 else if (c == 'A') {
445 p++; // Move to digits
446 int num = 0;
447 while (*p >= '0' && *p <= '9') {
448 num = num * 10 + (*p - '0');
449 p++;
450 }
451 p--; // Back up one (for loop will increment)
452 result.context_after = (num > 0) ? num : 1; // Default to 1 if no number
453 } else if (c == 'B') {
454 p++;
455 int num = 0;
456 while (*p >= '0' && *p <= '9') {
457 num = num * 10 + (*p - '0');
458 p++;
459 }
460 p--;
461 result.context_before = (num > 0) ? num : 1;
462 } else if (c == 'C') {
463 p++;
464 int num = 0;
465 while (*p >= '0' && *p <= '9') {
466 num = num * 10 + (*p - '0');
467 p++;
468 }
469 p--;
470 int ctx = (num > 0) ? num : 1;
471 result.context_before = ctx;
472 result.context_after = ctx;
473 } else {
474 // Invalid flag character - only error if not using F flag
475 if (!has_F_flag) {
476 return result; // Invalid
477 }
478 // Otherwise ignore invalid flags (they're part of the fixed string context)
479 }
480 }
481 } else {
482 // Format 3: Plain pattern without slashes (treat as regex, no flags)
483 size_t pattern_len = len;
484 if (pattern_len >= sizeof(result.pattern)) {
485 pattern_len = sizeof(result.pattern) - 1;
486 }
487 memcpy(result.pattern, input, pattern_len);
488 result.pattern[pattern_len] = '\0';
489
490 // No flags for plain format - just use default options
491 // (PCRE2_UTF | PCRE2_UCP already set at the top)
492 }
493
494 result.valid = true;
495 return result;
496}

References grep_parse_result_t::case_insensitive, grep_parse_result_t::context_after, grep_parse_result_t::context_before, grep_parse_result_t::global_flag, grep_parse_result_t::invert, grep_parse_result_t::is_fixed_string, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, and grep_parse_result_t::valid.

Referenced by grep_init(), log_search_enter_mode(), log_search_exit_mode(), log_search_gather_and_filter_logs(), log_search_get_match_info(), log_search_handle_key(), and log_search_init().

◆ grep_restore_patterns()

asciichat_error_t grep_restore_patterns ( void  )

Restore previously saved filter patterns.

Restores filter patterns saved by grep_save_patterns(). Used by interactive grep when user cancels (Escape) to restore CLI –grep patterns.

Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 1265 of file grep.c.

1265 {
1266 // Clear current patterns (but don't free the array)
1267 for (int i = 0; i < g_filter_state.pattern_count; i++) {
1268 SAFE_FREE(g_filter_state.patterns[i].parsed_pattern);
1269 g_filter_state.patterns[i].singleton = NULL;
1270 }
1271
1272 // Ensure we have capacity for saved patterns
1273 if (g_filter_state.saved_pattern_count > g_filter_state.pattern_capacity) {
1274 grep_pattern_t *new_patterns = SAFE_REALLOC(
1275 g_filter_state.patterns, g_filter_state.saved_pattern_count * sizeof(grep_pattern_t), grep_pattern_t *);
1276 if (!new_patterns) {
1277 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate pattern array for restore");
1278 }
1279 g_filter_state.patterns = new_patterns;
1280 g_filter_state.pattern_capacity = g_filter_state.saved_pattern_count;
1281 }
1282
1283 // Restore patterns
1284 if (g_filter_state.saved_patterns) {
1285 for (int i = 0; i < g_filter_state.saved_pattern_count; i++) {
1286 grep_pattern_t *src = &g_filter_state.saved_patterns[i];
1287 grep_pattern_t *dst = &g_filter_state.patterns[i];
1288
1289 // Copy pattern structure
1290 *dst = *src;
1291
1292 // Deep copy parsed_pattern string
1293 dst->parsed_pattern = SAFE_MALLOC(strlen(src->parsed_pattern) + 1, char *);
1294 if (!dst->parsed_pattern) {
1295 // Clean up partial restore
1296 for (int j = 0; j < i; j++) {
1297 SAFE_FREE(g_filter_state.patterns[j].parsed_pattern);
1298 }
1299 g_filter_state.pattern_count = 0;
1300 return SET_ERRNO(ERROR_MEMORY, "Failed to restore pattern string");
1301 }
1302 memcpy(dst->parsed_pattern, src->parsed_pattern, strlen(src->parsed_pattern) + 1);
1303
1304 // Singleton pointers are shared (reference counted)
1305 dst->singleton = src->singleton;
1306 }
1307 }
1308
1309 g_filter_state.pattern_count = g_filter_state.saved_pattern_count;
1310 g_filter_state.enabled = g_filter_state.saved_enabled;
1311
1312 return ASCIICHAT_OK;
1313}

References ASCIICHAT_OK, ERROR_MEMORY, grep_pattern_t::parsed_pattern, SAFE_FREE, SAFE_MALLOC, SAFE_REALLOC, SET_ERRNO, and grep_pattern_t::singleton.

Referenced by log_search_exit_mode().

◆ grep_save_patterns()

asciichat_error_t grep_save_patterns ( void  )

Save current filter patterns for later restoration.

Saves the current state of CLI –grep patterns so they can be restored later. Used by interactive grep to preserve initial filters when entering interactive mode.

Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 1215 of file grep.c.

1215 {
1216 // Free previous saved patterns if any
1217 if (g_filter_state.saved_patterns) {
1218 for (int i = 0; i < g_filter_state.saved_pattern_count; i++) {
1219 SAFE_FREE(g_filter_state.saved_patterns[i].parsed_pattern);
1220 }
1221 SAFE_FREE(g_filter_state.saved_patterns);
1222 }
1223
1224 // Allocate saved pattern array
1225 if (g_filter_state.pattern_count > 0) {
1226 g_filter_state.saved_patterns =
1227 SAFE_MALLOC(g_filter_state.pattern_count * sizeof(grep_pattern_t), grep_pattern_t *);
1228 if (!g_filter_state.saved_patterns) {
1229 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate saved pattern array");
1230 }
1231
1232 // Deep copy all patterns
1233 for (int i = 0; i < g_filter_state.pattern_count; i++) {
1234 grep_pattern_t *src = &g_filter_state.patterns[i];
1235 grep_pattern_t *dst = &g_filter_state.saved_patterns[i];
1236
1237 // Copy pattern structure
1238 *dst = *src;
1239
1240 // Deep copy parsed_pattern string
1241 dst->parsed_pattern = SAFE_MALLOC(strlen(src->parsed_pattern) + 1, char *);
1242 if (!dst->parsed_pattern) {
1243 // Clean up partial save
1244 for (int j = 0; j < i; j++) {
1245 SAFE_FREE(g_filter_state.saved_patterns[j].parsed_pattern);
1246 }
1247 SAFE_FREE(g_filter_state.saved_patterns);
1248 g_filter_state.saved_patterns = NULL;
1249 return SET_ERRNO(ERROR_MEMORY, "Failed to save pattern string");
1250 }
1251 memcpy(dst->parsed_pattern, src->parsed_pattern, strlen(src->parsed_pattern) + 1);
1252
1253 // Singleton pointers are shared (reference counted)
1254 dst->singleton = src->singleton;
1255 }
1256 }
1257
1258 g_filter_state.saved_pattern_count = g_filter_state.pattern_count;
1259 g_filter_state.saved_pattern_capacity = g_filter_state.pattern_count;
1260 g_filter_state.saved_enabled = g_filter_state.enabled;
1261
1262 return ASCIICHAT_OK;
1263}

References ASCIICHAT_OK, ERROR_MEMORY, grep_pattern_t::parsed_pattern, SAFE_FREE, SAFE_MALLOC, SET_ERRNO, and grep_pattern_t::singleton.

Referenced by log_search_enter_mode().

◆ grep_should_output()

bool grep_should_output ( const char *  log_line,
size_t *  match_start,
size_t *  match_len 
)

Check if log line should be output (matches filter)

Thread-safe: Uses thread-local match_data storage. Non-allocating: Reuses pre-allocated match_data.

Parameters
log_lineFormatted log line to check
match_startOutput: start offset of match
match_lenOutput: length of match
Returns
true if line should be output, false to suppress

Definition at line 597 of file grep.c.

597 {
598 // Reject NULL lines
599 if (!log_line) {
600 if (match_start)
601 *match_start = 0;
602 if (match_len)
603 *match_len = 0;
604 return false;
605 }
606
607 // If filtering disabled, output everything
608 if (!g_filter_state.enabled) {
609 return true;
610 }
611
612 // Check if we're in "context after" mode (outputting lines after a match)
613 if (g_filter_state.lines_after_match > 0) {
614 g_filter_state.lines_after_match--;
615 *match_start = 0;
616 *match_len = 0;
617
618 // Store in buffer even during context-after mode
619 if (g_filter_state.buffer_size > 0) {
620 SAFE_FREE(g_filter_state.line_buffer[g_filter_state.buffer_pos]);
621 size_t line_len = strlen(log_line) + 1;
622 g_filter_state.line_buffer[g_filter_state.buffer_pos] = SAFE_MALLOC(line_len, char *);
623 if (g_filter_state.line_buffer[g_filter_state.buffer_pos]) {
624 memcpy(g_filter_state.line_buffer[g_filter_state.buffer_pos], log_line, line_len);
625 }
626 g_filter_state.buffer_pos = (g_filter_state.buffer_pos + 1) % g_filter_state.buffer_size;
627 }
628
629 return true; // Output context line
630 }
631
632 pcre2_match_data *match_data = get_thread_match_data();
633 size_t line_len = strlen(log_line);
634
635 // Try each pattern (OR logic)
636 bool any_match = false;
637 grep_pattern_t *matched_pattern = NULL;
638
639 for (int i = 0; i < g_filter_state.pattern_count; i++) {
640 grep_pattern_t *pat = &g_filter_state.patterns[i];
641 bool this_match = false;
642
643 if (pat->is_fixed_string) {
644 // Fixed string matching (with optional case-insensitive support)
645 const char *found;
646 if (pat->case_insensitive) {
647 // Unicode-aware case-insensitive search
648 found = utf8_strcasestr(log_line, pat->parsed_pattern);
649 } else {
650 // Case-sensitive search
651 found = strstr(log_line, pat->parsed_pattern);
652 }
653
654 if (found) {
655 this_match = true;
656 if (!matched_pattern) {
657 *match_start = (size_t)(found - log_line);
658 *match_len = strlen(pat->parsed_pattern);
659 matched_pattern = pat;
660 }
661 }
662 } else {
663 // Regex matching
664 if (!match_data || !pat->singleton) {
665 continue;
666 }
667
668 pcre2_code *code = asciichat_pcre2_singleton_get_code(pat->singleton);
669 if (!code) {
670 continue;
671 }
672
673 int rc = pcre2_jit_match(code, (PCRE2_SPTR)log_line, line_len, 0, 0, match_data, NULL);
674 if (rc >= 0) {
675 this_match = true;
676 if (!matched_pattern) {
677 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
678 *match_start = (size_t)ovector[0];
679 *match_len = (size_t)(ovector[1] - ovector[0]);
680 matched_pattern = pat;
681 }
682 }
683 }
684
685 // Apply invert flag
686 if (pat->invert) {
687 this_match = !this_match;
688 }
689
690 if (this_match) {
691 any_match = true;
692 // Update matched_pattern if this one has more context
693 if (!matched_pattern || (pat->context_before + pat->context_after) >
694 (matched_pattern->context_before + matched_pattern->context_after)) {
695 matched_pattern = pat;
696 }
697 }
698 }
699
700 // If any pattern matched
701 if (any_match && matched_pattern) {
702 // Output context_before lines from circular buffer
703 if (matched_pattern->context_before > 0 && g_filter_state.buffer_size > 0) {
704 int num_lines = (matched_pattern->context_before < g_filter_state.buffer_size) ? matched_pattern->context_before
705 : g_filter_state.buffer_size;
706
707 // Calculate starting position in circular buffer
708 int start_pos = (g_filter_state.buffer_pos - num_lines + g_filter_state.buffer_size) % g_filter_state.buffer_size;
709
710 // Output buffered lines
711 for (int i = 0; i < num_lines; i++) {
712 int pos = (start_pos + i) % g_filter_state.buffer_size;
713 if (g_filter_state.line_buffer[pos]) {
714 fprintf(stderr, "%s\n", g_filter_state.line_buffer[pos]);
715 }
716 }
717 }
718
719 // Set up context_after counter
720 if (matched_pattern->context_after > 0) {
721 g_filter_state.lines_after_match = matched_pattern->context_after;
722 }
723
724 return true; // Match found, output line
725 }
726
727 // No match - store in context buffer for potential future use
728 if (g_filter_state.buffer_size > 0) {
729 SAFE_FREE(g_filter_state.line_buffer[g_filter_state.buffer_pos]);
730 size_t line_len_copy = strlen(log_line) + 1;
731 g_filter_state.line_buffer[g_filter_state.buffer_pos] = SAFE_MALLOC(line_len_copy, char *);
732 if (g_filter_state.line_buffer[g_filter_state.buffer_pos]) {
733 memcpy(g_filter_state.line_buffer[g_filter_state.buffer_pos], log_line, line_len_copy);
734 }
735 g_filter_state.buffer_pos = (g_filter_state.buffer_pos + 1) % g_filter_state.buffer_size;
736 }
737
738 return false; // No match, suppress line
739}
int buffer_size
Size of circular buffer.
Definition grep.c:90

References asciichat_pcre2_singleton_get_code(), grep_pattern_t::case_insensitive, grep_pattern_t::context_after, grep_pattern_t::context_before, grep_pattern_t::invert, grep_pattern_t::is_fixed_string, grep_pattern_t::parsed_pattern, SAFE_FREE, SAFE_MALLOC, grep_pattern_t::singleton, and utf8_strcasestr().