ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
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 LOG_SEARCH_OUTPUT_BUFFER_SIZE   512
#define MAX_LOG_SEARCH_PATTERNS   32
 Maximum number of patterns to support.
#define LOG_SEARCH_INPUT_BUFFER_SIZE   256
 Input buffer size.
#define LOG_SEARCH_DEBOUNCE_NS   200000000ULL

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.
void log_search_process_pending (void)
asciichat_error_t log_search_insert_text (const char *text)
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.
asciichat_error_t log_search_filter_buffer (session_log_buffer_t *log_buf, session_log_entry_t **out_entries, size_t *out_count)
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_copy_pattern (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_DEBOUNCE_NS

#define LOG_SEARCH_DEBOUNCE_NS   200000000ULL

Definition at line 43 of file search.c.

Referenced by log_search_handle_key(), and log_search_insert_text().

◆ LOG_SEARCH_INPUT_BUFFER_SIZE

#define LOG_SEARCH_INPUT_BUFFER_SIZE   256

Input buffer size.

Definition at line 42 of file search.c.

Referenced by log_search_get_match_info(), log_search_init(), and log_search_insert_text().

◆ LOG_SEARCH_OUTPUT_BUFFER_SIZE

#define LOG_SEARCH_OUTPUT_BUFFER_SIZE   512

Definition at line 3 of file search.c.

Referenced by log_search_render_input_line().

◆ MAX_LOG_SEARCH_PATTERNS

#define MAX_LOG_SEARCH_PATTERNS   32

Maximum number of patterns to support.

Definition at line 41 of file search.c.

Referenced by log_search_get_match_info().

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 403 of file search.c.

403 {
404 bool expected = true;
405 return atomic_cas_bool(&g_search_state.signal_cancelled, &expected, false);
406}

◆ log_search_copy_pattern()

void * log_search_copy_pattern ( void )

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

Returns
Owned PCRE2 code copy 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 960 of file search.c.

960 {
961 // Use atomic read to check if active
962 int mode = atomic_load_int(&g_search_state.mode_atomic);
963 if (mode == LOG_SEARCH_MODE_INACTIVE) {
964 return NULL;
965 }
966
967 // Safe read under mutex
968 mutex_lock(&g_search_state.mutex);
969
970 // Return the compiled pattern if we have one (not fixed string)
971 void *result = NULL;
972 if (g_search_state.active_pattern_count > 0 && !g_search_state.fixed_string) {
973 result = (void *)pcre2_code_copy(g_search_state.active_patterns[0]);
974 }
975
976 mutex_unlock(&g_search_state.mutex);
977
978 return result;
979}
@ LOG_SEARCH_MODE_INACTIVE
Not in search mode.
Definition search.h:33

References LOG_SEARCH_MODE_INACTIVE.

Referenced by grep_highlight_colored().

◆ 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 203 of file search.c.

203 {
204 if (!lifecycle_shutdown(&g_log_search_lifecycle)) {
205 return; // Already shut down or never initialized
206 }
207
208 mutex_lock(&g_search_state.mutex);
209
210 // Free active patterns
211 for (int i = 0; i < g_search_state.active_pattern_count; i++) {
212 if (g_search_state.active_patterns[i]) {
213 pcre2_code_free(g_search_state.active_patterns[i]);
214 g_search_state.active_patterns[i] = NULL;
215 }
216 }
217
218 mutex_unlock(&g_search_state.mutex);
219 mutex_destroy(&g_search_state.mutex);
220}

Referenced by log_system_destroy().

◆ 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 226 of file search.c.

226 {
227 ascii_otel_ui_usage("logs", "search_started", 0);
228 // Ensure cursor is visible for user typing
229 (void)terminal_cursor_show();
230
231 mutex_lock(&g_search_state.mutex);
232
233 // Save current patterns (CLI --grep patterns)
235 if (result != ASCIICHAT_OK) {
236 log_warn("Failed to save filter patterns");
237 }
238
239 // Clear input buffer
240 memset(g_search_state.input_buffer, 0, sizeof(g_search_state.input_buffer));
241 g_search_state.len = 0;
242 g_search_state.cursor = 0;
243 flow_control_debounce_cancel(&g_search_state.debounce);
244
245 // Pre-populate with CLI --grep pattern if available (only once at startup)
246 if (!g_search_state.cli_pattern_auto_populated) {
247 const char *cli_pattern = grep_get_last_pattern();
248 if (cli_pattern && cli_pattern[0] != '\0') {
249 // Skip leading slash if present (prompt already shows "/")
250 const char *pattern_to_use = cli_pattern;
251 if (cli_pattern[0] == '/') {
252 pattern_to_use = cli_pattern + 1;
253 }
254
255 size_t pattern_len = strlen(pattern_to_use);
256 if (pattern_len < sizeof(g_search_state.input_buffer) - 1) {
257 memcpy(g_search_state.input_buffer, pattern_to_use, pattern_len + 1);
258 g_search_state.len = pattern_len;
259 g_search_state.cursor = pattern_len;
260 g_search_state.cli_pattern_auto_populated = true;
261
262 // Compile and apply the pattern immediately so it starts filtering
263 grep_parse_result_t parsed = grep_parse_pattern(g_search_state.input_buffer);
264 if (parsed.valid) {
265 // Store parsed flags
266 g_search_state.case_insensitive = parsed.case_insensitive;
267 g_search_state.fixed_string = parsed.is_fixed_string;
268 g_search_state.global_highlight = parsed.global_flag;
269 g_search_state.invert_match = parsed.invert;
270 g_search_state.context_before = parsed.context_before;
271 g_search_state.context_after = parsed.context_after;
272
273 // Compile pattern if not fixed string
274 if (strlen(parsed.pattern) > 0 && !parsed.is_fixed_string) {
275 pcre2_code *compiled = asciichat_pcre2_compile(parsed.pattern, parsed.pcre2_options);
276 if (compiled) {
277 pcre2_code *code = compiled;
278 if (code) {
279 if (g_search_state.active_pattern_count > 0)
280 pcre2_code_free(g_search_state.active_patterns[0]);
281 g_search_state.active_patterns[0] = compiled;
282 g_search_state.active_pattern_count = 1;
283 } else {
284 g_search_state.fixed_string = true;
285 }
286 } else {
287 g_search_state.fixed_string = true;
288 }
289 }
290 }
291 }
292 }
293 }
294
295 // Enter input mode
296 g_search_state.mode = LOG_SEARCH_MODE_ENTERING;
297 atomic_store_int(&g_search_state.mode_atomic, LOG_SEARCH_MODE_ENTERING);
298 atomic_store_bool(&g_search_state.needs_rerender, true);
299
300 mutex_unlock(&g_search_state.mutex);
301}
void flow_control_debounce_cancel(flow_control_debounce_t *debounce)
asciichat_error_t grep_save_patterns(void)
Save current filter patterns for later restoration.
Definition grep.c:1093
const char * grep_get_last_pattern(void)
Get the last (most recent) filter pattern string.
Definition grep.c:1135
grep_parse_result_t grep_parse_pattern(const char *input)
Parse pattern in /pattern/flags or pattern/flags format.
Definition grep.c:265
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
void ascii_otel_ui_usage(const char *screen, const char *event, uint64_t duration_ns)
Definition otel.c:434
pcre2_code * asciichat_pcre2_compile(const char *pattern, uint32_t flags)
Definition pcre2.c:16
@ LOG_SEARCH_MODE_ENTERING
'/' pressed, typing pattern
Definition search.h:34
Parsed pattern result from /pattern/flags syntax.
Definition grep.h:22
int context_before
Lines before match (B<n> flag).
Definition grep.h:29
bool global_flag
Highlight all matches (g flag).
Definition grep.h:28
bool is_fixed_string
True if fixed string (not regex).
Definition grep.h:25
bool invert
Invert match (I flag).
Definition grep.h:27
char pattern[4096]
Parsed pattern string.
Definition grep.h:23
bool case_insensitive
Case-insensitive (i flag).
Definition grep.h:26
bool valid
True if parsing succeeded.
Definition grep.h:31
int context_after
Lines after match (A<n> flag).
Definition grep.h:30
uint32_t pcre2_options
PCRE2 compile options.
Definition grep.h:24

References ascii_otel_ui_usage(), ASCIICHAT_OK, asciichat_pcre2_compile(), grep_parse_result_t::case_insensitive, grep_parse_result_t::context_after, grep_parse_result_t::context_before, flow_control_debounce_cancel(), grep_parse_result_t::global_flag, grep_get_last_pattern(), grep_parse_pattern(), grep_save_patterns(), grep_parse_result_t::invert, grep_parse_result_t::is_fixed_string, LOG_SEARCH_MODE_ENTERING, log_warn, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, 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 303 of file search.c.

303 {
304 mutex_lock(&g_search_state.mutex);
305
306 if (g_search_state.mode == LOG_SEARCH_MODE_INACTIVE || (accept && g_search_state.mode != LOG_SEARCH_MODE_ENTERING)) {
307 mutex_unlock(&g_search_state.mutex);
308 return;
309 }
310
311 if (!accept) {
312 flow_control_debounce_cancel(&g_search_state.debounce);
313 ascii_otel_ui_usage("logs", "search_cancelled", 0);
314 // Cancel - restore previous patterns and reset CLI pattern flag so user can re-populate
316 if (result != ASCIICHAT_OK) {
317 log_warn("Failed to restore filter patterns");
318 }
319
320 // Clear interactive grep patterns
321 for (int i = 0; i < g_search_state.active_pattern_count; i++) {
322 pcre2_code_free(g_search_state.active_patterns[i]);
323 g_search_state.active_patterns[i] = NULL;
324 }
325 g_search_state.active_pattern_count = 0;
326
327 // Reset the CLI pattern auto-populated flag so user can re-enter grep mode with the pattern again
328 g_search_state.cli_pattern_auto_populated = false;
329
330 g_search_state.mode = LOG_SEARCH_MODE_INACTIVE;
331 atomic_store_int(&g_search_state.mode_atomic, LOG_SEARCH_MODE_INACTIVE);
332 atomic_store_bool(&g_search_state.needs_rerender, true);
333 mutex_unlock(&g_search_state.mutex);
334 return;
335 }
336
337 // Accept - parse and compile pattern
338 flow_control_debounce_cancel(&g_search_state.debounce);
339 grep_parse_result_t parsed = grep_parse_pattern(g_search_state.input_buffer);
340
341 if (!parsed.valid) {
342 ascii_otel_ui_usage("logs", "search_rejected", 0);
343 log_error("Invalid pattern format");
344 mutex_unlock(&g_search_state.mutex);
345 return; // Stay in input mode
346 }
347
348 // Clear old regex patterns
349 for (int i = 0; i < g_search_state.active_pattern_count; i++) {
350 pcre2_code_free(g_search_state.active_patterns[i]);
351 g_search_state.active_patterns[i] = NULL;
352 }
353 g_search_state.active_pattern_count = 0;
354
355 // Store parsed flags first
356 g_search_state.case_insensitive = parsed.case_insensitive;
357 g_search_state.fixed_string = parsed.is_fixed_string;
358 g_search_state.global_highlight = parsed.global_flag;
359 g_search_state.invert_match = parsed.invert;
360 g_search_state.context_before = parsed.context_before;
361 g_search_state.context_after = parsed.context_after;
362
363 // Compile and store new pattern (if not fixed string)
364 if (strlen(parsed.pattern) > 0 && !parsed.is_fixed_string) {
365 pcre2_code *compiled = asciichat_pcre2_compile(parsed.pattern, parsed.pcre2_options);
366 if (compiled) {
367 // Verify the pattern actually compiles
368 pcre2_code *code = compiled;
369 if (code) {
370 g_search_state.active_patterns[0] = compiled;
371 g_search_state.active_pattern_count = 1;
372 } else {
373 // Compilation failed - fall back to fixed string matching
374 g_search_state.fixed_string = true; // Override to use fixed string instead
375 }
376 } else {
377 // Malloc failed - fall back to fixed string
378 g_search_state.fixed_string = true;
379 }
380 }
381 // For fixed strings, pattern stays in input_buffer and fixed_string=true
382
383 ascii_otel_ui_usage("logs", "filter_applied", 0);
384 g_search_state.mode = LOG_SEARCH_MODE_ACTIVE;
385 atomic_store_int(&g_search_state.mode_atomic, LOG_SEARCH_MODE_ACTIVE);
386 atomic_store_bool(&g_search_state.needs_rerender, true);
387
388 mutex_unlock(&g_search_state.mutex);
389}
asciichat_error_t grep_restore_patterns(void)
Restore previously saved filter patterns.
Definition grep.c:1108
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
@ LOG_SEARCH_MODE_ACTIVE
Pattern accepted and filtering.
Definition search.h:35

References ascii_otel_ui_usage(), ASCIICHAT_OK, asciichat_pcre2_compile(), grep_parse_result_t::case_insensitive, grep_parse_result_t::context_after, grep_parse_result_t::context_before, flow_control_debounce_cancel(), grep_parse_result_t::global_flag, grep_parse_pattern(), grep_restore_patterns(), grep_parse_result_t::invert, grep_parse_result_t::is_fixed_string, log_error, LOG_SEARCH_MODE_ACTIVE, LOG_SEARCH_MODE_ENTERING, LOG_SEARCH_MODE_INACTIVE, log_warn, 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_filter_buffer()

asciichat_error_t log_search_filter_buffer ( session_log_buffer_t * log_buf,
session_log_entry_t ** out_entries,
size_t * out_count )

Definition at line 574 of file search.c.

575 {
576 if (!out_entries || !out_count) {
577 return SET_ERRNO(ERROR_INVALID_PARAM, "out_entries and out_count must not be NULL");
578 }
579
580 // Get logs from in-memory buffer
581 session_log_entry_t *buffer_entries =
583 if (!buffer_entries) {
584 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate log buffer");
585 }
586
587 size_t buffer_count = session_log_buffer_get_recent(log_buf, buffer_entries, SESSION_LOG_BUFFER_SIZE);
588
589 // Check if filtering is active (either regex patterns or fixed string)
590 mutex_lock(&g_search_state.mutex);
591 bool has_regex_patterns = (g_search_state.active_pattern_count > 0);
592 bool has_fixed_string = (g_search_state.fixed_string && g_search_state.len > 0);
593 bool filtering_active = has_regex_patterns || has_fixed_string;
594
595 if (!filtering_active) {
596 // No filtering active - return all entries
597 mutex_unlock(&g_search_state.mutex);
598 *out_entries = buffer_entries;
599 *out_count = buffer_count;
600 return ASCIICHAT_OK;
601 }
602
603 // Filter entries with PCRE2
604 session_log_entry_t *filtered =
606 if (!filtered) {
607 mutex_unlock(&g_search_state.mutex);
608 SAFE_FREE(buffer_entries);
609 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate filtered buffer");
610 }
611
612 size_t filtered_count = 0;
613
614 // Create match data for PCRE2 matching
615 pcre2_match_data *match_data = NULL;
616 for (int p = 0; p < g_search_state.active_pattern_count; p++) {
617 if (g_search_state.active_patterns[p]) {
618 pcre2_code *code = g_search_state.active_patterns[p];
619 if (code) {
620 match_data = pcre2_match_data_create_from_pattern(code, NULL);
621 break;
622 }
623 }
624 }
625
626 if (!match_data && g_search_state.active_pattern_count > 0) {
627 // Couldn't create match data - fall back to returning all entries
628 mutex_unlock(&g_search_state.mutex);
629 SAFE_FREE(filtered);
630 *out_entries = buffer_entries;
631 *out_count = buffer_count;
632 return ASCIICHAT_OK;
633 }
634
635 // Parse pattern once before loop (for fixed string mode)
636 grep_parse_result_t parsed = {0};
637 if (has_fixed_string) {
638 parsed = grep_parse_pattern(g_search_state.input_buffer);
639 }
640
641 for (size_t i = 0; i < buffer_count; i++) {
642 const char *original_message = buffer_entries[i].message;
643 bool matches = false;
644
645 // Strip ANSI codes to match against plain text (consistent with highlighting)
646 char plain_message[SESSION_LOG_LINE_MAX] = {0};
647 const char *strip_ansi_codes_fn(const char *src, char *dst, size_t dst_size);
648 // Use inline stripping to avoid function call overhead
649 {
650 size_t pos = 0;
651 const char *src = original_message;
652 char *dst = plain_message;
653 size_t dst_size = sizeof(plain_message);
654
655 while (*src && pos < dst_size - 1) {
656 if (*src == '\x1b' && src[1] != '\0' && src[1] == '[') {
657 // CSI sequence - skip until final byte
658 src += 2;
659 while (*src && pos < dst_size - 1 && !(*src >= 0x40 && *src <= 0x7E)) {
660 src++;
661 }
662 if (*src)
663 src++; // Skip final byte
664 } else {
665 dst[pos++] = *src++;
666 }
667 }
668 dst[pos] = '\0';
669 }
670
671 size_t message_len = strlen(plain_message);
672
673 // Fixed string matching
674 if (has_fixed_string) {
675 const char *search_pattern = parsed.pattern;
676
677 if (g_search_state.case_insensitive) {
678 // Unicode-aware case-insensitive fixed string search
679 matches = (utf8_strcasestr(plain_message, search_pattern) != NULL);
680 } else {
681 // Case-sensitive fixed string search
682 matches = (strstr(plain_message, search_pattern) != NULL);
683 }
684 }
685 // Regex pattern matching
686 else if (has_regex_patterns) {
687 // Check against all active patterns (OR logic)
688 for (int p = 0; p < g_search_state.active_pattern_count; p++) {
689 pcre2_code *compiled = g_search_state.active_patterns[p];
690 if (!compiled) {
691 continue;
692 }
693
694 pcre2_code *code = compiled;
695 if (!code) {
696 continue;
697 }
698
699 // Match against plain message (without ANSI codes)
700 int rc = pcre2_match(code, (PCRE2_SPTR)plain_message, message_len, 0, 0, match_data, NULL);
701 if (rc >= 0) {
702 matches = true;
703 break;
704 }
705 }
706 }
707
708 // Apply invert logic
709 if (g_search_state.invert_match) {
710 matches = !matches;
711 }
712
713 if (matches) {
714 filtered[filtered_count++] = buffer_entries[i];
715 }
716 }
717
718 // Free match data
719 if (match_data) {
720 pcre2_match_data_free(match_data);
721 }
722
723 mutex_unlock(&g_search_state.mutex);
724
725 SAFE_FREE(buffer_entries);
726 *out_entries = filtered;
727 *out_count = filtered_count;
728 return ASCIICHAT_OK;
729}
#define SET_ERRNO(code,...)
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
@ ERROR_MEMORY
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
const char * utf8_strcasestr(const char *haystack, const char *needle)
Case-insensitive substring search with full Unicode support.
Definition utf8.c:205
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.
#define SESSION_LOG_BUFFER_SIZE
#define SESSION_LOG_LINE_MAX
One formatted session log line and its monotonic history sequence.
char message[1024]
Formatted log message (may include ANSI colors).

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_MEMORY, grep_parse_pattern(), session_log_entry_t::message, 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 log_search_gather_and_filter_logs(), and terminal_screen_compose().

◆ 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 569 of file search.c.

569 {
571 return log_search_filter_buffer(logs ? logs : log_get_session_log_buffer(), out_entries, out_count);
572}
struct session_log_buffer session_log_buffer_t
Thread-safe fixed-size history ring for session-screen log lines.
session_log_buffer_t * log_get_session_log_buffer(void)
Get the currently registered session log buffer.
Definition log.c:2071
session_log_buffer_t * log_get_history_buffer(void)
Definition log.c:565
asciichat_error_t log_search_filter_buffer(session_log_buffer_t *log_buf, session_log_entry_t **out_entries, size_t *out_count)
Definition search.c:574

References log_get_history_buffer(), log_get_session_log_buffer(), and log_search_filter_buffer().

◆ 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 1001 of file search.c.

1001 {
1002 return g_search_state.case_insensitive;
1003}

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 993 of file search.c.

993 {
994 return (int)g_search_state.cursor;
995}

Referenced by terminal_screen_compose().

◆ 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 945 of file search.c.

945 {
946 // Use atomic read to avoid mutex contention with keyboard handler
947 int mode = atomic_load_int(&g_search_state.mode_atomic);
948 if (mode == LOG_SEARCH_MODE_INACTIVE) {
949 return false;
950 }
951
952 // Safe read under mutex (quick operation)
953 mutex_lock(&g_search_state.mutex);
954 bool result = g_search_state.global_highlight;
955 mutex_unlock(&g_search_state.mutex);
956
957 return result;
958}

References LOG_SEARCH_MODE_INACTIVE.

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 997 of file search.c.

997 {
998 return g_search_state.input_buffer;
999}

Referenced by grep_highlight_colored(), and terminal_screen_compose().

◆ 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 989 of file search.c.

989 {
990 return (int)g_search_state.len;
991}

Referenced by grep_highlight_colored().

◆ 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 773 of file search.c.

773 {
774 if (!message || !out_match_start || !out_match_len) {
775 return false;
776 }
777
778 *out_match_start = 0;
779 *out_match_len = 0;
780
781 // Use atomic read to avoid mutex contention with keyboard handler
782 int mode = atomic_load_int(&g_search_state.mode_atomic);
783 if (mode == LOG_SEARCH_MODE_INACTIVE) {
784 return false;
785 }
786
787 // Make a safe copy of state under mutex, then release before doing expensive work
788 mutex_lock(&g_search_state.mutex);
789
790 bool has_fixed_string = g_search_state.fixed_string && g_search_state.len > 0;
791 bool case_insensitive = g_search_state.case_insensitive;
792 char pattern_copy[LOG_SEARCH_INPUT_BUFFER_SIZE];
793 size_t pattern_len = g_search_state.len;
794 int pattern_count = g_search_state.active_pattern_count;
795 pcre2_code *patterns_copy[MAX_LOG_SEARCH_PATTERNS] = {0};
796
797 if (has_fixed_string) {
798 // Parse the input buffer to extract just the pattern part (without flags)
799 grep_parse_result_t parsed = grep_parse_pattern(g_search_state.input_buffer);
800 const char *src = parsed.valid ? parsed.pattern : g_search_state.input_buffer;
801 SAFE_STRNCPY(pattern_copy, src, sizeof(pattern_copy) - 1);
802 pattern_copy[sizeof(pattern_copy) - 1] = '\0';
803 pattern_len = strlen(pattern_copy); // Update pattern_len for the actual parsed pattern
804 }
805
806 for (int i = 0; i < pattern_count && i < MAX_LOG_SEARCH_PATTERNS; i++) {
807 if (g_search_state.active_patterns[i])
808 patterns_copy[i] = pcre2_code_copy(g_search_state.active_patterns[i]);
809 }
810
811 mutex_unlock(&g_search_state.mutex);
812
813 // Now do matching without holding mutex
814 size_t message_len = strlen(message);
815 bool matches = false;
816
817 // Fixed string matching
818 if (has_fixed_string) {
819 const char *found = NULL;
820
821 if (case_insensitive) {
822 found = utf8_strcasestr(message, pattern_copy);
823 } else {
824 found = strstr(message, pattern_copy);
825 }
826
827 if (found) {
828 matches = true;
829
830 // Convert byte position to character position (UTF-8 aware)
831 size_t byte_pos = (size_t)(found - message);
832 size_t char_pos = 0;
833 for (size_t i = 0; i < byte_pos && message[i] != '\0';) {
834 uint32_t codepoint;
835 int utf8_len = utf8_decode((const uint8_t *)(message + i), &codepoint);
836 if (utf8_len <= 0)
837 utf8_len = 1;
838 i += utf8_len;
839 char_pos++;
840 }
841
842 // Count characters in the match (pattern_len is in bytes for the pattern)
843 size_t match_char_len = 0;
844 for (size_t i = 0; i < pattern_len && found[i] != '\0';) {
845 uint32_t codepoint;
846 int utf8_len = utf8_decode((const uint8_t *)(found + i), &codepoint);
847 if (utf8_len <= 0)
848 utf8_len = 1;
849 i += utf8_len;
850 match_char_len++;
851 }
852
853 *out_match_start = char_pos;
854 *out_match_len = match_char_len;
855 }
856 }
857 // Regex pattern matching
858 else if (pattern_count > 0) {
859 pcre2_match_data *match_data = NULL;
860 for (int p = 0; p < pattern_count; p++) {
861 if (patterns_copy[p]) {
862 pcre2_code *code = patterns_copy[p];
863 if (code) {
864 match_data = pcre2_match_data_create_from_pattern(code, NULL);
865 break;
866 }
867 }
868 }
869
870 if (match_data) {
871 for (int p = 0; p < pattern_count; p++) {
872 pcre2_code *compiled = patterns_copy[p];
873 if (!compiled) {
874 continue;
875 }
876
877 pcre2_code *code = compiled;
878 if (!code) {
879 continue;
880 }
881
882 int rc = pcre2_match(code, (PCRE2_SPTR)message, message_len, 0, 0, match_data, NULL);
883 if (rc >= 0) {
884 matches = true;
885 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
886
887 // Convert byte positions to character positions (UTF-8 aware)
888 size_t byte_start = (size_t)ovector[0];
889 size_t byte_end = (size_t)ovector[1];
890
891 // Count characters up to byte_start
892 size_t char_start = 0;
893 for (size_t i = 0; i < byte_start && message[i] != '\0';) {
894 uint32_t codepoint;
895 int utf8_len = utf8_decode((const uint8_t *)(message + i), &codepoint);
896 if (utf8_len <= 0)
897 utf8_len = 1;
898 i += utf8_len;
899 char_start++;
900 }
901
902 // Count characters up to byte_end
903 size_t char_end = char_start;
904 for (size_t i = byte_start; i < byte_end && message[i] != '\0';) {
905 uint32_t codepoint;
906 int utf8_len = utf8_decode((const uint8_t *)(message + i), &codepoint);
907 if (utf8_len <= 0)
908 utf8_len = 1;
909 i += utf8_len;
910 char_end++;
911 }
912
913 *out_match_start = char_start;
914 *out_match_len = char_end - char_start;
915 break;
916 }
917 }
918 pcre2_match_data_free(match_data);
919 }
920 }
921
922 for (int i = 0; i < pattern_count && i < MAX_LOG_SEARCH_PATTERNS; i++) {
923 if (patterns_copy[i])
924 pcre2_code_free(patterns_copy[i]);
925 }
926 return matches;
927}
int pattern_count
Number of active patterns.
Definition grep.c:60
unsigned int uint32_t
Definition common.h:62
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
unsigned char uint8_t
Definition common.h:60
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
Decode a UTF-8 sequence to a Unicode codepoint.
Definition utf8.c:27
#define MAX_LOG_SEARCH_PATTERNS
Maximum number of patterns to support.
Definition search.c:41
#define LOG_SEARCH_INPUT_BUFFER_SIZE
Input buffer size.
Definition search.c:42

References grep_parse_pattern(), LOG_SEARCH_INPUT_BUFFER_SIZE, LOG_SEARCH_MODE_INACTIVE, MAX_LOG_SEARCH_PATTERNS, grep_parse_result_t::pattern, pattern_count, SAFE_STRNCPY, utf8_decode(), utf8_strcasestr(), and grep_parse_result_t::valid.

◆ 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 985 of file search.c.

985 {
986 return (void *)&g_search_state.mutex;
987}

Referenced by terminal_screen_compose().

◆ 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 508 of file search.c.

508 {
509 mutex_lock(&g_search_state.mutex);
510
511 // If not in input mode, check if user pressed '/'
512 if (g_search_state.mode != LOG_SEARCH_MODE_ENTERING) {
513 mutex_unlock(&g_search_state.mutex);
514
515 // Check for '/' key (key already passed as parameter, don't read again!)
516 if (key == '/') {
518 }
519 return ASCIICHAT_OK;
520 }
521
522 // In input mode - use keyboard_read_line_interactive()
523 // Keep the mutex held during editing to prevent races
524 // with the rendering thread. The validator callback and buffer modifications must be
525 // synchronized with readers in log_search_gather_and_filter_logs().
527 .buffer = g_search_state.input_buffer,
528 .max_len = sizeof(g_search_state.input_buffer),
529 .len = &g_search_state.len,
530 .cursor = &g_search_state.cursor,
531 .echo = false, // We handle rendering ourselves
532 .mask_char = 0, // No masking
533 .prefix = NULL, // Don't render prefix - log_search_render_input_line() handles it
534 .validator = validate_pcre2_pattern, // Live validation
535 .key = key // Pass the pre-read key
536 };
537
539
540 switch (result) {
542 // Unlock before exit_mode since it acquires the mutex
543 mutex_unlock(&g_search_state.mutex);
544 log_search_exit_mode(true); // Parse and accept pattern
545 break;
547 // Unlock before exit_mode since it acquires the mutex
548 mutex_unlock(&g_search_state.mutex);
549 log_search_exit_mode(false); // Restore previous
550 break;
552 flow_control_debounce_trigger(&g_search_state.debounce, time_get_ns(), LOG_SEARCH_DEBOUNCE_NS);
553 mutex_unlock(&g_search_state.mutex);
554 atomic_store_bool(&g_search_state.needs_rerender, true);
555 break;
557 // No input available
558 mutex_unlock(&g_search_state.mutex);
559 break;
560 }
561
562 return ASCIICHAT_OK;
563}
void flow_control_debounce_trigger(flow_control_debounce_t *debounce, uint64_t now_ns, uint64_t delay_ns)
Definition flow_control.c:3
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:223
@ LINE_EDIT_ACCEPTED
User pressed Enter (accept input).
Definition keyboard.h:225
@ LINE_EDIT_CANCELLED
User pressed Escape/Ctrl+C (cancel input).
Definition keyboard.h:226
@ LINE_EDIT_CONTINUE
Keep editing (more input needed).
Definition keyboard.h:224
@ LINE_EDIT_NO_INPUT
No key available (non-blocking mode).
Definition keyboard.h:227
void log_search_enter_mode(void)
Enter search mode (user pressed '/').
Definition search.c:226
void log_search_exit_mode(bool accept)
Exit search mode.
Definition search.c:303
#define LOG_SEARCH_DEBOUNCE_NS
Definition search.c:43
Options for interactive line editing.
Definition keyboard.h:203

References ASCIICHAT_OK, flow_control_debounce_trigger(), keyboard_read_line_interactive(), LINE_EDIT_ACCEPTED, LINE_EDIT_CANCELLED, LINE_EDIT_CONTINUE, LINE_EDIT_NO_INPUT, LOG_SEARCH_DEBOUNCE_NS, log_search_enter_mode(), log_search_exit_mode(), and LOG_SEARCH_MODE_ENTERING.

Referenced by 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 144 of file search.c.

144 {
145 // Use lifecycle API for unified initialization
146 if (!lifecycle_init_once(&g_log_search_lifecycle)) {
147 // Already initialized.
148 return ASCIICHAT_OK;
149 }
150
151 memset(&g_search_state, 0, sizeof(g_search_state));
152 if (mutex_init(&g_search_state.mutex, "log_search") != 0) {
153 lifecycle_init_abort(&g_log_search_lifecycle);
154 return SET_ERRNO(ERROR_THREAD, "Cannot initialize log search");
155 }
156 mutex_lock(&g_search_state.mutex);
157
158 // Check if there are CLI --grep patterns to use
159 const char *cli_pattern = grep_get_last_pattern();
160
161 // Start in INACTIVE mode if no CLI pattern is provided.
162 // Only use patterns from CLI --grep arguments, not default patterns.
163 // This ensures logs are visible by default and only filtered if explicitly requested.
164 if (!cli_pattern || cli_pattern[0] == '\0') {
165 // No CLI pattern - start inactive
166 g_search_state.mode = LOG_SEARCH_MODE_INACTIVE;
167 atomic_store_int(&g_search_state.mode_atomic, LOG_SEARCH_MODE_INACTIVE);
168 lifecycle_init_commit(&g_log_search_lifecycle);
169 mutex_unlock(&g_search_state.mutex);
170 return ASCIICHAT_OK;
171 }
172
173 // Load CLI pattern into input buffer
174 SAFE_STRNCPY(g_search_state.input_buffer, cli_pattern, LOG_SEARCH_INPUT_BUFFER_SIZE - 1);
175 g_search_state.len = strlen(cli_pattern);
176 g_search_state.cursor = g_search_state.len;
177 g_search_state.mode = LOG_SEARCH_MODE_ACTIVE;
178 atomic_store_int(&g_search_state.mode_atomic, LOG_SEARCH_MODE_ACTIVE);
179
180 // Compile the initial pattern
181 grep_parse_result_t parsed = grep_parse_pattern(cli_pattern);
182 if (parsed.valid) {
183 pcre2_code *compiled = asciichat_pcre2_compile(parsed.pattern, parsed.pcre2_options);
184 if (compiled) {
185 g_search_state.active_patterns[0] = compiled;
186 g_search_state.active_pattern_count = 1;
187 }
188 g_search_state.case_insensitive = parsed.case_insensitive;
189 g_search_state.fixed_string = parsed.is_fixed_string;
190 g_search_state.global_highlight = parsed.global_flag;
191 g_search_state.invert_match = parsed.invert;
192 g_search_state.context_before = parsed.context_before;
193 g_search_state.context_after = parsed.context_after;
194 }
195
196 atomic_store_bool(&g_search_state.needs_rerender, true);
197 lifecycle_init_commit(&g_log_search_lifecycle);
198
199 mutex_unlock(&g_search_state.mutex);
200 return ASCIICHAT_OK;
201}
@ ERROR_THREAD

References ASCIICHAT_OK, asciichat_pcre2_compile(), grep_parse_result_t::case_insensitive, grep_parse_result_t::context_after, grep_parse_result_t::context_before, ERROR_THREAD, grep_parse_result_t::global_flag, grep_get_last_pattern(), grep_parse_pattern(), grep_parse_result_t::invert, grep_parse_result_t::is_fixed_string, LOG_SEARCH_INPUT_BUFFER_SIZE, LOG_SEARCH_MODE_ACTIVE, LOG_SEARCH_MODE_INACTIVE, grep_parse_result_t::pattern, grep_parse_result_t::pcre2_options, SAFE_STRNCPY, SET_ERRNO, and grep_parse_result_t::valid.

Referenced by terminal_screen_log_init(), and ui_session_create().

◆ log_search_insert_text()

asciichat_error_t log_search_insert_text ( const char * text)

Atomically insert UTF-8 paste into an active search; controls become spaces.

Definition at line 480 of file search.c.

480 {
481 if (!text || strlen(text) >= LOG_SEARCH_INPUT_BUFFER_SIZE)
482 return SET_ERRNO(ERROR_INVALID_PARAM, "Search paste exceeds input capacity");
484 return SET_ERRNO(ERROR_INVALID_STATE, "Search input is not active");
485 char normalized[LOG_SEARCH_INPUT_BUFFER_SIZE];
486 size_t i;
487 for (i = 0; text[i]; ++i) {
488 unsigned char byte = (unsigned char)text[i];
489 normalized[i] = byte < 32 || byte == 127 ? ' ' : (char)byte;
490 }
491 normalized[i] = '\0';
492 mutex_lock(&g_search_state.mutex);
494 if (g_search_state.mode != LOG_SEARCH_MODE_ENTERING)
495 result = SET_ERRNO(ERROR_INVALID_STATE, "Search input is not active");
496 else {
497 result = ui_input_insert(g_search_state.input_buffer, sizeof(g_search_state.input_buffer), &g_search_state.len,
498 &g_search_state.cursor, normalized);
499 if (result == ASCIICHAT_OK)
500 flow_control_debounce_trigger(&g_search_state.debounce, time_get_ns(), LOG_SEARCH_DEBOUNCE_NS);
501 }
502 mutex_unlock(&g_search_state.mutex);
503 if (result == ASCIICHAT_OK)
504 atomic_store_bool(&g_search_state.needs_rerender, true);
505 return result;
506}
@ ERROR_INVALID_STATE
bool log_search_is_entering(void)
Check if currently in input mode (typing pattern).
Definition search.c:408
asciichat_error_t ui_input_insert(char *buffer, size_t capacity, size_t *length, size_t *cursor, const char *text)
Definition ui/input.c:130

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, flow_control_debounce_trigger(), LOG_SEARCH_DEBOUNCE_NS, LOG_SEARCH_INPUT_BUFFER_SIZE, log_search_is_entering(), LOG_SEARCH_MODE_ENTERING, SET_ERRNO, and ui_input_insert().

Referenced by ui_input_read_key().

◆ 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 413 of file search.c.

413 {
414 // Use atomic read (async-signal-safe) to avoid mutex issues when called from signal handlers
415 return atomic_load_int(&g_search_state.mode_atomic) != LOG_SEARCH_MODE_INACTIVE;
416}

References LOG_SEARCH_MODE_INACTIVE.

Referenced by grep_highlight_colored(), terminal_screen_compose(), 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 408 of file search.c.

408 {
409 // Use atomic read (async-signal-safe) to avoid mutex issues when called from signal handlers
410 return atomic_load_int(&g_search_state.mode_atomic) == LOG_SEARCH_MODE_ENTERING;
411}

References LOG_SEARCH_MODE_ENTERING.

Referenced by log_search_insert_text(), session_handle_keyboard_input(), terminal_screen_compose(), terminal_screen_render(), ui_input_poll_sync(), and ui_input_read_key().

◆ 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 395 of file search.c.

395 {
396 return atomic_load_int(&g_search_state.mode_atomic) == LOG_SEARCH_MODE_ENTERING;
397}

References LOG_SEARCH_MODE_ENTERING.

◆ 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 933 of file search.c.

933 {
934 bool needs = atomic_load_bool(&g_search_state.needs_rerender);
935 if (needs) {
936 atomic_store_bool(&g_search_state.needs_rerender, false);
937 }
938 return needs;
939}

◆ log_search_process_pending()

void log_search_process_pending ( void )

Apply a pending interactive-search action after its debounce interval.

Definition at line 469 of file search.c.

469 {
470 mutex_lock(&g_search_state.mutex);
471 bool ready = g_search_state.mode == LOG_SEARCH_MODE_ENTERING &&
472 flow_control_debounce_take_ready(&g_search_state.debounce, time_get_ns());
473 if (ready)
474 log_search_update_filter_locked();
475 mutex_unlock(&g_search_state.mutex);
476 if (ready)
477 atomic_store_bool(&g_search_state.needs_rerender, true);
478}
bool flow_control_debounce_take_ready(flow_control_debounce_t *debounce, uint64_t now_ns)

References flow_control_debounce_take_ready(), and LOG_SEARCH_MODE_ENTERING.

Referenced by terminal_screen_compose().

◆ 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 735 of file search.c.

735 {
736 (void)width; // Reserved for future use (line truncation)
737
738 mutex_lock(&g_search_state.mutex);
739
740 if (g_search_state.mode != LOG_SEARCH_MODE_ENTERING) {
741 mutex_unlock(&g_search_state.mutex);
742 return;
743 }
744
745 // Render the pattern and position terminal cursor at current edit position
746 char output_buf[LOG_SEARCH_OUTPUT_BUFFER_SIZE];
747
748 // Build output: "/" + pattern
749 int pattern_len =
750 snprintf(output_buf, sizeof(output_buf), "/%.*s", (int)g_search_state.len, g_search_state.input_buffer);
751
752 if (pattern_len > 0 && pattern_len < (int)sizeof(output_buf)) {
753 // Write the pattern to terminal
754 ui_controller_write(STDOUT_FILENO, output_buf, (size_t)pattern_len);
755
756 // After writing, cursor is at: column = 1 (for "/") + len + 1
757 // We want it at: column = 1 (for "/") + cursor_position + 1
758 // So we need to move back by: len - cursor_position columns
759 int cursor_offset = (int)g_search_state.len - (int)g_search_state.cursor;
760 if (cursor_offset > 0) {
761 // Move left by the difference
762 ui_controller_printf(STDOUT_FILENO, "\033[%dD", cursor_offset);
763 }
764 }
765
766 mutex_unlock(&g_search_state.mutex);
767}
unsigned int width
Definition fft.c:23
#define LOG_SEARCH_OUTPUT_BUFFER_SIZE
Definition search.c:3
asciichat_error_t ui_controller_write(int fd, const char *data, size_t len)
asciichat_error_t ui_controller_printf(int fd, const char *format,...)

References LOG_SEARCH_MODE_ENTERING, LOG_SEARCH_OUTPUT_BUFFER_SIZE, ui_controller_printf(), ui_controller_write(), and width.

◆ 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 422 of file search.c.

422 {
423 mutex_lock(&g_search_state.mutex);
424
425 // If in input mode, handle all keys
426 if (g_search_state.mode == LOG_SEARCH_MODE_ENTERING) {
427 mutex_unlock(&g_search_state.mutex);
428 return true;
429 }
430
431 // If not in input mode, only handle '/' to enter mode
432 bool should_handle = (key == '/');
433 mutex_unlock(&g_search_state.mutex);
434 return should_handle;
435}

References LOG_SEARCH_MODE_ENTERING.

Referenced by 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 399 of file search.c.

399 {
400 atomic_store_bool(&g_search_state.signal_cancelled, true);
401}