tui: guide first-time users

This commit is contained in:
m1ngsama 2026-05-21 12:36:06 +08:00
parent 69ddcd2d95
commit 36dbe8d549
5 changed files with 126 additions and 13 deletions

View file

@ -26,6 +26,10 @@
counting escape sequences as visible width or cutting color codes.
- Host-key generation now uses the non-deprecated libssh PKI API on libssh
0.12+ while keeping compatibility with older libssh releases.
- INSERT mode now shows a lightweight first-use hint for sending, browsing,
and `:support`.
- `:support` is now task-oriented around common user goals, and mistyped
commands suggest the nearest known command when possible.
## 2026-05-18 - Interactive input polish

View file

@ -44,6 +44,64 @@ static void append_highlighted(char *output, size_t buf_size, size_t *pos,
}
}
static int min3(int a, int b, int c) {
int m = a < b ? a : b;
return m < c ? m : c;
}
static int command_edit_distance(const char *a, const char *b) {
size_t la = strlen(a);
size_t lb = strlen(b);
int prev[32];
int curr[32];
if (la >= 32 || lb >= 32) {
return 99;
}
for (size_t j = 0; j <= lb; j++) {
prev[j] = (int)j;
}
for (size_t i = 1; i <= la; i++) {
curr[0] = (int)i;
for (size_t j = 1; j <= lb; j++) {
int cost = a[i - 1] == b[j - 1] ? 0 : 1;
curr[j] = min3(prev[j] + 1, curr[j - 1] + 1,
prev[j - 1] + cost);
}
for (size_t j = 0; j <= lb; j++) {
prev[j] = curr[j];
}
}
return prev[lb];
}
static const char *suggest_command(const char *cmd) {
static const char *commands[] = {
"list", "users", "who", "nick", "name", "msg", "w", "inbox",
"last", "search", "mute-joins", "mute", "support", "guide",
"help", "commands", "clear", "cls", "q", "quit", "exit"
};
const char *best = NULL;
int best_distance = 99;
if (!cmd || !*cmd) {
return NULL;
}
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
int distance = command_edit_distance(cmd, commands[i]);
if (distance < best_distance) {
best_distance = distance;
best = commands[i];
}
}
return best_distance <= 2 ? best : NULL;
}
void commands_dispatch(client_t *client) {
char cmd_buf[256];
strncpy(cmd_buf, client->command_input, sizeof(cmd_buf) - 1);
@ -368,9 +426,15 @@ void commands_dispatch(client_t *client) {
return;
} else {
const char *suggestion = suggest_command(cmd);
buffer_appendf(output, sizeof(output), &pos,
"Unknown command: %s\n"
"Type 'help' for available commands\n", cmd);
"Unknown command: %s\n", cmd);
if (suggestion) {
buffer_appendf(output, sizeof(output), &pos,
"Did you mean :%s?\n", suggestion);
}
buffer_appendf(output, sizeof(output), &pos,
"Type :support for guidance or :help for commands\n");
}
cmd_done:

View file

@ -7,23 +7,25 @@ void support_append_interactive_panel(char *buffer, size_t buf_size,
buffer_appendf(buffer, buf_size, pos,
"\033[1;36m支持 · support\033[0m\n"
"\n"
"\033[1;37m快速开始\033[0m\n"
"\033[1;37m第一次进来\033[0m\n"
" INSERT 输入消息Enter 发送ESC 进入 NORMAL\n"
" NORMAL 浏览消息G 回到最新i 继续输入\n"
" COMMAND 按 : 输入命令q/ESC 关闭当前面板\n"
"\n"
"\033[1;37m常用动作\033[0m\n"
" :users 查看在线用户\n"
" :last 20 查看最近 20 条历史\n"
" :search <keyword> 搜索聊天记录\n"
" :msg <user> <text> 私聊\n"
" :inbox 查看私聊收件箱\n"
" :mute-joins 静音加入/离开提示\n"
"\033[1;37m我想...\033[0m\n"
" 看谁在线 :users\n"
" 看最近历史 :last 20\n"
" 搜索聊天记录 :search <keyword>\n"
" 回到最新消息 G 或 End\n"
" 私聊某个人 :msg <user> <text>\n"
" 查看私聊收件箱 :inbox\n"
" 静音进出提示 :mute-joins\n"
"\n"
"\033[1;37m遇到问题\033[0m\n"
" 看不到新消息: 在 NORMAL 按 G 或 End 回到最新\n"
" 粘贴多行文本: 直接粘贴TNT 会等 Enter 后一次发送\n"
" 输入太长: 状态行接近限制时会提示,超出会响铃\n"
" 命令不记得: 输入 :help 看列表,输入 :support 回到这里\n"
" 连接断开: 可能是空闲超时、连接数限制或网络重连\n"
"\n"
"\033[2;37m更多: ? 打开完整按键帮助,:help 查看命令列表\033[0m\n");

View file

@ -7,7 +7,18 @@ void tui_status_append(char *buffer, size_t buf_size, size_t *pos,
if (!buffer || !pos || !client) return;
if (client->mode == MODE_INSERT) {
buffer_appendf(buffer, buf_size, pos, "\033[2;37m\033[0m \033[K");
if (client->width >= 58) {
buffer_appendf(buffer, buf_size, pos,
"\033[2;37m\033[0m "
"\033[2;37mEnter send · Esc browse · :support\033[0m"
"\033[K");
} else if (client->width >= 36) {
buffer_appendf(buffer, buf_size, pos,
"\033[2;37m\033[0m "
"\033[2;37mEnter · Esc · :support\033[0m\033[K");
} else {
buffer_appendf(buffer, buf_size, pos, "\033[2;37m\033[0m \033[K");
}
} else if (client->mode == MODE_NORMAL) {
int total = msg_count;
int range_start = total == 0 ? 0 : start + 1;

View file

@ -64,7 +64,7 @@ set timeout 10
spawn ssh $SSH_OPTS anonymous@127.0.0.1
sleep 1
send -- "tester\r"
expect ""
expect ":support"
send -- "\033\[200~"
send -- "line1\nline2\nline3"
send -- "\033\[201~"
@ -139,7 +139,7 @@ set timeout 10
spawn ssh $SSH_OPTS anonymous@127.0.0.1
sleep 1
send -- "supporter\r"
expect ""
expect ":support"
send -- "\033"
expect "NORMAL"
send -- ":"
@ -165,6 +165,38 @@ else
FAIL=$((FAIL + 1))
fi
UNKNOWN_SCRIPT="$STATE_DIR/unknown-command.expect"
cat >"$UNKNOWN_SCRIPT" <<EOF
set timeout 10
spawn ssh $SSH_OPTS anonymous@127.0.0.1
sleep 1
send -- "mistype\r"
expect ":support"
send -- "\033"
expect "NORMAL"
send -- ":"
expect ":"
send -- "suport\r"
expect "Did you mean :support"
expect "Press any key"
send -- "q"
sleep 0.2
send -- "\003"
sleep 0.2
send -- "\003"
expect eof
EOF
if expect "$UNKNOWN_SCRIPT" >"$STATE_DIR/unknown-command.log" 2>&1; then
echo "✓ mistyped command suggests nearest command"
PASS=$((PASS + 1))
else
echo "x mistyped command suggestion failed"
sed -n '1,160p' "$STATE_DIR/unknown-command.log"
sed -n '1,120p' "$STATE_DIR/server.log"
FAIL=$((FAIL + 1))
fi
echo ""
echo "PASSED: $PASS"
echo "FAILED: $FAIL"