From 0e03c4d21619d9583e5c3d8d0e2797a4b45804e7 Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 17 May 2026 14:13:21 +0800 Subject: [PATCH] commands: highlight matched keyword in :search results (UX-7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :search dumps the matching lines with username and content, but the query word itself was just rendered as-is. In a result set with 15 matches you had to eye-scan each line for the keyword. Now each occurrence of the (case-insensitively matched) needle is wrapped in a reverse-yellow ANSI chip both in the username column and in the content column. Original casing of the matched substring is preserved. Helper: append_highlighted(output, buf_size, &pos, text, needle) emits text into the output buffer with every case-insensitive hit wrapped in `\033[7;33m … \033[0m`. strcasestr() needs _DEFAULT_SOURCE / _DARWIN_C_SOURCE feature macros; the same dance message.c already does is now mirrored in commands.c. --- src/commands.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 1fe3726..641c52f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1,3 +1,9 @@ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE /* for strcasestr() on glibc */ +#endif +#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE) +#define _DARWIN_C_SOURCE /* for strcasestr() on macOS */ +#endif #include "commands.h" #include "chat_room.h" #include "client.h" @@ -10,6 +16,33 @@ #include #include +/* Append `text` to the output buffer with every case-insensitive match of + * `needle` wrapped in a reverse-yellow ANSI chip. Preserves the original + * casing of the matched substring. needle == NULL or empty appends raw. */ +static void append_highlighted(char *output, size_t buf_size, size_t *pos, + const char *text, const char *needle) { + if (!needle || !*needle) { + buffer_appendf(output, buf_size, pos, "%s", text); + return; + } + size_t nlen = strlen(needle); + const char *p = text; + while (*p) { + const char *hit = strcasestr(p, needle); + if (!hit) { + buffer_appendf(output, buf_size, pos, "%s", p); + return; + } + if (hit > p) { + buffer_append_bytes(output, buf_size, pos, p, (size_t)(hit - p)); + } + buffer_append_bytes(output, buf_size, pos, "\033[7;33m", 7); + buffer_append_bytes(output, buf_size, pos, hit, nlen); + buffer_append_bytes(output, buf_size, pos, "\033[0m", 4); + p = hit + nlen; + } +} + void commands_dispatch(client_t *client) { char cmd_buf[256]; strncpy(cmd_buf, client->command_input, sizeof(cmd_buf) - 1); @@ -253,7 +286,13 @@ void commands_dispatch(client_t *client) { localtime_r(&found[i].timestamp, &tmi); strftime(ts, sizeof(ts), "%m-%d %H:%M", &tmi); buffer_appendf(output, sizeof(output), &pos, - "[%s] %s: %s\n", ts, found[i].username, found[i].content); + "[%s] ", ts); + append_highlighted(output, sizeof(output), &pos, + found[i].username, query); + buffer_appendf(output, sizeof(output), &pos, ": "); + append_highlighted(output, sizeof(output), &pos, + found[i].content, query); + buffer_appendf(output, sizeof(output), &pos, "\n"); } free(found); }