mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:44:38 +08:00
input: Tab completes @mentions in INSERT mode (UX-9)
Typing @al<Tab> in INSERT mode now resolves to @alice and appends a trailing space so the next word starts cleanly. Algorithm: 1. walk back from end-of-input until '@' or ' ' is seen 2. '@' counts as a mention start only when at start-of-input or preceded by a space (avoids matching e.g. email@host) 3. case-insensitive strncasecmp against current g_room usernames 4. first hit wins; the search ignores the local user when the prefix is empty (so a lone "@<Tab>" defaults to the first *other* member, matching the typical "ping someone" intent) If the buffer is too short to hold "@<match> ", the completion is a no-op rather than silently truncating the match. Standard chat-client behaviour — much less typing for @mentions.
This commit is contained in:
parent
585262fe4f
commit
6a36cbcb82
1 changed files with 48 additions and 0 deletions
48
src/input.c
48
src/input.c
|
|
@ -11,6 +11,7 @@
|
|||
#include <libssh/callbacks.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/server.h>
|
||||
#include <strings.h> /* strncasecmp */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -322,6 +323,53 @@ static bool handle_key(client_t *client, unsigned char key, char *input) {
|
|||
tui_render_input(client, input);
|
||||
}
|
||||
return true;
|
||||
} else if (key == 9) { /* Tab: complete @mention */
|
||||
/* Walk back from end to find the start of the trailing
|
||||
* "@…" token (an '@' not preceded by an alphanumeric).
|
||||
* If found, scan g_room for the first case-insensitive
|
||||
* username prefix-match (cycling past self) and replace
|
||||
* the token. */
|
||||
size_t in_len = strlen(input);
|
||||
ssize_t at_idx = -1;
|
||||
for (ssize_t i = (ssize_t)in_len - 1; i >= 0; i--) {
|
||||
unsigned char c = (unsigned char)input[i];
|
||||
if (c == '@') {
|
||||
if (i == 0 || input[i - 1] == ' ') at_idx = i;
|
||||
break;
|
||||
}
|
||||
if (c == ' ') break;
|
||||
}
|
||||
if (at_idx >= 0) {
|
||||
const char *prefix = input + at_idx + 1;
|
||||
size_t plen = strlen(prefix);
|
||||
char match[MAX_USERNAME_LEN] = "";
|
||||
pthread_rwlock_rdlock(&g_room->lock);
|
||||
for (int i = 0; i < g_room->client_count; i++) {
|
||||
const char *uname = g_room->clients[i]->username;
|
||||
if (plen == 0
|
||||
? strcmp(uname, client->username) != 0
|
||||
: strncasecmp(uname, prefix, plen) == 0) {
|
||||
strncpy(match, uname, sizeof(match) - 1);
|
||||
match[sizeof(match) - 1] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_unlock(&g_room->lock);
|
||||
if (match[0] != '\0') {
|
||||
/* Replace "@<prefix>" with "@<match> " (trailing
|
||||
* space so the next word starts cleanly). */
|
||||
size_t avail = MAX_MESSAGE_LEN - 1
|
||||
- (size_t)at_idx - 1;
|
||||
size_t mlen = strlen(match);
|
||||
if (mlen + 1 <= avail) {
|
||||
input[at_idx + 1] = '\0';
|
||||
strncat(input, match, avail);
|
||||
strncat(input, " ", 1);
|
||||
tui_render_input(client, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue