ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
search.c File Reference

Interactive search filtering implementation. More...

Go to the source code of this file.

Data Structures

struct  log_search_state_t
 

Macros

#define MAX_LOG_SEARCH_PATTERNS   32
 Maximum number of patterns to support.
 
#define LOG_SEARCH_INPUT_BUFFER_SIZE   256
 Input buffer size.
 

Functions

asciichat_error_t log_search_init (void)
 Initialize interactive grep subsystem.
 
void log_search_destroy (void)
 Clean up interactive grep subsystem.
 
void log_search_enter_mode (void)
 Enter search mode (user pressed '/')
 
void log_search_exit_mode (bool accept)
 Exit search mode.
 
bool log_search_is_entering_atomic (void)
 Check if grep is in entering mode (async-signal-safe)
 
void log_search_signal_cancel (void)
 Cancel grep mode from a signal handler (async-signal-safe)
 
bool log_search_check_signal_cancel (void)
 Check and clear the signal-cancel flag.
 
bool log_search_is_entering (void)
 Check if currently in input mode (typing pattern)
 
bool log_search_is_active (void)
 Check if filtering is active.
 
bool log_search_should_handle (int key)
 Check if a key should be handled by grep module.
 
asciichat_error_t log_search_handle_key (keyboard_key_t key)
 Process keyboard input for grep.
 
asciichat_error_t log_search_gather_and_filter_logs (session_log_entry_t **out_entries, size_t *out_count)
 Gather and filter logs for display.
 
void log_search_render_input_line (int width)
 Render grep input line at bottom of screen.
 
bool log_search_get_match_info (const char *message, size_t *out_match_start, size_t *out_match_len)
 Get match info for highlighting a log message.
 
bool log_search_needs_rerender (void)
 Check if screen needs immediate re-render.
 
bool log_search_get_global_highlight (void)
 Check if global highlighting (/g flag) is enabled in current pattern.
 
void * log_search_get_pattern_singleton (void)
 Get the compiled regex pattern for interactive grep (internal use)
 
void * log_search_get_mutex (void)
 Get the mutex protecting grep state.
 
int log_search_get_input_len (void)
 Get current input buffer length (must hold mutex)
 
int log_search_get_cursor_position (void)
 Get current cursor position in input buffer (must hold mutex)
 
const char * log_search_get_input_buffer (void)
 Get current input buffer content (must hold mutex)
 
bool log_search_get_case_insensitive (void)
 Check if case-insensitive flag is set (must hold mutex)
 

Detailed Description

Interactive search filtering implementation.

Implements vim-style / search functionality for terminal screens. Supports full /pattern/flags syntax with real-time filtering.

Definition in file search.c.

Macro Definition Documentation

◆ LOG_SEARCH_INPUT_BUFFER_SIZE

#define LOG_SEARCH_INPUT_BUFFER_SIZE   256

Input buffer size.

Definition at line 33 of file search.c.

◆ MAX_LOG_SEARCH_PATTERNS

#define MAX_LOG_SEARCH_PATTERNS   32

Maximum number of patterns to support.

Definition at line 32 of file search.c.

Function Documentation

◆ log_search_check_signal_cancel()

bool log_search_check_signal_cancel ( void  )

Check and clear the signal-cancel flag.

Returns
true if grep was cancelled by a signal since last check

Called by the status screen loop to detect signal-initiated cancellation.

Definition at line 380 of file search.c.

380 {
381 bool expected = true;
382 return atomic_cas_bool(&g_search_state.signal_cancelled, &expected, false);
383}
bool atomic_cas_bool(atomic_t *a, bool *expected, bool new_value)
Atomically compare-and-swap a boolean.
Definition atomic.c:184
atomic_t signal_cancelled
Set by signal handler, checked by render loop.
Definition search.c:64

References atomic_cas_bool(), and log_search_state_t::signal_cancelled.

◆ log_search_destroy()

void log_search_destroy ( void  )

Clean up interactive grep subsystem.

Frees all allocated resources. Safe to call multiple times.

Definition at line 192 of file search.c.

192 {
193 if (!lifecycle_shutdown(&g_log_search_lifecycle)) {
194 return; // Already shut down or never initialized
195 }
196
197 mutex_lock(&g_search_state.mutex);
198
199 // Free active patterns
200 for (int i = 0; i < g_search_state.active_pattern_count; i++) {
201 if (g_search_state.active_patterns[i]) {
202 // Note: pcre2_singleton handles its own reference counting
203 g_search_state.active_patterns[i] = NULL;
204 }
205 }
206
207 mutex_unlock(&g_search_state.mutex);
208}
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
pcre2_singleton_t * active_patterns[32]
Definition search.c:50
int active_pattern_count
Definition search.c:51
mutex_t mutex
Definition search.c:61

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, lifecycle_shutdown(), log_search_state_t::mutex, mutex_lock, and mutex_unlock.

◆ log_search_enter_mode()

void log_search_enter_mode ( void  )

Enter search mode (user pressed '/')

Saves current CLI –grep patterns, activates input mode, and prepares for interactive pattern entry.

Definition at line 214 of file search.c.

214 {
215 // Ensure cursor is visible for user typing
216 (void)terminal_cursor_show();
217
218 mutex_lock(&g_search_state.mutex);
219
220 // Save current patterns (CLI --grep patterns)
222 if (result != ASCIICHAT_OK) {
223 log_warn("Failed to save filter patterns");
224 }
225
226 // Clear input buffer
227 memset(g_search_state.input_buffer, 0, sizeof(g_search_state.input_buffer));
228 g_search_state.len = 0;
229 g_search_state.cursor = 0;
230
231 // Pre-populate with CLI --grep pattern if available (only once at startup)
232 if (!g_search_state.cli_pattern_auto_populated) {
233 const char *cli_pattern = grep_get_last_pattern();
234 if (cli_pattern && cli_pattern[0] != '\0') {
235 // Skip leading slash if present (prompt already shows "/")
236 const char *pattern_to_use = cli_pattern;
237 if (cli_pattern[0] == '/') {
238 pattern_to_use = cli_pattern + 1;
239 }
240
241 size_t pattern_len = strlen(pattern_to_use);
242 if (pattern_len < sizeof(g_search_state.input_buffer) - 1) {
243 memcpy(g_search_state.input_buffer, pattern_to_use, pattern_len + 1);
244 g_search_state.len = pattern_len;
245 g_search_state.cursor = pattern_len;
246 g_search_state.cli_pattern_auto_populated = true;
247
248 // Compile and apply the pattern immediately so it starts filtering
249 grep_parse_result_t parsed = grep_parse_pattern(g_search_state.input_buffer);
250 if (parsed.valid) {
251 // Store parsed flags
252 g_search_state.case_insensitive = parsed.case_insensitive;
253 g_search_state.fixed_string = parsed.is_fixed_string;
254 g_search_state.global_highlight = parsed.global_flag;
255 g_search_state.invert_match = parsed.invert;
256 g_search_state.context_before = parsed.context_before;
257 g_search_state.context_after = parsed.context_after;
258
259 // Compile pattern if not fixed string
260 if (strlen(parsed.pattern) > 0 && !parsed.is_fixed_string) {
262 if (singleton) {
263 pcre2_code *code = asciichat_pcre2_singleton_get_code(singleton);
264 if (code) {
265 g_search_state.active_patterns[0] = singleton;
266 g_search_state.active_pattern_count = 1;
267 } else {
268 g_search_state.fixed_string = true;
269 }
270 } else {
271 g_search_state.fixed_string = true;
272 }
273 }
274 }
275 }
276 }
277 }
278
279 // Enter input mode
280 g_search_state.mode = LOG_SEARCH_MODE_ENTERING;
282 atomic_store_bool(&g_search_state.needs_rerender, true);
283
284 mutex_unlock(&g_search_state.mutex);
285}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
void atomic_store_int(atomic_t *a, int value)
Atomically store an int value.
Definition atomic.c:202
asciichat_error_t grep_save_patterns(void)
Save current filter patterns for later restoration.
Definition grep.c:1215
const char * grep_get_last_pattern(void)
Get the last (most recent) filter pattern string.
Definition grep.c:1326
grep_parse_result_t grep_parse_pattern(const char *input)
Parse pattern in /pattern/flags or pattern/flags format.
Definition grep.c:351
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
asciichat_error_t terminal_cursor_show(void)
Show terminal cursor.
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 * 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
@ LOG_SEARCH_MODE_ENTERING
'/' pressed, typing pattern
Definition search.h:34
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
atomic_t mode_atomic
Shadow of mode for signal-safe reads.
Definition search.c:65
bool cli_pattern_auto_populated
Track if CLI pattern was already populated.
Definition search.c:66
log_search_mode_t mode
Definition search.c:40
atomic_t needs_rerender
Definition search.c:63
bool global_highlight
Definition search.c:56
bool case_insensitive
Definition search.c:54
char input_buffer[256]
Definition search.c:41
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:22

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, ASCIICHAT_OK, asciichat_pcre2_singleton_compile(), asciichat_pcre2_singleton_get_code(), atomic_store_bool(), atomic_store_int(), grep_parse_result_t::case_insensitive, log_search_state_t::case_insensitive, log_search_state_t::cli_pattern_auto_populated, grep_parse_result_t::context_after, log_search_state_t::context_after, grep_parse_result_t::context_before, log_search_state_t::context_before, log_search_state_t::cursor, log_search_state_t::fixed_string, grep_parse_result_t::global_flag, log_search_state_t::global_highlight, grep_get_last_pattern(), grep_parse_pattern(), grep_save_patterns(), log_search_state_t::input_buffer, grep_parse_result_t::invert, log_search_state_t::invert_match, grep_parse_result_t::is_fixed_string, log_search_state_t::len, LOG_SEARCH_MODE_ENTERING, log_warn, log_search_state_t::mode, log_search_state_t::mode_atomic, log_search_state_t::mutex, mutex_lock, mutex_unlock, log_search_state_t::needs_rerender, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, terminal_cursor_show(), and grep_parse_result_t::valid.

Referenced by log_search_handle_key(), ui_status_display(), and ui_status_display_interactive().

◆ log_search_exit_mode()

void log_search_exit_mode ( bool  accept)

Exit search mode.

Parameters
acceptIf true, compile and activate typed pattern; if false, restore previous patterns

If accepting: Parses pattern with /pattern/flags syntax, compiles with PCRE2, and activates filtering. If canceling: Restores CLI –grep patterns.

Definition at line 287 of file search.c.

287 {
288 mutex_lock(&g_search_state.mutex);
289
290 if (g_search_state.mode != LOG_SEARCH_MODE_ENTERING) {
291 mutex_unlock(&g_search_state.mutex);
292 return;
293 }
294
295 if (!accept) {
296 // Cancel - restore previous patterns and reset CLI pattern flag so user can re-populate
298 if (result != ASCIICHAT_OK) {
299 log_warn("Failed to restore filter patterns");
300 }
301
302 // Clear interactive grep patterns
303 for (int i = 0; i < g_search_state.active_pattern_count; i++) {
304 g_search_state.active_patterns[i] = NULL;
305 }
306 g_search_state.active_pattern_count = 0;
307
308 // Reset the CLI pattern auto-populated flag so user can re-enter grep mode with the pattern again
309 g_search_state.cli_pattern_auto_populated = false;
310
311 g_search_state.mode = LOG_SEARCH_MODE_INACTIVE;
313 atomic_store_bool(&g_search_state.needs_rerender, true);
314 mutex_unlock(&g_search_state.mutex);
315 return;
316 }
317
318 // Accept - parse and compile pattern
319 grep_parse_result_t parsed = grep_parse_pattern(g_search_state.input_buffer);
320
321 if (!parsed.valid) {
322 log_error("Invalid pattern format");
323 mutex_unlock(&g_search_state.mutex);
324 return; // Stay in input mode
325 }
326
327 // Clear old regex patterns
328 for (int i = 0; i < g_search_state.active_pattern_count; i++) {
329 g_search_state.active_patterns[i] = NULL;
330 }
331 g_search_state.active_pattern_count = 0;
332
333 // Store parsed flags first
334 g_search_state.case_insensitive = parsed.case_insensitive;
335 g_search_state.fixed_string = parsed.is_fixed_string;
336 g_search_state.global_highlight = parsed.global_flag;
337 g_search_state.invert_match = parsed.invert;
338 g_search_state.context_before = parsed.context_before;
339 g_search_state.context_after = parsed.context_after;
340
341 // Compile and store new pattern (if not fixed string)
342 if (strlen(parsed.pattern) > 0 && !parsed.is_fixed_string) {
344 if (singleton) {
345 // Verify the pattern actually compiles
346 pcre2_code *code = asciichat_pcre2_singleton_get_code(singleton);
347 if (code) {
348 g_search_state.active_patterns[0] = singleton;
349 g_search_state.active_pattern_count = 1;
350 } else {
351 // Compilation failed - fall back to fixed string matching
352 g_search_state.fixed_string = true; // Override to use fixed string instead
353 }
354 } else {
355 // Malloc failed - fall back to fixed string
356 g_search_state.fixed_string = true;
357 }
358 }
359 // For fixed strings, pattern stays in input_buffer and fixed_string=true
360
361 g_search_state.mode = LOG_SEARCH_MODE_ACTIVE;
363 atomic_store_bool(&g_search_state.needs_rerender, true);
364
365 mutex_unlock(&g_search_state.mutex);
366}
asciichat_error_t grep_restore_patterns(void)
Restore previously saved filter patterns.
Definition grep.c:1265
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
@ LOG_SEARCH_MODE_ACTIVE
Pattern accepted and filtering.
Definition search.h:35
@ LOG_SEARCH_MODE_INACTIVE
Not in search mode.
Definition search.h:33

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, ASCIICHAT_OK, asciichat_pcre2_singleton_compile(), asciichat_pcre2_singleton_get_code(), atomic_store_bool(), atomic_store_int(), grep_parse_result_t::case_insensitive, log_search_state_t::case_insensitive, log_search_state_t::cli_pattern_auto_populated, grep_parse_result_t::context_after, log_search_state_t::context_after, grep_parse_result_t::context_before, log_search_state_t::context_before, log_search_state_t::fixed_string, grep_parse_result_t::global_flag, log_search_state_t::global_highlight, grep_parse_pattern(), grep_restore_patterns(), log_search_state_t::input_buffer, grep_parse_result_t::invert, log_search_state_t::invert_match, grep_parse_result_t::is_fixed_string, log_error, LOG_SEARCH_MODE_ACTIVE, LOG_SEARCH_MODE_ENTERING, LOG_SEARCH_MODE_INACTIVE, log_warn, log_search_state_t::mode, log_search_state_t::mode_atomic, log_search_state_t::mutex, mutex_lock, mutex_unlock, log_search_state_t::needs_rerender, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, and grep_parse_result_t::valid.

Referenced by log_search_handle_key(), and ui_status_display_interactive().

◆ log_search_gather_and_filter_logs()

asciichat_error_t log_search_gather_and_filter_logs ( session_log_entry_t **  out_entries,
size_t *  out_count 
)

Gather and filter logs for display.

Parameters
out_entriesOutput array of filtered log entries (caller must free with SAFE_FREE)
out_countOutput count of filtered entries
Returns
ASCIICHAT_OK on success, error code on failure

Hybrid approach:

  1. If –log-file specified: tail last N KB, parse entries
  2. Fetch entries from in-memory session_log_buffer (up to 100 entries)
  3. Merge and deduplicate by timestamp
  4. Filter combined set with active PCRE2 patterns
  5. Return filtered entries (caller must free)

If no log file or read error: Falls back to in-memory buffer only.

Definition at line 530 of file search.c.

530 {
531 if (!out_entries || !out_count) {
532 return SET_ERRNO(ERROR_INVALID_PARAM, "out_entries and out_count must not be NULL");
533 }
534
535 // Get logs from in-memory buffer
536 session_log_entry_t *buffer_entries =
538 if (!buffer_entries) {
539 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate log buffer");
540 }
541
543 size_t buffer_count = session_log_buffer_get_recent(log_buf, buffer_entries, SESSION_LOG_BUFFER_SIZE);
544
545 // Check if filtering is active (either regex patterns or fixed string)
546 mutex_lock(&g_search_state.mutex);
547 bool has_regex_patterns = (g_search_state.active_pattern_count > 0);
548 bool has_fixed_string = (g_search_state.fixed_string && g_search_state.len > 0);
549 bool filtering_active = has_regex_patterns || has_fixed_string;
550
551 if (!filtering_active) {
552 // No filtering active - return all entries
553 mutex_unlock(&g_search_state.mutex);
554 *out_entries = buffer_entries;
555 *out_count = buffer_count;
556 return ASCIICHAT_OK;
557 }
558
559 // Filter entries with PCRE2
560 session_log_entry_t *filtered =
562 if (!filtered) {
563 mutex_unlock(&g_search_state.mutex);
564 SAFE_FREE(buffer_entries);
565 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate filtered buffer");
566 }
567
568 size_t filtered_count = 0;
569
570 // Create match data for PCRE2 matching
571 pcre2_match_data *match_data = NULL;
572 for (int p = 0; p < g_search_state.active_pattern_count; p++) {
573 if (g_search_state.active_patterns[p]) {
574 pcre2_code *code = asciichat_pcre2_singleton_get_code(g_search_state.active_patterns[p]);
575 if (code) {
576 match_data = pcre2_match_data_create_from_pattern(code, NULL);
577 break;
578 }
579 }
580 }
581
582 if (!match_data && g_search_state.active_pattern_count > 0) {
583 // Couldn't create match data - fall back to returning all entries
584 mutex_unlock(&g_search_state.mutex);
585 SAFE_FREE(filtered);
586 *out_entries = buffer_entries;
587 *out_count = buffer_count;
588 return ASCIICHAT_OK;
589 }
590
591 // Parse pattern once before loop (for fixed string mode)
592 grep_parse_result_t parsed = {0};
593 if (has_fixed_string) {
594 parsed = grep_parse_pattern(g_search_state.input_buffer);
595 }
596
597 for (size_t i = 0; i < buffer_count; i++) {
598 const char *original_message = buffer_entries[i].message;
599 bool matches = false;
600
601 // Strip ANSI codes to match against plain text (consistent with highlighting)
602 char plain_message[SESSION_LOG_LINE_MAX] = {0};
603 const char *strip_ansi_codes_fn(const char *src, char *dst, size_t dst_size);
604 // Use inline stripping to avoid function call overhead
605 {
606 size_t pos = 0;
607 const char *src = original_message;
608 char *dst = plain_message;
609 size_t dst_size = sizeof(plain_message);
610
611 while (*src && pos < dst_size - 1) {
612 if (*src == '\x1b' && src[1] != '\0' && src[1] == '[') {
613 // CSI sequence - skip until final byte
614 src += 2;
615 while (*src && pos < dst_size - 1 && !(*src >= 0x40 && *src <= 0x7E)) {
616 src++;
617 }
618 if (*src)
619 src++; // Skip final byte
620 } else {
621 dst[pos++] = *src++;
622 }
623 }
624 dst[pos] = '\0';
625 }
626
627 size_t message_len = strlen(plain_message);
628
629 // Fixed string matching
630 if (has_fixed_string) {
631 const char *search_pattern = parsed.pattern;
632
633 if (g_search_state.case_insensitive) {
634 // Unicode-aware case-insensitive fixed string search
635 matches = (utf8_strcasestr(plain_message, search_pattern) != NULL);
636 } else {
637 // Case-sensitive fixed string search
638 matches = (strstr(plain_message, search_pattern) != NULL);
639 }
640 }
641 // Regex pattern matching
642 else if (has_regex_patterns) {
643 // Check against all active patterns (OR logic)
644 for (int p = 0; p < g_search_state.active_pattern_count; p++) {
645 pcre2_singleton_t *singleton = g_search_state.active_patterns[p];
646 if (!singleton) {
647 continue;
648 }
649
650 pcre2_code *code = asciichat_pcre2_singleton_get_code(singleton);
651 if (!code) {
652 continue;
653 }
654
655 // Match against plain message (without ANSI codes)
656 int rc = pcre2_jit_match(code, (PCRE2_SPTR)plain_message, message_len, 0, 0, match_data, NULL);
657 if (rc >= 0) {
658 matches = true;
659 break;
660 }
661 }
662 }
663
664 // Apply invert logic
665 if (g_search_state.invert_match) {
666 matches = !matches;
667 }
668
669 if (matches) {
670 filtered[filtered_count++] = buffer_entries[i];
671 }
672 }
673
674 // Free match data
675 if (match_data) {
676 pcre2_match_data_free(match_data);
677 }
678
679 mutex_unlock(&g_search_state.mutex);
680
681 SAFE_FREE(buffer_entries);
682 *out_entries = filtered;
683 *out_count = filtered_count;
684 return ASCIICHAT_OK;
685}
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#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
@ ERROR_INVALID_PARAM
session_log_buffer_t * log_get_session_log_buffer(void)
Get the currently registered session log buffer.
Definition log/log.c:1857
const char * utf8_strcasestr(const char *haystack, const char *needle)
Case-insensitive substring search with full Unicode support.
Definition utf8.c:272
size_t session_log_buffer_get_recent(session_log_buffer_t *buf, session_log_entry_t *out_entries, size_t max_count)
Get recent log entries from buffer.
Internal circular buffer structure.
Single log entry with sequence number for ordering.
char message[1024]
Formatted log message (may include ANSI colors)

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, ASCIICHAT_OK, asciichat_pcre2_singleton_get_code(), log_search_state_t::case_insensitive, ERROR_INVALID_PARAM, ERROR_MEMORY, log_search_state_t::fixed_string, grep_parse_pattern(), log_search_state_t::input_buffer, log_search_state_t::invert_match, log_search_state_t::len, log_get_session_log_buffer(), session_log_entry_t::message, log_search_state_t::mutex, mutex_lock, mutex_unlock, grep_parse_result_t::pattern, SAFE_FREE, SAFE_MALLOC, session_log_buffer_get_recent(), SESSION_LOG_BUFFER_SIZE, SESSION_LOG_LINE_MAX, SET_ERRNO, and utf8_strcasestr().

Referenced by terminal_screen_render().

◆ log_search_get_case_insensitive()

bool log_search_get_case_insensitive ( void  )

Check if case-insensitive flag is set (must hold mutex)

Returns
true if /i flag is set, false otherwise

Must be called while holding the mutex from log_search_get_mutex().

Definition at line 952 of file search.c.

952 {
953 return g_search_state.case_insensitive;
954}

References log_search_state_t::case_insensitive.

Referenced by grep_highlight_colored().

◆ log_search_get_cursor_position()

int log_search_get_cursor_position ( void  )

Get current cursor position in input buffer (must hold mutex)

Returns
Cursor position (0-based index within input buffer)

Must be called while holding the mutex from log_search_get_mutex().

Definition at line 944 of file search.c.

944 {
945 return (int)g_search_state.cursor;
946}

References log_search_state_t::cursor.

Referenced by terminal_screen_render().

◆ log_search_get_global_highlight()

bool log_search_get_global_highlight ( void  )

Check if global highlighting (/g flag) is enabled in current pattern.

Returns
true if interactive grep is active and /g flag is set, false otherwise

Used by filter highlighting code to determine if all matches should be highlighted or just the first match. Returns false when interactive grep is inactive.

Definition at line 896 of file search.c.

896 {
897 // Use atomic read to avoid mutex contention with keyboard handler
898 int mode = atomic_load_int(&g_search_state.mode_atomic);
899 if (mode == LOG_SEARCH_MODE_INACTIVE) {
900 return false;
901 }
902
903 // Safe read under mutex (quick operation)
904 mutex_lock(&g_search_state.mutex);
905 bool result = g_search_state.global_highlight;
906 mutex_unlock(&g_search_state.mutex);
907
908 return result;
909}
int atomic_load_int(atomic_t *a)
Atomically load an int value.
Definition atomic.c:194

References atomic_load_int(), log_search_state_t::global_highlight, LOG_SEARCH_MODE_INACTIVE, log_search_state_t::mode_atomic, log_search_state_t::mutex, mutex_lock, and mutex_unlock.

Referenced by grep_highlight_colored().

◆ log_search_get_input_buffer()

const char * log_search_get_input_buffer ( void  )

Get current input buffer content (must hold mutex)

Returns
Pointer to the input buffer (valid only while holding mutex)

Must be called while holding the mutex from log_search_get_mutex().

Definition at line 948 of file search.c.

948 {
949 return g_search_state.input_buffer;
950}

References log_search_state_t::input_buffer.

Referenced by grep_highlight_colored(), and terminal_screen_render().

◆ log_search_get_input_len()

int log_search_get_input_len ( void  )

Get current input buffer length (must hold mutex)

Returns
Length of the current grep input pattern

Must be called while holding the mutex from log_search_get_mutex().

Definition at line 940 of file search.c.

940 {
941 return (int)g_search_state.len;
942}

References log_search_state_t::len.

Referenced by grep_highlight_colored(), and terminal_screen_render().

◆ log_search_get_match_info()

bool log_search_get_match_info ( const char *  message,
size_t *  out_match_start,
size_t *  out_match_len 
)

Get match info for highlighting a log message.

Parameters
messageLog message to check
out_match_startOutput: start position of match in plain text
out_match_lenOutput: length of match
Returns
true if message matches current filter pattern, false otherwise

Used by display code to apply highlighting to matching portions of logs. Returns match position for use with log_filter_highlight().

Definition at line 729 of file search.c.

729 {
730 if (!message || !out_match_start || !out_match_len) {
731 return false;
732 }
733
734 *out_match_start = 0;
735 *out_match_len = 0;
736
737 // Use atomic read to avoid mutex contention with keyboard handler
738 int mode = atomic_load_int(&g_search_state.mode_atomic);
739 if (mode == LOG_SEARCH_MODE_INACTIVE) {
740 return false;
741 }
742
743 // Make a safe copy of state under mutex, then release before doing expensive work
744 mutex_lock(&g_search_state.mutex);
745
746 bool has_fixed_string = g_search_state.fixed_string && g_search_state.len > 0;
747 bool case_insensitive = g_search_state.case_insensitive;
748 char pattern_copy[LOG_SEARCH_INPUT_BUFFER_SIZE];
749 size_t pattern_len = g_search_state.len;
750 int pattern_count = g_search_state.active_pattern_count;
752
753 if (has_fixed_string) {
754 // Parse the input buffer to extract just the pattern part (without flags)
755 grep_parse_result_t parsed = grep_parse_pattern(g_search_state.input_buffer);
756 const char *src = parsed.valid ? parsed.pattern : g_search_state.input_buffer;
757 SAFE_STRNCPY(pattern_copy, src, sizeof(pattern_copy) - 1);
758 pattern_copy[sizeof(pattern_copy) - 1] = '\0';
759 pattern_len = strlen(pattern_copy); // Update pattern_len for the actual parsed pattern
760 }
761
762 for (int i = 0; i < pattern_count && i < MAX_LOG_SEARCH_PATTERNS; i++) {
763 patterns_copy[i] = g_search_state.active_patterns[i];
764 }
765
766 mutex_unlock(&g_search_state.mutex);
767
768 // Now do matching without holding mutex
769 size_t message_len = strlen(message);
770 bool matches = false;
771
772 // Fixed string matching
773 if (has_fixed_string) {
774 const char *found = NULL;
775
776 if (case_insensitive) {
777 found = utf8_strcasestr(message, pattern_copy);
778 } else {
779 found = strstr(message, pattern_copy);
780 }
781
782 if (found) {
783 matches = true;
784
785 // Convert byte position to character position (UTF-8 aware)
786 size_t byte_pos = (size_t)(found - message);
787 size_t char_pos = 0;
788 for (size_t i = 0; i < byte_pos && message[i] != '\0';) {
789 uint32_t codepoint;
790 int utf8_len = utf8_decode((const uint8_t *)(message + i), &codepoint);
791 if (utf8_len <= 0)
792 utf8_len = 1;
793 i += utf8_len;
794 char_pos++;
795 }
796
797 // Count characters in the match (pattern_len is in bytes for the pattern)
798 size_t match_char_len = 0;
799 for (size_t i = 0; i < pattern_len && found[i] != '\0';) {
800 uint32_t codepoint;
801 int utf8_len = utf8_decode((const uint8_t *)(found + i), &codepoint);
802 if (utf8_len <= 0)
803 utf8_len = 1;
804 i += utf8_len;
805 match_char_len++;
806 }
807
808 *out_match_start = char_pos;
809 *out_match_len = match_char_len;
810 }
811 }
812 // Regex pattern matching
813 else if (pattern_count > 0) {
814 pcre2_match_data *match_data = NULL;
815 for (int p = 0; p < pattern_count; p++) {
816 if (patterns_copy[p]) {
817 pcre2_code *code = asciichat_pcre2_singleton_get_code(patterns_copy[p]);
818 if (code) {
819 match_data = pcre2_match_data_create_from_pattern(code, NULL);
820 break;
821 }
822 }
823 }
824
825 if (match_data) {
826 for (int p = 0; p < pattern_count; p++) {
827 pcre2_singleton_t *singleton = patterns_copy[p];
828 if (!singleton) {
829 continue;
830 }
831
832 pcre2_code *code = asciichat_pcre2_singleton_get_code(singleton);
833 if (!code) {
834 continue;
835 }
836
837 int rc = pcre2_jit_match(code, (PCRE2_SPTR)message, message_len, 0, 0, match_data, NULL);
838 if (rc >= 0) {
839 matches = true;
840 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
841
842 // Convert byte positions to character positions (UTF-8 aware)
843 size_t byte_start = (size_t)ovector[0];
844 size_t byte_end = (size_t)ovector[1];
845
846 // Count characters up to byte_start
847 size_t char_start = 0;
848 for (size_t i = 0; i < byte_start && message[i] != '\0';) {
849 uint32_t codepoint;
850 int utf8_len = utf8_decode((const uint8_t *)(message + i), &codepoint);
851 if (utf8_len <= 0)
852 utf8_len = 1;
853 i += utf8_len;
854 char_start++;
855 }
856
857 // Count characters up to byte_end
858 size_t char_end = char_start;
859 for (size_t i = byte_start; i < byte_end && message[i] != '\0';) {
860 uint32_t codepoint;
861 int utf8_len = utf8_decode((const uint8_t *)(message + i), &codepoint);
862 if (utf8_len <= 0)
863 utf8_len = 1;
864 i += utf8_len;
865 char_end++;
866 }
867
868 *out_match_start = char_start;
869 *out_match_len = char_end - char_start;
870 break;
871 }
872 }
873 pcre2_match_data_free(match_data);
874 }
875 }
876
877 return matches;
878}
int pattern_count
Number of active patterns.
Definition grep.c:82
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
unsigned char uint8_t
Definition common.h:56
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
Decode a UTF-8 sequence to a Unicode codepoint.
Definition utf8.c:18
#define MAX_LOG_SEARCH_PATTERNS
Maximum number of patterns to support.
Definition search.c:32
#define LOG_SEARCH_INPUT_BUFFER_SIZE
Input buffer size.
Definition search.c:33

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, asciichat_pcre2_singleton_get_code(), atomic_load_int(), log_search_state_t::case_insensitive, log_search_state_t::fixed_string, grep_parse_pattern(), log_search_state_t::input_buffer, log_search_state_t::len, LOG_SEARCH_INPUT_BUFFER_SIZE, LOG_SEARCH_MODE_INACTIVE, MAX_LOG_SEARCH_PATTERNS, log_search_state_t::mode_atomic, log_search_state_t::mutex, mutex_lock, mutex_unlock, grep_parse_result_t::pattern, pattern_count, SAFE_STRNCPY, utf8_decode(), utf8_strcasestr(), and grep_parse_result_t::valid.

Referenced by terminal_screen_render().

◆ log_search_get_mutex()

void * log_search_get_mutex ( void  )

Get the mutex protecting grep state.

Returns
Pointer to the grep state mutex (internal use only)

Used by terminal rendering to perform atomic read of grep input for consistent rendering without flicker.

Definition at line 936 of file search.c.

936 {
937 return (void *)&g_search_state.mutex;
938}

References log_search_state_t::mutex.

Referenced by terminal_screen_render().

◆ log_search_get_pattern_singleton()

void * log_search_get_pattern_singleton ( void  )

Get the compiled regex pattern for interactive grep (internal use)

Returns
Opaque PCRE2 singleton for the current pattern, or NULL if inactive/no pattern

Internal function used by filter highlighting code to enable global match highlighting in interactive grep mode. Returns NULL when not in active mode or if pattern is fixed string type (no regex compilation).

Definition at line 911 of file search.c.

911 {
912 // Use atomic read to check if active
913 int mode = atomic_load_int(&g_search_state.mode_atomic);
914 if (mode == LOG_SEARCH_MODE_INACTIVE) {
915 return NULL;
916 }
917
918 // Safe read under mutex
919 mutex_lock(&g_search_state.mutex);
920
921 // Return the compiled pattern if we have one (not fixed string)
922 void *result = NULL;
923 if (g_search_state.active_pattern_count > 0 && !g_search_state.fixed_string) {
924 result = (void *)g_search_state.active_patterns[0];
925 }
926
927 mutex_unlock(&g_search_state.mutex);
928
929 return result;
930}

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, atomic_load_int(), log_search_state_t::fixed_string, LOG_SEARCH_MODE_INACTIVE, log_search_state_t::mode_atomic, log_search_state_t::mutex, mutex_lock, and mutex_unlock.

Referenced by grep_highlight_colored().

◆ log_search_handle_key()

asciichat_error_t log_search_handle_key ( keyboard_key_t  key)

Process keyboard input for grep.

Parameters
keyThe keyboard key to process
Returns
ASCIICHAT_OK on success, error code on failure

Uses keyboard_read_line_interactive() to handle text editing. Supports backspace, delete, arrows, home, end, enter, escape, UTF-8.

Pattern validation happens live via validator callback. Enter compiles and activates pattern. Escape cancels and restores previous patterns.

Definition at line 414 of file search.c.

414 {
415 mutex_lock(&g_search_state.mutex);
416
417 // If not in input mode, check if user pressed '/'
418 if (g_search_state.mode != LOG_SEARCH_MODE_ENTERING) {
419 mutex_unlock(&g_search_state.mutex);
420
421 // Check for '/' key (key already passed as parameter, don't read again!)
422 if (key == '/') {
424 }
425 return ASCIICHAT_OK;
426 }
427
428 // In input mode - use keyboard_read_line_interactive()
429 // CRITICAL: Keep mutex held during keyboard_read_line_interactive() to prevent races
430 // with the rendering thread. The validator callback and buffer modifications must be
431 // synchronized with readers in log_search_gather_and_filter_logs().
433 .buffer = g_search_state.input_buffer,
434 .max_len = sizeof(g_search_state.input_buffer),
435 .len = &g_search_state.len,
436 .cursor = &g_search_state.cursor,
437 .echo = false, // We handle rendering ourselves
438 .mask_char = 0, // No masking
439 .prefix = NULL, // Don't render prefix - log_search_render_input_line() handles it
440 .validator = validate_pcre2_pattern, // Live validation
441 .key = key // Pass the pre-read key
442 };
443
445
446 switch (result) {
448 // Unlock before exit_mode since it acquires the mutex
449 mutex_unlock(&g_search_state.mutex);
450 log_search_exit_mode(true); // Parse and accept pattern
451 break;
453 // Unlock before exit_mode since it acquires the mutex
454 mutex_unlock(&g_search_state.mutex);
455 log_search_exit_mode(false); // Restore previous
456 break;
458 // Still editing - compile pattern for live filtering
459 // Mutex is still held here
460
461 // If buffer is empty, clear patterns to show all logs (don't exit grep mode)
462 if (g_search_state.len == 0) {
463 g_search_state.active_pattern_count = 0;
464 mutex_unlock(&g_search_state.mutex);
465 atomic_store_bool(&g_search_state.needs_rerender, true);
466 break;
467 }
468
469 // Parse and compile pattern for live filtering
470 grep_parse_result_t parsed = grep_parse_pattern(g_search_state.input_buffer);
471
472 if (!parsed.valid) {
473 // Invalid pattern - keep previous patterns active (don't clear)
474 mutex_unlock(&g_search_state.mutex);
475 atomic_store_bool(&g_search_state.needs_rerender, true);
476 break;
477 }
478
479 // Valid pattern - update filtering state
480 if (parsed.is_fixed_string) {
481 // Fixed string matching - no regex compilation needed
482 g_search_state.active_pattern_count = 0;
483 g_search_state.case_insensitive = parsed.case_insensitive;
484 g_search_state.fixed_string = true;
485 } else {
486 // Regex pattern - compile with full pcre2_options
488 if (singleton) {
489 pcre2_code *code = asciichat_pcre2_singleton_get_code(singleton);
490 if (code) {
491 g_search_state.active_patterns[0] = singleton;
492 g_search_state.active_pattern_count = 1;
493 g_search_state.case_insensitive = parsed.case_insensitive;
494 g_search_state.fixed_string = false;
495 } else {
496 // Compilation failed - fall back to fixed string matching
497 g_search_state.active_pattern_count = 0;
498 g_search_state.fixed_string = true;
499 g_search_state.case_insensitive = parsed.case_insensitive;
500 }
501 } else {
502 g_search_state.active_pattern_count = 0;
503 g_search_state.fixed_string = true;
504 g_search_state.case_insensitive = parsed.case_insensitive;
505 }
506 }
507
508 // Store parsed flags
509 g_search_state.global_highlight = parsed.global_flag;
510 g_search_state.invert_match = parsed.invert;
511 g_search_state.context_before = parsed.context_before;
512 g_search_state.context_after = parsed.context_after;
513
514 mutex_unlock(&g_search_state.mutex);
515 atomic_store_bool(&g_search_state.needs_rerender, true);
516 break;
518 // No input available
519 mutex_unlock(&g_search_state.mutex);
520 break;
521 }
522
523 return ASCIICHAT_OK;
524}
keyboard_line_edit_result_t keyboard_read_line_interactive(keyboard_line_edit_opts_t *opts)
Process one keystroke for interactive line editing.
keyboard_line_edit_result_t
Result codes for interactive line editing.
Definition keyboard.h:212
@ LINE_EDIT_ACCEPTED
User pressed Enter (accept input)
Definition keyboard.h:214
@ LINE_EDIT_CANCELLED
User pressed Escape/Ctrl+C (cancel input)
Definition keyboard.h:215
@ LINE_EDIT_CONTINUE
Keep editing (more input needed)
Definition keyboard.h:213
@ LINE_EDIT_NO_INPUT
No key available (non-blocking mode)
Definition keyboard.h:216
void log_search_enter_mode(void)
Enter search mode (user pressed '/')
Definition search.c:214
void log_search_exit_mode(bool accept)
Exit search mode.
Definition search.c:287
Options for interactive line editing.
Definition keyboard.h:192
char * buffer
Input buffer (modified in-place)
Definition keyboard.h:193

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, ASCIICHAT_OK, asciichat_pcre2_singleton_compile(), asciichat_pcre2_singleton_get_code(), atomic_store_bool(), keyboard_line_edit_opts_t::buffer, grep_parse_result_t::case_insensitive, log_search_state_t::case_insensitive, grep_parse_result_t::context_after, log_search_state_t::context_after, grep_parse_result_t::context_before, log_search_state_t::context_before, log_search_state_t::cursor, log_search_state_t::fixed_string, grep_parse_result_t::global_flag, log_search_state_t::global_highlight, grep_parse_pattern(), log_search_state_t::input_buffer, grep_parse_result_t::invert, log_search_state_t::invert_match, grep_parse_result_t::is_fixed_string, keyboard_read_line_interactive(), log_search_state_t::len, LINE_EDIT_ACCEPTED, LINE_EDIT_CANCELLED, LINE_EDIT_CONTINUE, LINE_EDIT_NO_INPUT, log_search_enter_mode(), log_search_exit_mode(), LOG_SEARCH_MODE_ENTERING, log_search_state_t::mode, log_search_state_t::mutex, mutex_lock, mutex_unlock, log_search_state_t::needs_rerender, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, and grep_parse_result_t::valid.

Referenced by session_render_loop(), and ui_status_display_interactive().

◆ log_search_init()

asciichat_error_t log_search_init ( void  )

Initialize interactive grep subsystem.

Returns
ASCIICHAT_OK on success, error code on failure

Must be called before using any other log_search functions. Initializes state, mutex, and loads CLI –grep patterns if any.

Definition at line 125 of file search.c.

125 {
126 // Use lifecycle API for unified initialization
127 if (!lifecycle_init_once(&g_log_search_lifecycle)) {
128 // Already initialized, acquire mutex lock and return
129 mutex_lock(&g_search_state.mutex);
130 return ASCIICHAT_OK;
131 }
132
133 // Winner of init_once - initialize mutex and state
134 g_log_search_lifecycle.sync_type = LIFECYCLE_SYNC_MUTEX;
135 g_log_search_lifecycle.sync.mutex = &g_search_state.mutex;
136 lifecycle_init(&g_log_search_lifecycle, "log_search");
137
138 mutex_lock(&g_search_state.mutex);
139
140 // Initialize state (careful not to destroy the mutex!)
141 // Save the mutex before clearing state
142 mutex_t saved_mutex = g_search_state.mutex;
143 memset(&g_search_state, 0, sizeof(g_search_state));
144 // Restore the mutex
145 g_search_state.mutex = saved_mutex;
146
147 // Check if there are CLI --grep patterns to use
148 const char *cli_pattern = grep_get_last_pattern();
149
150 // Start in INACTIVE mode if no CLI pattern is provided.
151 // Only use patterns from CLI --grep arguments, not default patterns.
152 // This ensures logs are visible by default and only filtered if explicitly requested.
153 if (!cli_pattern || cli_pattern[0] == '\0') {
154 // No CLI pattern - start inactive
155 g_search_state.mode = LOG_SEARCH_MODE_INACTIVE;
157 lifecycle_init_commit(&g_log_search_lifecycle);
158 mutex_unlock(&g_search_state.mutex);
159 return ASCIICHAT_OK;
160 }
161
162 // Load CLI pattern into input buffer
163 SAFE_STRNCPY(g_search_state.input_buffer, cli_pattern, LOG_SEARCH_INPUT_BUFFER_SIZE - 1);
164 g_search_state.len = strlen(cli_pattern);
165 g_search_state.cursor = g_search_state.len;
166 g_search_state.mode = LOG_SEARCH_MODE_ACTIVE;
168
169 // Compile the initial pattern
170 grep_parse_result_t parsed = grep_parse_pattern(cli_pattern);
171 if (parsed.valid) {
173 if (singleton) {
174 g_search_state.active_patterns[0] = singleton;
175 g_search_state.active_pattern_count = 1;
176 }
177 g_search_state.case_insensitive = parsed.case_insensitive;
178 g_search_state.fixed_string = parsed.is_fixed_string;
179 g_search_state.global_highlight = parsed.global_flag;
180 g_search_state.invert_match = parsed.invert;
181 g_search_state.context_before = parsed.context_before;
182 g_search_state.context_after = parsed.context_after;
183 }
184
185 atomic_store_bool(&g_search_state.needs_rerender, true);
186 lifecycle_init_commit(&g_log_search_lifecycle);
187
188 mutex_unlock(&g_search_state.mutex);
189 return ASCIICHAT_OK;
190}
void lifecycle_init_commit(lifecycle_t *lc)
Definition lifecycle.c:90
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
bool lifecycle_init_once(lifecycle_t *lc)
Definition lifecycle.c:52
@ LIFECYCLE_SYNC_MUTEX
Contains mutex_t pointer.
Definition lifecycle.h:50
union lifecycle_t::@24 sync
mutex_t * mutex
Pointer to mutex (if sync_type == LIFECYCLE_SYNC_MUTEX)
Definition lifecycle.h:62
lifecycle_sync_type_t sync_type
Type of sync primitive (if any)
Definition lifecycle.h:60
Mutex type (POSIX: pthread_mutex_t with debug tracking)

References log_search_state_t::active_pattern_count, log_search_state_t::active_patterns, ASCIICHAT_OK, asciichat_pcre2_singleton_compile(), atomic_store_bool(), atomic_store_int(), grep_parse_result_t::case_insensitive, log_search_state_t::case_insensitive, grep_parse_result_t::context_after, log_search_state_t::context_after, grep_parse_result_t::context_before, log_search_state_t::context_before, log_search_state_t::cursor, log_search_state_t::fixed_string, grep_parse_result_t::global_flag, log_search_state_t::global_highlight, grep_get_last_pattern(), grep_parse_pattern(), log_search_state_t::input_buffer, grep_parse_result_t::invert, log_search_state_t::invert_match, grep_parse_result_t::is_fixed_string, log_search_state_t::len, lifecycle_init(), lifecycle_init_commit(), lifecycle_init_once(), LIFECYCLE_SYNC_MUTEX, LOG_SEARCH_INPUT_BUFFER_SIZE, LOG_SEARCH_MODE_ACTIVE, LOG_SEARCH_MODE_INACTIVE, log_search_state_t::mode, log_search_state_t::mode_atomic, lifecycle_t::mutex, log_search_state_t::mutex, mutex_lock, mutex_unlock, log_search_state_t::needs_rerender, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, SAFE_STRNCPY, lifecycle_t::sync, lifecycle_t::sync_type, and grep_parse_result_t::valid.

◆ log_search_is_active()

bool log_search_is_active ( void  )

Check if filtering is active.

Returns
true if in GREP_MODE_ENTERING or GREP_MODE_ACTIVE, false if INACTIVE

Definition at line 390 of file search.c.

390 {
391 // Use atomic read (async-signal-safe) to avoid mutex issues when called from signal handlers
392 return atomic_load_int(&g_search_state.mode_atomic) != LOG_SEARCH_MODE_INACTIVE;
393}

References atomic_load_int(), LOG_SEARCH_MODE_INACTIVE, and log_search_state_t::mode_atomic.

Referenced by grep_highlight_colored(), terminal_screen_render(), and ui_status_display_interactive().

◆ log_search_is_entering()

bool log_search_is_entering ( void  )

Check if currently in input mode (typing pattern)

Returns
true if in GREP_MODE_ENTERING, false otherwise

Definition at line 385 of file search.c.

385 {
386 // Use atomic read (async-signal-safe) to avoid mutex issues when called from signal handlers
387 return atomic_load_int(&g_search_state.mode_atomic) == LOG_SEARCH_MODE_ENTERING;
388}

References atomic_load_int(), LOG_SEARCH_MODE_ENTERING, and log_search_state_t::mode_atomic.

Referenced by terminal_screen_render().

◆ log_search_is_entering_atomic()

bool log_search_is_entering_atomic ( void  )

Check if grep is in entering mode (async-signal-safe)

Returns
true if in GREP_MODE_ENTERING

Uses atomic load only (no mutex). Safe to call from signal handlers.

Definition at line 372 of file search.c.

372 {
373 return atomic_load_int(&g_search_state.mode_atomic) == LOG_SEARCH_MODE_ENTERING;
374}

References atomic_load_int(), LOG_SEARCH_MODE_ENTERING, and log_search_state_t::mode_atomic.

◆ log_search_needs_rerender()

bool log_search_needs_rerender ( void  )

Check if screen needs immediate re-render.

Returns
true if pattern changed and re-render needed, false otherwise

Uses atomic flag for lock-free checking in render loops. Automatically clears flag after returning true.

Definition at line 884 of file search.c.

884 {
885 bool needs = atomic_load_bool(&g_search_state.needs_rerender);
886 if (needs) {
887 atomic_store_bool(&g_search_state.needs_rerender, false);
888 }
889 return needs;
890}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169

References atomic_load_bool(), atomic_store_bool(), and log_search_state_t::needs_rerender.

Referenced by terminal_screen_render().

◆ log_search_render_input_line()

void log_search_render_input_line ( int  width)

Render grep input line at bottom of screen.

Parameters
widthTerminal width in columns

Renders: /<pattern>/flagsā–ˆ with cursor position indicated. Cursor shown as block character or with background color.

If pattern is invalid (validator returns false), shows red background.

Definition at line 691 of file search.c.

691 {
692 (void)width; // Reserved for future use (line truncation)
693
694 mutex_lock(&g_search_state.mutex);
695
696 if (g_search_state.mode != LOG_SEARCH_MODE_ENTERING) {
697 mutex_unlock(&g_search_state.mutex);
698 return;
699 }
700
701 // Render the pattern and position terminal cursor at current edit position
702 char output_buf[512];
703
704 // Build output: "/" + pattern
705 int pattern_len =
706 snprintf(output_buf, sizeof(output_buf), "/%.*s", (int)g_search_state.len, g_search_state.input_buffer);
707
708 if (pattern_len > 0 && pattern_len < (int)sizeof(output_buf)) {
709 // Write the pattern to terminal
710 platform_write_all(STDOUT_FILENO, output_buf, pattern_len);
711
712 // After writing, cursor is at: column = 1 (for "/") + len + 1
713 // We want it at: column = 1 (for "/") + cursor_position + 1
714 // So we need to move back by: len - cursor_position columns
715 int cursor_offset = (int)g_search_state.len - (int)g_search_state.cursor;
716 if (cursor_offset > 0) {
717 // Move left by the difference
718 terminal_move_cursor_relative(-cursor_offset);
719 }
720 }
721
722 mutex_unlock(&g_search_state.mutex);
723}
asciichat_error_t terminal_move_cursor_relative(int offset)
Move cursor relative to current position.
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
Definition system.c:224

References log_search_state_t::cursor, log_search_state_t::input_buffer, log_search_state_t::len, LOG_SEARCH_MODE_ENTERING, log_search_state_t::mode, log_search_state_t::mutex, mutex_lock, mutex_unlock, platform_write_all(), and terminal_move_cursor_relative().

Referenced by terminal_screen_render().

◆ log_search_should_handle()

bool log_search_should_handle ( int  key)

Check if a key should be handled by grep module.

Parameters
keyKeyboard key code
Returns
true if grep should handle this key, false to pass through

Returns true for:

  • '/' when not in input mode (to enter mode)
  • All keys when in GREP_MODE_ENTERING (to edit pattern)

Definition at line 399 of file search.c.

399 {
400 mutex_lock(&g_search_state.mutex);
401
402 // If in input mode, handle all keys
403 if (g_search_state.mode == LOG_SEARCH_MODE_ENTERING) {
404 mutex_unlock(&g_search_state.mutex);
405 return true;
406 }
407
408 // If not in input mode, only handle '/' to enter mode
409 bool should_handle = (key == '/');
410 mutex_unlock(&g_search_state.mutex);
411 return should_handle;
412}

References LOG_SEARCH_MODE_ENTERING, log_search_state_t::mode, log_search_state_t::mutex, mutex_lock, and mutex_unlock.

Referenced by session_render_loop(), and ui_status_display_interactive().

◆ log_search_signal_cancel()

void log_search_signal_cancel ( void  )

Cancel grep mode from a signal handler (async-signal-safe)

Sets an atomic flag that the status screen loop checks on its next iteration. Does not use mutexes or allocate memory.

Definition at line 376 of file search.c.

376 {
377 atomic_store_bool(&g_search_state.signal_cancelled, true);
378}

References atomic_store_bool(), and log_search_state_t::signal_cancelled.