commands: highlight matched keyword in :search results (UX-7)

: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.
This commit is contained in:
m1ngsama 2026-05-17 14:13:21 +08:00
parent 0a013ed40f
commit 0e03c4d216

View file

@ -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 <string.h>
#include <time.h>
/* 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);
}