ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
grep.c File Reference

Go to the source code of this file.

Data Structures

struct  grep_pattern_t
 Single filter pattern with all its settings. More...
 

Macros

#define HIGHLIGHT_DARK_BG   70
 Default highlight colors (grey) Dark grey (70) for dark backgrounds, light grey (200) for light backgrounds.
 
#define HIGHLIGHT_LIGHT_BG   200
 
#define MIN_HIGHLIGHT_DISTANCE   40
 Minimum color difference threshold (0-255 scale) If background is within this distance of the highlight, use black/white instead.
 

Typedefs

typedef grep_parse_result_t parse_result_t
 

Functions

grep_parse_result_t grep_parse_pattern (const char *input)
 Parse pattern in /pattern/flags or pattern/flags format.
 
asciichat_error_t grep_init (const char *pattern)
 
bool grep_should_output (const char *log_line, size_t *match_start, size_t *match_len)
 
char * grep_highlight_colored_copy (const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
 
const char * grep_highlight_colored (const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
 
const char * grep_highlight (const char *log_line, size_t match_start, size_t match_len)
 
void grep_destroy (void)
 
asciichat_error_t grep_save_patterns (void)
 
asciichat_error_t grep_restore_patterns (void)
 
void grep_clear_patterns (void)
 
int grep_get_pattern_count (void)
 
const char * grep_get_last_pattern (void)
 

Macro Definition Documentation

◆ HIGHLIGHT_DARK_BG

#define HIGHLIGHT_DARK_BG   70

Default highlight colors (grey) Dark grey (70) for dark backgrounds, light grey (200) for light backgrounds.

Definition at line 39 of file grep.c.

◆ HIGHLIGHT_LIGHT_BG

#define HIGHLIGHT_LIGHT_BG   200

Definition at line 40 of file grep.c.

◆ MIN_HIGHLIGHT_DISTANCE

#define MIN_HIGHLIGHT_DISTANCE   40

Minimum color difference threshold (0-255 scale) If background is within this distance of the highlight, use black/white instead.

Definition at line 46 of file grep.c.

Typedef Documentation

◆ parse_result_t

typedef grep_parse_result_t parse_result_t

Definition at line 371 of file grep.c.

Function Documentation

◆ grep_clear_patterns()

void grep_clear_patterns ( void  )

Definition at line 1378 of file grep.c.

1378 {
1379 // Don't free patterns, just set count to 0
1380 // This allows quick restore without reallocation
1381 g_filter_state.pattern_count = 0;
1382 g_filter_state.enabled = false;
1383}

◆ grep_destroy()

void grep_destroy ( void  )

Definition at line 1240 of file grep.c.

1240 {
1241 // Free all patterns
1242 for (int i = 0; i < g_filter_state.pattern_count; i++) {
1243 grep_pattern_t *pat = &g_filter_state.patterns[i];
1244 // Free the copied original string (need temp variable due to const qualifier)
1245 char *original_to_free = (char *)pat->original;
1246 SAFE_FREE(original_to_free);
1247 SAFE_FREE(pat->parsed_pattern);
1248 // Singletons are auto-cleaned by asciichat_pcre2_cleanup_all()
1249 pat->singleton = NULL;
1250 }
1251 SAFE_FREE(g_filter_state.patterns);
1252 g_filter_state.patterns = NULL; // Make idempotent
1253
1254 // Free context buffer
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]);
1258 }
1259 SAFE_FREE(g_filter_state.line_buffer);
1260 g_filter_state.line_buffer = NULL; // Make idempotent
1261 }
1262
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;
1270
1271 // Note: Thread-local match_data is cleaned up via pthread_key destructor
1272}
Single filter pattern with all its settings.
Definition grep.c:59
const char * original
Original pattern string (for display)
Definition grep.c:60

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

Referenced by log_destroy().

◆ grep_get_last_pattern()

const char * grep_get_last_pattern ( void  )

Definition at line 1389 of file grep.c.

1389 {
1390 if (g_filter_state.pattern_count == 0) {
1391 return NULL;
1392 }
1393
1394 // Return the original pattern string of the last pattern
1395 return g_filter_state.patterns[g_filter_state.pattern_count - 1].original;
1396}

Referenced by interactive_grep_enter_mode(), interactive_grep_init(), server_status_display(), and server_status_display_interactive().

◆ grep_get_pattern_count()

int grep_get_pattern_count ( void  )

Definition at line 1385 of file grep.c.

1385 {
1386 return g_filter_state.pattern_count;
1387}

◆ grep_highlight()

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

Definition at line 1131 of file grep.c.

1131 {
1132 static __thread char highlight_buffer[16384];
1133
1134 if (!log_line || match_len == 0) {
1135 return log_line; // No highlighting needed
1136 }
1137
1138 size_t line_len = strlen(log_line);
1139 if (match_start + match_len > line_len) {
1140 return log_line; // Invalid range
1141 }
1142
1143 char *dst = highlight_buffer;
1144 const char *src = log_line;
1145 size_t pos = 0;
1146
1147 // If any pattern has global flag, highlight ALL matches
1148 grep_pattern_t *global_pat = NULL;
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];
1152 break;
1153 }
1154 }
1155
1156 if (global_pat && global_pat->singleton) {
1157 pcre2_code *code = asciichat_pcre2_singleton_get_code(global_pat->singleton);
1158 pcre2_match_data *match_data = get_thread_match_data();
1159
1160 if (!code || !match_data) {
1161 return log_line; // Fall back to no highlighting
1162 }
1163
1164 // Find all matches in the line
1165 size_t offset = 0;
1166 while (offset < line_len) {
1167 int rc = pcre2_jit_match(code, (PCRE2_SPTR)log_line, line_len, offset, 0, match_data, NULL);
1168 if (rc < 0) {
1169 break; // No more matches
1170 }
1171
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;
1176
1177 // Copy text before match
1178 if (match_pos > pos) {
1179 memcpy(dst, src + pos, match_pos - pos);
1180 dst += (match_pos - pos);
1181 }
1182
1183 // Highlight the match (background only, preserve foreground color)
1184 uint8_t r, g, b;
1185 get_highlight_color(&r, &g, &b);
1186 dst = append_truecolor_bg(dst, r, g, b);
1187 memcpy(dst, src + match_pos, len);
1188 dst += len;
1189 memcpy(dst, "\x1b[0m", 4);
1190 dst += 4;
1191
1192 pos = match_end;
1193 offset = match_end;
1194
1195 // Prevent infinite loop on zero-length matches
1196 if (len == 0) {
1197 offset++;
1198 }
1199 }
1200
1201 // Copy remaining text after last match
1202 if (pos < line_len) {
1203 memcpy(dst, src + pos, line_len - pos);
1204 dst += (line_len - pos);
1205 }
1206
1207 } else {
1208 // Single match highlighting (original behavior)
1209 // Copy text before match
1210 if (match_start > 0) {
1211 memcpy(dst, log_line, match_start);
1212 dst += match_start;
1213 }
1214
1215 // Add background color for match (preserve foreground color)
1216 uint8_t r, g, b;
1217 get_highlight_color(&r, &g, &b);
1218 dst = append_truecolor_bg(dst, r, g, b);
1219
1220 // Copy matched text
1221 memcpy(dst, log_line + match_start, match_len);
1222 dst += match_len;
1223
1224 // Reset color
1225 memcpy(dst, "\x1b[0m", 4);
1226 dst += 4;
1227
1228 // Copy text after match
1229 size_t remaining = line_len - (match_start + match_len);
1230 if (remaining > 0) {
1231 memcpy(dst, log_line + match_start + match_len, remaining);
1232 dst += remaining;
1233 }
1234 }
1235
1236 *dst = '\0';
1237 return highlight_buffer;
1238}
char * append_truecolor_bg(char *dst, uint8_t r, uint8_t g, uint8_t b)
Definition ansi_fast.c:56
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:95
pcre2_singleton_t * singleton
PCRE2 singleton (NULL if fixed string)
Definition grep.c:62

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 
)

Definition at line 806 of file grep.c.

807 {
808 static __thread char highlight_buffer[16384];
809
810 if (!colored_text || !plain_text || match_len == 0) {
811 return colored_text;
812 }
813
814 // When interactive grep is active, check if it has global flag
815 bool is_interactive_grep = interactive_grep_is_active();
816 bool should_use_global_pattern = false;
817 grep_pattern_t *global_pat = NULL;
818
819 if (!is_interactive_grep) {
820 // If any pattern has global flag, highlight ALL matches for that pattern
821 // Find first pattern with global flag
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;
826 break;
827 }
828 }
830 // Interactive grep is active and has /g flag - do global matching on the provided pattern
831 // We'll do all-match highlighting using the match position to extract the pattern
832 // and re-match for all occurrences
833 should_use_global_pattern = true;
834 // global_pat stays NULL, but we set the flag to enable global matching below
835 }
836
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;
843
844 // Get code from CLI pattern or interactive grep pattern
845 if (global_pat && global_pat->singleton) {
847 match_data = get_thread_match_data();
848 } else if (is_interactive_grep) {
849 // Interactive grep global highlighting - get pattern from grep state
850 void *grep_singleton_void = interactive_grep_get_pattern_singleton();
851 if (grep_singleton_void) {
852 pcre2_singleton_t *grep_singleton = (pcre2_singleton_t *)grep_singleton_void;
853 code = asciichat_pcre2_singleton_get_code(grep_singleton);
854 // Create match data for this pattern
855 if (code) {
856 match_data = pcre2_match_data_create_from_pattern(code, NULL);
857 }
858 } else {
859 // No regex pattern - check if it's a fixed string pattern in interactive grep
860 int pattern_len = interactive_grep_get_input_len();
861 const char *pattern = interactive_grep_get_input_buffer();
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;
865 case_insensitive_fixed = interactive_grep_get_case_insensitive();
866 }
867 }
868 }
869
870 if (!code && !is_fixed_string_pattern) {
871 if (match_data && is_interactive_grep) {
872 pcre2_match_data_free(match_data); // Free if we created it
873 }
874 return colored_text; // Fall back to no highlighting
875 }
876
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;
882
883 // Handle fixed string global matching
884 if (is_fixed_string_pattern) {
885 while (plain_offset < plain_len) {
886 // Find next occurrence of fixed string
887 const char *found = NULL;
888 if (case_insensitive_fixed) {
889 found = utf8_strcasestr(plain_text + plain_offset, fixed_string_pattern);
890 } else {
891 found = strstr(plain_text + plain_offset, fixed_string_pattern);
892 }
893
894 if (!found) {
895 break; // No more matches
896 }
897
898 size_t plain_match_start = (size_t)(found - plain_text);
899 size_t plain_match_end = plain_match_start + strlen(fixed_string_pattern);
900
901 // Map to colored text positions (same as single-match path)
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);
904
905 // Copy text before match
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);
909 }
910
911 // Add highlight background
912 uint8_t r, g, b;
913 get_highlight_color(&r, &g, &b);
914 dst = append_truecolor_bg(dst, r, g, b);
915
916 // Copy matched text with background
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;
920
921 // Reset background
922 memcpy(dst, "\x1b[49m", 5);
923 dst += 5;
924
925 colored_pos = colored_match_end;
926 plain_offset = plain_match_end;
927 }
928
929 // Copy remaining text
930 if (colored_pos < colored_len) {
931 memcpy(dst, colored_text + colored_pos, colored_len - colored_pos);
932 dst += (colored_len - colored_pos);
933 }
934
935 // Reset both background and foreground at end
936 memcpy(dst, "\x1b[0m", 4);
937 dst += 4;
938
939 *dst = '\0';
940 return highlight_buffer;
941 }
942
943 // Find all matches in plain text and highlight in colored text (regex matching)
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);
946 if (rc < 0) {
947 break; // No more matches
948 }
949
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];
953
954 // Map to colored text positions
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);
957
958 // Copy text before match
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);
962 }
963
964 // Add highlight background
965 uint8_t r, g, b;
966 get_highlight_color(&r, &g, &b);
967 dst = append_truecolor_bg(dst, r, g, b);
968
969 // Copy matched text, re-applying background after any [0m or [00m reset codes
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; // Absolute buffer end
973 size_t i = 0;
974 while (i < match_byte_len) {
975 // Check for [0m or [00m reset codes
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') {
978 // Found [0m - copy it and re-apply background (if room)
979 if (dst + 4 >= dst_end)
980 break; // Not enough space for the reset code itself
981 *dst++ = match_src[i++]; // ESC
982 *dst++ = match_src[i++]; // '['
983 *dst++ = match_src[i++]; // '0'
984 *dst++ = match_src[i++]; // 'm'
985 // Re-apply highlight background after reset (only if room)
986 if (dst + 20 < dst_end) {
987 uint8_t r2, g2, b2;
988 get_highlight_color(&r2, &g2, &b2);
989 dst = append_truecolor_bg(dst, r2, g2, b2);
990 }
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') {
993 // Found [00m - copy it and re-apply background (if room)
994 if (dst + 5 >= dst_end)
995 break; // Not enough space for the reset code itself
996 *dst++ = match_src[i++]; // ESC
997 *dst++ = match_src[i++]; // '['
998 *dst++ = match_src[i++]; // '0'
999 *dst++ = match_src[i++]; // '0'
1000 *dst++ = match_src[i++]; // 'm'
1001 // Re-apply highlight background after reset (only if room)
1002 if (dst + 20 < dst_end) {
1003 uint8_t r2, g2, b2;
1004 get_highlight_color(&r2, &g2, &b2);
1005 dst = append_truecolor_bg(dst, r2, g2, b2);
1006 }
1007 } else {
1008 // Regular character - just copy
1009 if (dst + 1 >= dst_end)
1010 break;
1011 *dst++ = match_src[i++];
1012 }
1013 }
1014
1015 // Reset background only
1016 memcpy(dst, "\x1b[49m", 5);
1017 dst += 5;
1018
1019 colored_pos = colored_match_end;
1020 plain_offset = plain_match_end;
1021
1022 // Prevent infinite loop on zero-length matches
1023 if (plain_match_end == plain_match_start) {
1024 plain_offset++;
1025 }
1026 }
1027
1028 // Copy remaining text
1029 if (colored_pos < colored_len) {
1030 memcpy(dst, colored_text + colored_pos, colored_len - colored_pos);
1031 dst += (colored_len - colored_pos);
1032 }
1033
1034 // Reset both background and foreground at end to prevent color bleeding
1035 memcpy(dst, "\x1b[0m", 4);
1036 dst += 4;
1037
1038 *dst = '\0';
1039
1040 // Clean up match data if we created it for interactive grep
1041 if (is_interactive_grep && match_data && !global_pat) {
1042 pcre2_match_data_free(match_data);
1043 }
1044
1045 return highlight_buffer;
1046 }
1047
1048 // Single match highlighting (same logic as global path)
1049 // map_plain_to_colored_pos(N) returns byte position after counting N characters
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);
1052
1053 size_t colored_len = strlen(colored_text);
1054 char *dst = highlight_buffer;
1055
1056 // Copy everything before the match
1057 if (colored_start > 0) {
1058 memcpy(dst, colored_text, colored_start);
1059 dst += colored_start;
1060 }
1061
1062 // Add highlight background
1063 uint8_t r, g, b;
1064 get_highlight_color(&r, &g, &b);
1065 dst = append_truecolor_bg(dst, r, g, b);
1066
1067 // Copy matched text, re-applying background after any [0m or [00m reset codes
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; // Absolute buffer end
1071 size_t i = 0;
1072
1073 while (i < match_byte_len) {
1074 // Check for [0m or [00m reset codes
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') {
1077 // Found [0m - copy it and re-apply background (if room)
1078 if (dst + 4 >= dst_end)
1079 break; // Not enough space for even the reset code
1080 *dst++ = match_src[i++]; // ESC
1081 *dst++ = match_src[i++]; // '['
1082 *dst++ = match_src[i++]; // '0'
1083 *dst++ = match_src[i++]; // 'm'
1084 // Re-apply highlight background after reset (only if room)
1085 if (dst + 20 < dst_end) {
1086 uint8_t r2, g2, b2;
1087 get_highlight_color(&r2, &g2, &b2);
1088 dst = append_truecolor_bg(dst, r2, g2, b2);
1089 }
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') {
1092 // Found [00m - copy it and re-apply background (if room)
1093 if (dst + 5 >= dst_end)
1094 break; // Not enough space for even the reset code
1095 *dst++ = match_src[i++]; // ESC
1096 *dst++ = match_src[i++]; // '['
1097 *dst++ = match_src[i++]; // '0'
1098 *dst++ = match_src[i++]; // '0'
1099 *dst++ = match_src[i++]; // 'm'
1100 // Re-apply highlight background after reset (only if room)
1101 if (dst + 20 < dst_end) {
1102 uint8_t r3, g3, b3;
1103 get_highlight_color(&r3, &g3, &b3);
1104 dst = append_truecolor_bg(dst, r3, g3, b3);
1105 }
1106 } else {
1107 // Regular character - just copy
1108 if (dst + 1 >= dst_end) {
1109 break;
1110 }
1111 *dst++ = match_src[i++];
1112 }
1113 }
1114
1115 // Reset only the background color to preserve foreground colors from headers
1116 // Using \x1b[49m (reset background only) instead of \x1b[0m (reset all)
1117 memcpy(dst, "\x1b[49m", 5);
1118 dst += 5;
1119
1120 // Copy remaining text
1121 size_t remaining = colored_len - colored_end;
1122 if (remaining > 0) {
1123 memcpy(dst, colored_text + colored_end, remaining);
1124 dst += remaining;
1125 }
1126
1127 *dst = '\0';
1128 return highlight_buffer;
1129}
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)
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:21
const char * utf8_strcasestr(const char *haystack, const char *needle)
Case-insensitive substring search with full Unicode support.
Definition utf8.c:274

References append_truecolor_bg(), asciichat_pcre2_singleton_get_code(), interactive_grep_get_case_insensitive(), interactive_grep_get_global_highlight(), interactive_grep_get_input_buffer(), interactive_grep_get_input_len(), interactive_grep_get_pattern_singleton(), interactive_grep_is_active(), 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 
)

Definition at line 792 of file grep.c.

793 {
794 const char *result = grep_highlight_colored(colored_text, plain_text, match_start, match_len);
795 if (result) {
796 size_t result_len = strlen(result) + 1;
797 char *copy = SAFE_MALLOC(result_len, char *);
798 if (copy) {
799 memcpy(copy, result, result_len);
800 return copy;
801 }
802 }
803 return NULL;
804}
const char * grep_highlight_colored(const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
Definition grep.c:806

References grep_highlight_colored().

◆ grep_init()

asciichat_error_t grep_init ( const char *  pattern)

Definition at line 549 of file grep.c.

549 {
550 // Reject NULL or empty patterns
551 if (!pattern) {
552 return SET_ERRNO(ERROR_INVALID_PARAM, "Pattern cannot be NULL");
553 }
554 if (strlen(pattern) == 0) {
555 return SET_ERRNO(ERROR_INVALID_PARAM, "Pattern cannot be empty");
556 }
557
558 // Parse pattern
559 parse_result_t parsed = grep_parse_pattern(pattern);
560 if (!parsed.valid) {
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");
564 }
565
566 // Allocate or expand pattern array
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;
569 grep_pattern_t *new_patterns =
570 SAFE_REALLOC(g_filter_state.patterns, new_capacity * sizeof(grep_pattern_t), grep_pattern_t *);
571 if (!new_patterns) {
572 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate pattern array");
573 }
574 g_filter_state.patterns = new_patterns;
575 g_filter_state.pattern_capacity = new_capacity;
576 }
577
578 // Add new pattern
579 grep_pattern_t *new_pat = &g_filter_state.patterns[g_filter_state.pattern_count];
580 memset(new_pat, 0, sizeof(*new_pat));
581
582 // Make a copy of the original pattern string (argv pointers may be deallocated)
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");
586 }
587 memcpy(original_copy, pattern, strlen(pattern) + 1);
588 new_pat->original = original_copy;
589
590 new_pat->parsed_pattern = SAFE_MALLOC(strlen(parsed.pattern) + 1, char *);
591 if (!new_pat->parsed_pattern) {
592 SAFE_FREE(original_copy);
593 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate pattern string");
594 }
595 memcpy(new_pat->parsed_pattern, parsed.pattern, strlen(parsed.pattern) + 1);
596
597 new_pat->is_fixed_string = parsed.is_fixed_string;
598 new_pat->case_insensitive = parsed.case_insensitive;
599 new_pat->invert = parsed.invert;
600 new_pat->global_flag = parsed.global_flag;
601 new_pat->context_before = parsed.context_before;
602 new_pat->context_after = parsed.context_after;
603
604 // Compile regex (skip for fixed strings)
605 if (!parsed.is_fixed_string) {
606 new_pat->singleton = asciichat_pcre2_singleton_compile(parsed.pattern, parsed.pcre2_options);
607 if (!new_pat->singleton) {
608 SAFE_FREE(new_pat->parsed_pattern);
609 return SET_ERRNO(ERROR_INVALID_PARAM, "Failed to compile regex pattern");
610 }
611
612 // Validate compilation
613 pcre2_code *code = asciichat_pcre2_singleton_get_code(new_pat->singleton);
614 if (!code) {
615 SAFE_FREE(new_pat->parsed_pattern);
616 return SET_ERRNO(ERROR_INVALID_PARAM, "Regex compilation failed");
617 }
618 }
619
620 g_filter_state.pattern_count++;
621
622 // Update context buffer size if needed
623 if (parsed.context_before > g_filter_state.buffer_size) {
624 // Reallocate line buffer
625 char **new_buffer = SAFE_REALLOC(g_filter_state.line_buffer, parsed.context_before * sizeof(char *), char **);
626 if (!new_buffer) {
627 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate context line buffer");
628 }
629 // Initialize new slots
630 for (int i = g_filter_state.buffer_size; i < parsed.context_before; i++) {
631 new_buffer[i] = NULL;
632 }
633 g_filter_state.line_buffer = new_buffer;
634 g_filter_state.buffer_size = parsed.context_before;
635 }
636
637 // Update max context_after
638 if (parsed.context_after > g_filter_state.max_context_after) {
639 g_filter_state.max_context_after = parsed.context_after;
640 }
641
642 g_filter_state.enabled = true;
643 log_info("Added --grep pattern #%d: %s", g_filter_state.pattern_count, pattern);
644
645 return ASCIICHAT_OK;
646}
grep_parse_result_t grep_parse_pattern(const char *input)
Parse pattern in /pattern/flags or pattern/flags format.
Definition grep.c:414
grep_parse_result_t parse_result_t
Definition grep.c:371
int pattern_capacity
Allocated capacity.
Definition grep.c:77
int context_before
Lines before match (B flag)
Definition grep.c:67
int context_after
Lines after match (A flag)
Definition grep.c:68
bool case_insensitive
Case-insensitive matching (i flag)
Definition grep.c:64
bool global_flag
Highlight all matches (g flag)
Definition grep.c:66
bool is_fixed_string
True for fixed string matching (no regex)
Definition grep.c:63
bool invert
Invert match (I flag)
Definition grep.c:65
char * parsed_pattern
Parsed pattern (without delimiters/flags)
Definition grep.c:61

References asciichat_pcre2_singleton_get_code(), grep_pattern_t::case_insensitive, grep_pattern_t::context_after, grep_pattern_t::context_before, grep_pattern_t::global_flag, grep_parse_pattern(), grep_pattern_t::invert, grep_pattern_t::is_fixed_string, grep_pattern_t::original, grep_pattern_t::parsed_pattern, and grep_pattern_t::singleton.

Referenced by main().

◆ grep_parse_pattern()

grep_parse_result_t grep_parse_pattern ( const char *  input)

Parse pattern in /pattern/flags or pattern/flags 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 414 of file grep.c.

414 {
415 parse_result_t result = {0};
416 result.pcre2_options = PCRE2_UTF | PCRE2_UCP; // Default: UTF-8 mode
417
418 if (!input || strlen(input) == 0) {
419 return result; // Invalid: empty pattern
420 }
421
422 size_t len = strlen(input);
423 const char *closing_slash = NULL;
424 const char *pattern_start = input;
425
426 // Check if pattern uses /pattern/flags format (explicit)
427 if (input[0] == '/') {
428 // Format 1: /pattern/flags
429 if (len < 3) {
430 return result; // Invalid: too short for /pattern/ format
431 }
432
433 // Find closing slash - don't treat backslash as an escape character
434 // (backslashes are regex escapes, not delimiter escapes)
435 closing_slash = strchr(input + 1, '/');
436 if (!closing_slash) {
437 return result; // Invalid: missing closing /
438 }
439
440 pattern_start = input + 1;
441 } else {
442 // Check for pattern/flags format (implicit, no leading slash)
443 // Find the last forward slash to use as delimiter
444 closing_slash = strrchr(input, '/');
445
446 if (closing_slash && closing_slash > input) {
447 // There's at least one slash (not at the beginning)
448 // Try to parse as pattern/flags format
449 pattern_start = input;
450 } else {
451 // No slash or slash at beginning - treat as plain pattern
452 closing_slash = NULL;
453 }
454 }
455
456 if (closing_slash) {
457 // Extract pattern between start and closing slash
458 size_t pattern_len = (size_t)(closing_slash - pattern_start);
459 if (pattern_len == 0) {
460 return result; // Invalid: empty pattern
461 }
462 if (pattern_len >= sizeof(result.pattern)) {
463 pattern_len = sizeof(result.pattern) - 1;
464 }
465 memcpy(result.pattern, pattern_start, pattern_len);
466 result.pattern[pattern_len] = '\0';
467
468 // Parse flags after closing slash
469 // First pass: check for F flag
470 const char *flags = closing_slash + 1;
471 bool has_F_flag = (strchr(flags, 'F') != NULL);
472
473 // Parse all flags
474 for (const char *p = flags; *p; p++) {
475 char c = *p;
476
477 // Single-character flags
478 if (c == 'i') {
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;
493 }
494 // Multi-character flags with integers
495 else if (c == 'A') {
496 p++; // Move to digits
497 int num = 0;
498 while (*p >= '0' && *p <= '9') {
499 num = num * 10 + (*p - '0');
500 p++;
501 }
502 p--; // Back up one (for loop will increment)
503 result.context_after = (num > 0) ? num : 1; // Default to 1 if no number
504 } else if (c == 'B') {
505 p++;
506 int num = 0;
507 while (*p >= '0' && *p <= '9') {
508 num = num * 10 + (*p - '0');
509 p++;
510 }
511 p--;
512 result.context_before = (num > 0) ? num : 1;
513 } else if (c == 'C') {
514 p++;
515 int num = 0;
516 while (*p >= '0' && *p <= '9') {
517 num = num * 10 + (*p - '0');
518 p++;
519 }
520 p--;
521 int ctx = (num > 0) ? num : 1;
522 result.context_before = ctx;
523 result.context_after = ctx;
524 } else {
525 // Invalid flag character - only error if not using F flag
526 if (!has_F_flag) {
527 return result; // Invalid
528 }
529 // Otherwise ignore invalid flags (they're part of the fixed string context)
530 }
531 }
532 } else {
533 // Format 3: Plain pattern without slashes (treat as regex, no flags)
534 size_t pattern_len = len;
535 if (pattern_len >= sizeof(result.pattern)) {
536 pattern_len = sizeof(result.pattern) - 1;
537 }
538 memcpy(result.pattern, input, pattern_len);
539 result.pattern[pattern_len] = '\0';
540
541 // No flags for plain format - just use default options
542 // (PCRE2_UTF | PCRE2_UCP already set at the top)
543 }
544
545 result.valid = true;
546 return result;
547}

Referenced by grep_init(), interactive_grep_enter_mode(), interactive_grep_exit_mode(), interactive_grep_gather_and_filter_logs(), interactive_grep_get_match_info(), interactive_grep_handle_key(), and interactive_grep_init().

◆ grep_restore_patterns()

asciichat_error_t grep_restore_patterns ( void  )

Definition at line 1328 of file grep.c.

1328 {
1329 // Clear current patterns (but don't free the array)
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;
1333 }
1334
1335 // Ensure we have capacity for saved patterns
1336 if (g_filter_state.saved_pattern_count > g_filter_state.pattern_capacity) {
1337 grep_pattern_t *new_patterns = SAFE_REALLOC(
1338 g_filter_state.patterns, g_filter_state.saved_pattern_count * sizeof(grep_pattern_t), grep_pattern_t *);
1339 if (!new_patterns) {
1340 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate pattern array for restore");
1341 }
1342 g_filter_state.patterns = new_patterns;
1343 g_filter_state.pattern_capacity = g_filter_state.saved_pattern_count;
1344 }
1345
1346 // Restore patterns
1347 if (g_filter_state.saved_patterns) {
1348 for (int i = 0; i < g_filter_state.saved_pattern_count; i++) {
1349 grep_pattern_t *src = &g_filter_state.saved_patterns[i];
1350 grep_pattern_t *dst = &g_filter_state.patterns[i];
1351
1352 // Copy pattern structure
1353 *dst = *src;
1354
1355 // Deep copy parsed_pattern string
1356 dst->parsed_pattern = SAFE_MALLOC(strlen(src->parsed_pattern) + 1, char *);
1357 if (!dst->parsed_pattern) {
1358 // Clean up partial restore
1359 for (int j = 0; j < i; j++) {
1360 SAFE_FREE(g_filter_state.patterns[j].parsed_pattern);
1361 }
1362 g_filter_state.pattern_count = 0;
1363 return SET_ERRNO(ERROR_MEMORY, "Failed to restore pattern string");
1364 }
1365 memcpy(dst->parsed_pattern, src->parsed_pattern, strlen(src->parsed_pattern) + 1);
1366
1367 // Singleton pointers are shared (reference counted)
1368 dst->singleton = src->singleton;
1369 }
1370 }
1371
1372 g_filter_state.pattern_count = g_filter_state.saved_pattern_count;
1373 g_filter_state.enabled = g_filter_state.saved_enabled;
1374
1375 return ASCIICHAT_OK;
1376}

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

Referenced by interactive_grep_exit_mode().

◆ grep_save_patterns()

asciichat_error_t grep_save_patterns ( void  )

Definition at line 1278 of file grep.c.

1278 {
1279 // Free previous saved patterns if any
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);
1283 }
1284 SAFE_FREE(g_filter_state.saved_patterns);
1285 }
1286
1287 // Allocate saved pattern array
1288 if (g_filter_state.pattern_count > 0) {
1289 g_filter_state.saved_patterns =
1290 SAFE_MALLOC(g_filter_state.pattern_count * sizeof(grep_pattern_t), grep_pattern_t *);
1291 if (!g_filter_state.saved_patterns) {
1292 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate saved pattern array");
1293 }
1294
1295 // Deep copy all patterns
1296 for (int i = 0; i < g_filter_state.pattern_count; i++) {
1297 grep_pattern_t *src = &g_filter_state.patterns[i];
1298 grep_pattern_t *dst = &g_filter_state.saved_patterns[i];
1299
1300 // Copy pattern structure
1301 *dst = *src;
1302
1303 // Deep copy parsed_pattern string
1304 dst->parsed_pattern = SAFE_MALLOC(strlen(src->parsed_pattern) + 1, char *);
1305 if (!dst->parsed_pattern) {
1306 // Clean up partial save
1307 for (int j = 0; j < i; j++) {
1308 SAFE_FREE(g_filter_state.saved_patterns[j].parsed_pattern);
1309 }
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");
1313 }
1314 memcpy(dst->parsed_pattern, src->parsed_pattern, strlen(src->parsed_pattern) + 1);
1315
1316 // Singleton pointers are shared (reference counted)
1317 dst->singleton = src->singleton;
1318 }
1319 }
1320
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;
1324
1325 return ASCIICHAT_OK;
1326}

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

Referenced by interactive_grep_enter_mode().

◆ grep_should_output()

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

Definition at line 648 of file grep.c.

648 {
649 // Reject NULL lines
650 if (!log_line) {
651 if (match_start)
652 *match_start = 0;
653 if (match_len)
654 *match_len = 0;
655 return false;
656 }
657
658 // If filtering disabled, output everything
659 if (!g_filter_state.enabled) {
660 return true;
661 }
662
663 // Check if we're in "context after" mode (outputting lines after a match)
664 if (g_filter_state.lines_after_match > 0) {
665 g_filter_state.lines_after_match--;
666 *match_start = 0;
667 *match_len = 0;
668
669 // Store in buffer even during context-after mode
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);
676 }
677 g_filter_state.buffer_pos = (g_filter_state.buffer_pos + 1) % g_filter_state.buffer_size;
678 }
679
680 return true; // Output context line
681 }
682
683 pcre2_match_data *match_data = get_thread_match_data();
684 size_t line_len = strlen(log_line);
685
686 // Try each pattern (OR logic)
687 bool any_match = false;
688 grep_pattern_t *matched_pattern = NULL;
689
690 for (int i = 0; i < g_filter_state.pattern_count; i++) {
691 grep_pattern_t *pat = &g_filter_state.patterns[i];
692 bool this_match = false;
693
694 if (pat->is_fixed_string) {
695 // Fixed string matching (with optional case-insensitive support)
696 const char *found;
697 if (pat->case_insensitive) {
698 // Unicode-aware case-insensitive search
699 found = utf8_strcasestr(log_line, pat->parsed_pattern);
700 } else {
701 // Case-sensitive search
702 found = strstr(log_line, pat->parsed_pattern);
703 }
704
705 if (found) {
706 this_match = true;
707 if (!matched_pattern) {
708 *match_start = (size_t)(found - log_line);
709 *match_len = strlen(pat->parsed_pattern);
710 matched_pattern = pat;
711 }
712 }
713 } else {
714 // Regex matching
715 if (!match_data || !pat->singleton) {
716 continue;
717 }
718
719 pcre2_code *code = asciichat_pcre2_singleton_get_code(pat->singleton);
720 if (!code) {
721 continue;
722 }
723
724 int rc = pcre2_jit_match(code, (PCRE2_SPTR)log_line, line_len, 0, 0, match_data, NULL);
725 if (rc >= 0) {
726 this_match = true;
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;
732 }
733 }
734 }
735
736 // Apply invert flag
737 if (pat->invert) {
738 this_match = !this_match;
739 }
740
741 if (this_match) {
742 any_match = true;
743 // Update matched_pattern if this one has more context
744 if (!matched_pattern || (pat->context_before + pat->context_after) >
745 (matched_pattern->context_before + matched_pattern->context_after)) {
746 matched_pattern = pat;
747 }
748 }
749 }
750
751 // If any pattern matched
752 if (any_match && matched_pattern) {
753 // Output context_before lines from circular buffer
754 if (matched_pattern->context_before > 0 && g_filter_state.buffer_size > 0) {
755 int num_lines = (matched_pattern->context_before < g_filter_state.buffer_size) ? matched_pattern->context_before
756 : g_filter_state.buffer_size;
757
758 // Calculate starting position in circular buffer
759 int start_pos = (g_filter_state.buffer_pos - num_lines + g_filter_state.buffer_size) % g_filter_state.buffer_size;
760
761 // Output buffered lines
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]);
766 }
767 }
768 }
769
770 // Set up context_after counter
771 if (matched_pattern->context_after > 0) {
772 g_filter_state.lines_after_match = matched_pattern->context_after;
773 }
774
775 return true; // Match found, output line
776 }
777
778 // No match - store in context buffer for potential future use
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);
785 }
786 g_filter_state.buffer_pos = (g_filter_state.buffer_pos + 1) % g_filter_state.buffer_size;
787 }
788
789 return false; // No match, suppress line
790}
int buffer_size
Size of circular buffer.
Definition grep.c:84

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, grep_pattern_t::singleton, and utf8_strcasestr().

Variable Documentation

◆ buffer_pos

int buffer_pos

Current position in buffer.

Definition at line 85 of file grep.c.

◆ buffer_size

◆ cached_highlight_b

uint8_t cached_highlight_b

Definition at line 96 of file grep.c.

◆ cached_highlight_g

uint8_t cached_highlight_g

Definition at line 96 of file grep.c.

◆ cached_highlight_r

uint8_t cached_highlight_r

Definition at line 96 of file grep.c.

◆ enabled

bool enabled

Is filtering active?

Definition at line 78 of file grep.c.

Referenced by log_set_force_stderr(), log_set_terminal_output(), set_flip_x(), and set_matrix_rain().

◆ last_color_query_us

uint64_t last_color_query_us

Definition at line 97 of file grep.c.

◆ line_buffer

char** line_buffer

Circular buffer for context_before.

Definition at line 83 of file grep.c.

Referenced by sdp_parse().

◆ lines_after_match

int lines_after_match

Counter for context_after lines.

Definition at line 86 of file grep.c.

◆ match_data_initialized

volatile int match_data_initialized

Once flag for key initialization.

Definition at line 80 of file grep.c.

◆ match_data_key

tls_key_t match_data_key

Thread-local match_data key.

Definition at line 79 of file grep.c.

◆ max_context_after

int max_context_after

Maximum context_after across all patterns.

Definition at line 87 of file grep.c.

◆ pattern_capacity

int pattern_capacity

Allocated capacity.

Definition at line 77 of file grep.c.

◆ pattern_count

int pattern_count

Number of active patterns.

Definition at line 76 of file grep.c.

Referenced by interactive_grep_get_match_info().

◆ patterns

grep_pattern_t* patterns

Array of patterns.

Definition at line 75 of file grep.c.

◆ saved_enabled

bool saved_enabled

Saved enabled state.

Definition at line 93 of file grep.c.

◆ saved_pattern_capacity

int saved_pattern_capacity

Allocated capacity for saved.

Definition at line 92 of file grep.c.

◆ saved_pattern_count

int saved_pattern_count

Number of saved patterns.

Definition at line 91 of file grep.c.

◆ saved_patterns

grep_pattern_t* saved_patterns

Backup of patterns for restore.

Definition at line 90 of file grep.c.