96 {
97
98 if (!config || !config->render_header) {
99 return;
100 }
101
102
105 terminal_size_t new_size;
107 g_cached_term_size = new_size;
108 }
109 g_last_term_size_check_us = now_us;
110 }
111
113
114
115
116 static bool last_grep_state = false;
117 if (last_grep_state && !grep_entering) {
118 terminal_screen_clear_cache();
119 }
120
121
123 terminal_screen_clear_cache();
124 }
125
126 last_grep_state = grep_entering;
127
128 if (!grep_entering) {
129
130 terminal_clear_screen();
131 terminal_cursor_home(STDOUT_FILENO);
132 } else {
133
134 terminal_cursor_home(STDOUT_FILENO);
135 }
136
137
138 config->render_header(g_cached_term_size, config->user_data);
139
140
141 if (!config->show_logs) {
142 fflush(stdout);
143 return;
144 }
145
146
147 int log_area_rows = g_cached_term_size.rows - config->fixed_header_lines - 1;
148
149 if (log_area_rows <= 0) {
150 if (grep_entering) {
152 }
153 fflush(stdout);
154 return;
155 }
156
157
158
159
160 int renderable_log_rows = log_area_rows;
161
162
163 session_log_entry_t *log_entries = NULL;
164 size_t log_count = 0;
165
168 if (result != ASCIICHAT_OK || !log_entries) {
169 log_entries = SAFE_MALLOC(SESSION_LOG_BUFFER_SIZE * sizeof(session_log_entry_t), session_log_entry_t *);
170 if (log_entries) {
172 }
173 }
174 } else {
175
176 session_log_entry_t *buffer_entries =
177 SAFE_MALLOC(SESSION_LOG_BUFFER_SIZE * sizeof(session_log_entry_t), session_log_entry_t *);
178 if (buffer_entries) {
180 log_entries = buffer_entries;
181 log_count = buffer_count;
182 }
183 }
184
185
186
187
188 int total_lines_needed = 0;
189 int first_log_to_display = (log_count > 0) ? (int)log_count - 1 : 0;
190
191 for (int i = (int)log_count - 1; i >= 0; i--) {
192 const char *msg = log_entries[i].message;
194 if (msg_display_width < 0) {
195 msg_display_width = (int)strlen(msg);
196 }
197
198 int lines_for_this_log = 1;
199 if (g_cached_term_size.cols > 0 && msg_display_width > g_cached_term_size.cols) {
200 lines_for_this_log = (msg_display_width + g_cached_term_size.cols - 1) / g_cached_term_size.cols;
201 }
202
203 if (total_lines_needed + lines_for_this_log > renderable_log_rows) {
204 first_log_to_display = i + 1;
205 break;
206 }
207
208 total_lines_needed += lines_for_this_log;
209 first_log_to_display = i;
210 }
211
212 if (!grep_entering) {
213
214 for (int i = first_log_to_display; i < (int)log_count; i++) {
215 fprintf(stdout, "%s\n", log_entries[i].message);
216 }
217
218
219 int remaining = log_area_rows - total_lines_needed;
220 for (int i = 0; i < remaining; i++) {
221 fprintf(stdout, "\n");
222 }
223 } else {
224
225
226
227 int log_idx = 0;
228 int lines_used = 0;
229
230 for (int i = first_log_to_display; i < (int)log_count; i++) {
231 const char *original_msg = log_entries[i].message;
232 const char *msg = original_msg;
233
234
235 char plain_text[SESSION_LOG_LINE_MAX] = {0};
236 strip_ansi_codes(original_msg, plain_text, sizeof(plain_text));
237
238
239 size_t match_start = 0, match_len = 0;
241
242 size_t plain_len = strlen(plain_text);
243 if (match_start < plain_len && (match_start + match_len) <= plain_len) {
245 if (highlighted && highlighted[0] != '\0') {
246 msg = highlighted;
247 }
248 }
249 }
250
252 if (msg_display_width < 0) {
253 msg_display_width = (int)strlen(msg);
254 }
255 int lines_for_this = 1;
256 if (g_cached_term_size.cols > 0 && msg_display_width > g_cached_term_size.cols) {
257 lines_for_this = (msg_display_width + g_cached_term_size.cols - 1) / g_cached_term_size.cols;
258 }
259
260
261
262
263 bool same_as_before = false;
265 same_as_before = (log_idx < g_prev_log_count && g_prev_log_ptrs[log_idx] == original_msg);
266 }
267
268 if (same_as_before) {
269
270
271 if (lines_for_this == 1) {
272 fprintf(stdout, "\x1b[0m\n");
273 } else {
274 fprintf(stdout, "\x1b[0m\x1b[%dB", lines_for_this);
275 }
276 } else {
277
278
279
280 fprintf(stdout, "%s\x1b[0m\x1b[K\n", msg);
281 }
282
284 g_prev_log_ptrs[log_idx] = original_msg;
285 }
286 log_idx++;
287 lines_used += lines_for_this;
288 }
289
290
291
292 int remaining = renderable_log_rows - lines_used;
293
294 if (remaining >= 1 && first_log_to_display > 0) {
295
296 int prev_idx = first_log_to_display - 1;
297 const char *prev_msg = log_entries[prev_idx].message;
298
300 if (prev_width < 0) {
301 prev_width = (int)strlen(prev_msg);
302 }
303
304 if (prev_width > g_cached_term_size.cols) {
305
306 int target_width = g_cached_term_size.cols - 3;
307 if (target_width <= 0) {
308 fprintf(stdout, "...\x1b[K\n");
309 } else {
310 size_t src_len = strlen(prev_msg);
311 bool found = false;
312
313 for (size_t truncate_at = src_len; truncate_at > 0; truncate_at--) {
314 char test_buf[SESSION_LOG_LINE_MAX];
315 SAFE_STRNCPY(test_buf, prev_msg, truncate_at);
316 test_buf[truncate_at] = '\0';
317
319 if (test_width < 0) {
320 test_width = (int)strlen(test_buf);
321 }
322
323 if (test_width <= target_width) {
324
325 fprintf(stdout, "%s...\x1b[K\n", test_buf);
326 found = true;
327 break;
328 }
329 }
330
331 if (!found) {
332 fprintf(stdout, "...\x1b[K\n");
333 }
334 }
335 } else {
336
337 fprintf(stdout, "%s\x1b[K\n", prev_msg);
338 }
339
340 remaining--;
341 }
342
343
344 for (int i = 0; i < remaining; i++) {
345 fprintf(stdout, "\x1b[0m\x1b[K\n");
346 }
347
348 g_prev_log_count = log_idx;
349 g_prev_total_lines = lines_used;
350
351
352 fflush(stdout);
353
354
355
356
357
358 char grep_ui_buffer[512];
359 int pos = snprintf(grep_ui_buffer, sizeof(grep_ui_buffer), "\x1b[%d;1H\x1b[0m\x1b[K",
360 g_cached_term_size.rows);
361
362
363 if (pos > 0 && pos < (int)sizeof(grep_ui_buffer) - 256) {
364
365
367 if (grep_mutex) {
368 mutex_lock(grep_mutex);
371
372 if (pattern_len > 0 && pattern) {
373 int remaining = snprintf(grep_ui_buffer + pos, sizeof(grep_ui_buffer) - (size_t)pos,
374 "/%.*s", pattern_len, pattern);
375 if (remaining > 0 && remaining < (int)sizeof(grep_ui_buffer) - (int)pos) {
376 pos += remaining;
377 }
378 } else {
379
380 if (pos + 1 < (int)sizeof(grep_ui_buffer)) {
381 grep_ui_buffer[pos++] = '/';
382 }
383 }
384 mutex_unlock(grep_mutex);
385 }
386 }
387
388
389 if (pos > 0 && pos <= (int)sizeof(grep_ui_buffer)) {
391 }
392 }
393
394 SAFE_FREE(log_entries);
395}
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all data to file descriptor with automatic retry on transient errors.
const char * grep_highlight_colored(const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
bool interactive_grep_is_entering(void)
const char * interactive_grep_get_input_buffer(void)
asciichat_error_t interactive_grep_gather_and_filter_logs(session_log_entry_t **out_entries, size_t *out_count)
bool interactive_grep_is_active(void)
bool interactive_grep_needs_rerender(void)
void * interactive_grep_get_mutex(void)
void interactive_grep_render_input_line(int width)
int interactive_grep_get_input_len(void)
bool interactive_grep_get_match_info(const char *message, size_t *out_match_start, size_t *out_match_len)
int display_width(const char *text)
size_t session_log_buffer_get_recent(session_log_entry_t *out_entries, size_t max_count)
#define TERM_SIZE_CHECK_INTERVAL_US