From 94f3d28562ecaa2c12523d21306a8e9e9f6ce7e6 Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 17 May 2026 13:35:57 +0800 Subject: [PATCH] =?UTF-8?q?tui:=20cyan=20=E2=96=8E=20gutter=20on=20the=20u?= =?UTF-8?q?ser's=20own=20messages=20(UX-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-messages were rendered identically to anyone else's, so scrolling back to find "what did I just say?" meant scanning every author name. Now every rendered chat line carries a 1-column left gutter: - ▎ (U+258E, cyan) when the message is from the local user - single space otherwise — keeps the rest of the line aligned Detection handles both regular messages (username == my_username) and /me action messages (which use "*" as the author and prefix the actor into the content). System messages ("系统") always render with the space gutter regardless. Gutter width is folded into the existing truncation budget so long messages still fit the terminal. --- src/tui.c | 56 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/tui.c b/src/tui.c index ea56468..0ee9bc0 100644 --- a/src/tui.c +++ b/src/tui.c @@ -30,6 +30,25 @@ static void format_message_colored(const message_t *msg, char *buffer, char time_str[32]; strftime(time_str, sizeof(time_str), "%H:%M", &tm_info); + /* Is this message from the local user? Used to draw a 1-column gutter + * marker so they can scan their own contributions when scrolling. */ + bool is_self = false; + if (my_username && my_username[0] != '\0' && + strcmp(msg->username, "系统") != 0) { + if (strcmp(msg->username, "*") == 0) { + /* /me message: content starts with the actor's username */ + size_t un_len = strlen(my_username); + if (strncmp(msg->content, my_username, un_len) == 0 && + (msg->content[un_len] == ' ' || msg->content[un_len] == '\0')) { + is_self = true; + } + } else if (strcmp(msg->username, my_username) == 0) { + is_self = true; + } + } + /* Always 1 column wide so all messages align vertically. */ + const char *gutter = is_self ? "\033[36m▎\033[0m" : " "; + bool mentioned = false; if (my_username && my_username[0] != '\0' && strcmp(msg->username, "系统") != 0) { @@ -44,39 +63,40 @@ static void format_message_colored(const message_t *msg, char *buffer, if (strcmp(msg->username, "系统") == 0) { snprintf(buffer, buf_size, - "\033[90m--> %s\033[0m", msg->content); + "%s\033[90m--> %s\033[0m", gutter, msg->content); } else if (strcmp(msg->username, "*") == 0) { snprintf(buffer, buf_size, - "\033[90m%s\033[0m \033[3;36m* %s\033[0m", - time_str, msg->content); + "%s\033[90m%s\033[0m \033[3;36m* %s\033[0m", + gutter, time_str, msg->content); } else { snprintf(buffer, buf_size, - "\033[90m%s\033[0m %s%s\033[0m: %s%s%s", - time_str, username_color(msg->username), + "%s\033[90m%s\033[0m %s%s\033[0m: %s%s%s", + gutter, time_str, username_color(msg->username), msg->username, hl_start, msg->content, hl_end); } - /* Plain-text version for width calculation */ + /* Plain-text version for width calculation — gutter is 1 column. */ char plain[MAX_MESSAGE_LEN + 128]; if (strcmp(msg->username, "系统") == 0) { - snprintf(plain, sizeof(plain), "--> %s", msg->content); + snprintf(plain, sizeof(plain), " --> %s", msg->content); } else if (strcmp(msg->username, "*") == 0) { - snprintf(plain, sizeof(plain), "%s * %s", time_str, msg->content); + snprintf(plain, sizeof(plain), " %s * %s", time_str, msg->content); } else { - snprintf(plain, sizeof(plain), "%s %s: %s", + snprintf(plain, sizeof(plain), " %s %s: %s", time_str, msg->username, msg->content); } if (utf8_string_width(plain) > width) { - /* Rebuild with truncated content */ + /* Rebuild with truncated content — prefix_plain also includes the + * 1-column gutter so the budget math comes out right. */ int prefix_width; char prefix_plain[256]; if (strcmp(msg->username, "系统") == 0) { - snprintf(prefix_plain, sizeof(prefix_plain), "--> "); + snprintf(prefix_plain, sizeof(prefix_plain), " --> "); } else if (strcmp(msg->username, "*") == 0) { - snprintf(prefix_plain, sizeof(prefix_plain), "%s * ", time_str); + snprintf(prefix_plain, sizeof(prefix_plain), " %s * ", time_str); } else { - snprintf(prefix_plain, sizeof(prefix_plain), "%s %s: ", + snprintf(prefix_plain, sizeof(prefix_plain), " %s %s: ", time_str, msg->username); } prefix_width = utf8_string_width(prefix_plain); @@ -97,15 +117,15 @@ static void format_message_colored(const message_t *msg, char *buffer, if (strcmp(msg->username, "系统") == 0) { snprintf(buffer, buf_size, - "\033[90m--> %s\033[0m", truncated_content); + "%s\033[90m--> %s\033[0m", gutter, truncated_content); } else if (strcmp(msg->username, "*") == 0) { snprintf(buffer, buf_size, - "\033[90m%s\033[0m \033[3;36m%s\033[0m", - time_str, truncated_content); + "%s\033[90m%s\033[0m \033[3;36m%s\033[0m", + gutter, time_str, truncated_content); } else { snprintf(buffer, buf_size, - "\033[90m%s\033[0m %s%s\033[0m: %s%s%s", - time_str, username_color(msg->username), + "%s\033[90m%s\033[0m %s%s\033[0m: %s%s%s", + gutter, time_str, username_color(msg->username), msg->username, hl_start, truncated_content, hl_end); } }