TNT/src/tui.c
2025-07-01 09:00:00 +08:00

340 lines
11 KiB
C

#include "tui.h"
#include "ssh_server.h"
#include "chat_room.h"
#include "utf8.h"
#include <stdarg.h>
#include <unistd.h>
/* Clear the screen */
void tui_clear_screen(int fd) {
const char *clear = ANSI_CLEAR ANSI_HOME;
write(fd, clear, strlen(clear));
}
/* Render the main screen */
void tui_render_screen(client_t *client) {
if (!client || !client->connected) return;
char buffer[8192];
int pos = 0;
pthread_rwlock_rdlock(&g_room->lock);
int online = g_room->client_count;
int msg_count = g_room->message_count;
pthread_rwlock_unlock(&g_room->lock);
/* Clear and move to top */
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_CLEAR ANSI_HOME);
/* Title bar */
const char *mode_str = (client->mode == MODE_INSERT) ? "INSERT" :
(client->mode == MODE_NORMAL) ? "NORMAL" :
(client->mode == MODE_COMMAND) ? "COMMAND" : "HELP";
char title[256];
snprintf(title, sizeof(title),
" 聊天室 | 在线: %d | 模式: %s | Ctrl+C 退出 | ? 帮助 ",
online, mode_str);
int title_width = utf8_string_width(title);
int padding = client->width - title_width;
if (padding < 0) padding = 0;
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_REVERSE "%s", title);
for (int i = 0; i < padding; i++) {
buffer[pos++] = ' ';
}
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_RESET "\n");
/* Messages area */
int msg_height = client->height - 3;
if (msg_height < 1) msg_height = 1;
/* Calculate which messages to show */
int start = 0;
if (client->mode == MODE_NORMAL) {
start = client->scroll_pos;
if (start > msg_count - msg_height) {
start = msg_count - msg_height;
}
if (start < 0) start = 0;
} else {
/* INSERT mode: show latest */
if (msg_count > msg_height) {
start = msg_count - msg_height;
}
}
pthread_rwlock_rdlock(&g_room->lock);
int end = start + msg_height;
if (end > msg_count) end = msg_count;
for (int i = start; i < end; i++) {
char msg_line[1024];
message_format(&g_room->messages[i], msg_line, sizeof(msg_line), client->width);
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "%s\n", msg_line);
}
pthread_rwlock_unlock(&g_room->lock);
/* Fill empty lines */
for (int i = end - start; i < msg_height; i++) {
buffer[pos++] = '\n';
}
/* Separator - use box drawing character */
for (int i = 0; i < client->width && pos < (int)sizeof(buffer) - 10; i++) {
const char *line_char = ""; /* U+2500 box drawing */
int len = strlen(line_char);
memcpy(buffer + pos, line_char, len);
pos += len;
}
buffer[pos++] = '\n';
/* Status/Input line */
if (client->mode == MODE_INSERT) {
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "> ");
} else if (client->mode == MODE_NORMAL) {
int total = msg_count;
int scroll_pos = client->scroll_pos + 1;
if (total == 0) scroll_pos = 0;
pos += snprintf(buffer + pos, sizeof(buffer) - pos,
"-- NORMAL -- (%d/%d)", scroll_pos, total);
} else if (client->mode == MODE_COMMAND) {
pos += snprintf(buffer + pos, sizeof(buffer) - pos,
":%s", client->command_input);
}
client_send(client, buffer, pos);
}
/* Render the input line */
void tui_render_input(client_t *client, const char *input) {
if (!client || !client->connected) return;
char buffer[2048];
int input_width = utf8_string_width(input);
/* Truncate from start if too long */
char display[1024];
strncpy(display, input, sizeof(display) - 1);
display[sizeof(display) - 1] = '\0';
if (input_width > client->width - 3) {
/* Find where to start displaying */
int excess = input_width - (client->width - 3);
int skip_width = 0;
const char *p = input;
int bytes_read;
while (*p && skip_width < excess) {
uint32_t cp = utf8_decode(p, &bytes_read);
skip_width += utf8_char_width(cp);
p += bytes_read;
}
strncpy(display, p, sizeof(display) - 1);
}
/* Move to input line and clear it, then write input */
snprintf(buffer, sizeof(buffer), "\033[%d;1H" ANSI_CLEAR_LINE "> %s",
client->height, display);
client_send(client, buffer, strlen(buffer));
}
/* Render the command output screen */
void tui_render_command_output(client_t *client) {
if (!client || !client->connected) return;
char buffer[4096];
int pos = 0;
/* Clear screen */
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_CLEAR ANSI_HOME);
/* Title */
const char *title = " COMMAND OUTPUT ";
int title_width = strlen(title);
int padding = client->width - title_width;
if (padding < 0) padding = 0;
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_REVERSE "%s", title);
for (int i = 0; i < padding; i++) {
buffer[pos++] = ' ';
}
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_RESET "\n");
/* Command output */
char *line = strtok(client->command_output, "\n");
int line_count = 0;
int max_lines = client->height - 2;
while (line && line_count < max_lines) {
char truncated[1024];
strncpy(truncated, line, sizeof(truncated) - 1);
truncated[sizeof(truncated) - 1] = '\0';
if (utf8_string_width(truncated) > client->width) {
utf8_truncate(truncated, client->width);
}
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "%s\n", truncated);
line = strtok(NULL, "\n");
line_count++;
}
client_send(client, buffer, pos);
}
/* Get help text based on language */
const char* tui_get_help_text(help_lang_t lang) {
if (lang == LANG_EN) {
return "TERMINAL CHAT ROOM - HELP\n"
"\n"
"OPERATING MODES:\n"
" INSERT - Type and send messages (default)\n"
" NORMAL - Browse message history\n"
" COMMAND - Execute commands\n"
"\n"
"INSERT MODE KEYS:\n"
" ESC - Enter NORMAL mode\n"
" Enter - Send message\n"
" Backspace - Delete character\n"
" Ctrl+C - Exit chat\n"
"\n"
"NORMAL MODE KEYS:\n"
" i - Return to INSERT mode\n"
" : - Enter COMMAND mode\n"
" j - Scroll down (older messages)\n"
" k - Scroll up (newer messages)\n"
" g - Jump to top (oldest)\n"
" G - Jump to bottom (newest)\n"
" ? - Show this help\n"
" Ctrl+C - Exit chat\n"
"\n"
"COMMAND MODE KEYS:\n"
" Enter - Execute command\n"
" ESC - Cancel, return to NORMAL\n"
" Backspace - Delete character\n"
"\n"
"AVAILABLE COMMANDS:\n"
" list, users, who - Show online users\n"
" help, commands - Show available commands\n"
" clear, cls - Clear command output\n"
"\n"
"HELP SCREEN KEYS:\n"
" q, ESC - Close help\n"
" j - Scroll down\n"
" k - Scroll up\n"
" g - Jump to top\n"
" G - Jump to bottom\n"
" e, E - Switch to English\n"
" z, Z - Switch to Chinese\n";
} else {
return "终端聊天室 - 帮助\n"
"\n"
"操作模式:\n"
" INSERT - 输入和发送消息(默认)\n"
" NORMAL - 浏览消息历史\n"
" COMMAND - 执行命令\n"
"\n"
"INSERT 模式按键:\n"
" ESC - 进入 NORMAL 模式\n"
" Enter - 发送消息\n"
" Backspace - 删除字符\n"
" Ctrl+C - 退出聊天\n"
"\n"
"NORMAL 模式按键:\n"
" i - 返回 INSERT 模式\n"
" : - 进入 COMMAND 模式\n"
" j - 向下滚动(更早的消息)\n"
" k - 向上滚动(更新的消息)\n"
" g - 跳到顶部(最早)\n"
" G - 跳到底部(最新)\n"
" ? - 显示此帮助\n"
" Ctrl+C - 退出聊天\n"
"\n"
"COMMAND 模式按键:\n"
" Enter - 执行命令\n"
" ESC - 取消,返回 NORMAL 模式\n"
" Backspace - 删除字符\n"
"\n"
"可用命令:\n"
" list, users, who - 显示在线用户\n"
" help, commands - 显示可用命令\n"
" clear, cls - 清空命令输出\n"
"\n"
"帮助界面按键:\n"
" q, ESC - 关闭帮助\n"
" j - 向下滚动\n"
" k - 向上滚动\n"
" g - 跳到顶部\n"
" G - 跳到底部\n"
" e, E - 切换到英文\n"
" z, Z - 切换到中文\n";
}
}
/* Render the help screen */
void tui_render_help(client_t *client) {
if (!client || !client->connected) return;
char buffer[8192];
int pos = 0;
/* Clear screen */
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_CLEAR ANSI_HOME);
/* Title */
const char *title = " HELP ";
int title_width = strlen(title);
int padding = client->width - title_width;
if (padding < 0) padding = 0;
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_REVERSE "%s", title);
for (int i = 0; i < padding; i++) {
buffer[pos++] = ' ';
}
pos += snprintf(buffer + pos, sizeof(buffer) - pos, ANSI_RESET "\n");
/* Help content */
const char *help_text = tui_get_help_text(client->help_lang);
char help_copy[4096];
strncpy(help_copy, help_text, sizeof(help_copy) - 1);
help_copy[sizeof(help_copy) - 1] = '\0';
/* Split into lines and display with scrolling */
char *lines[100];
int line_count = 0;
char *line = strtok(help_copy, "\n");
while (line && line_count < 100) {
lines[line_count++] = line;
line = strtok(NULL, "\n");
}
int content_height = client->height - 2;
int start = client->help_scroll_pos;
int end = start + content_height - 1;
if (end > line_count) end = line_count;
for (int i = start; i < end && (i - start) < content_height - 1; i++) {
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "%s\n", lines[i]);
}
/* Fill remaining lines */
for (int i = end - start; i < content_height - 1; i++) {
buffer[pos++] = '\n';
}
/* Status line */
int max_scroll = line_count - content_height + 1;
if (max_scroll < 0) max_scroll = 0;
pos += snprintf(buffer + pos, sizeof(buffer) - pos,
"-- HELP -- (%d/%d) j/k:scroll g/G:top/bottom e/z:lang q:close",
client->help_scroll_pos + 1, max_scroll + 1);
client_send(client, buffer, pos);
}