Fix tab cycling, add /ctcp, PM tab completion, query in status bar
- Fix tab completion cycling (track tab_end properly) - Tab on window 1 cycles through nicks who PM'd you - /ctcp <nick> <command> sends CTCP requests - Query target shown in status bar on window 1 - Channel errors (404 etc) routed to channel window - Long lines wrap properly on window redraw
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
|
||||
#include "charset.h"
|
||||
|
||||
static size_t display_cols(const char *buf, size_t bytes);
|
||||
|
||||
#define BUF_SIZE 4096
|
||||
#define IRC_MAX 512
|
||||
|
||||
@@ -75,6 +77,19 @@ static struct {
|
||||
/* Query target (for /q) */
|
||||
static char query_target[64] = "";
|
||||
|
||||
/* Track nicks who sent us private messages */
|
||||
#define MAX_PM_NICKS 32
|
||||
static char pm_nicks[MAX_PM_NICKS][NICK_LEN];
|
||||
static int pm_nick_count = 0;
|
||||
|
||||
static void pm_nick_add(const char *n)
|
||||
{
|
||||
for (int i = 0; i < pm_nick_count; i++)
|
||||
if (strcasecmp(pm_nicks[i], n) == 0) return;
|
||||
if (pm_nick_count < MAX_PM_NICKS)
|
||||
snprintf(pm_nicks[pm_nick_count++], NICK_LEN, "%s", n);
|
||||
}
|
||||
|
||||
/* Get the active channel for current window, or "" */
|
||||
static const char *current_channel(void)
|
||||
{
|
||||
@@ -113,7 +128,7 @@ static void draw_statusbar(void)
|
||||
snprintf(prefix_str, sizeof(prefix_str), "%c",
|
||||
win_chans[idx].my_prefix);
|
||||
} else {
|
||||
chan = "(status)";
|
||||
chan = query_target[0] ? query_target : "(status)";
|
||||
}
|
||||
|
||||
snprintf(bar, sizeof(bar), " [%d:%s] %s%s %s%s%s",
|
||||
@@ -149,8 +164,7 @@ static void draw_statusbar(void)
|
||||
}
|
||||
|
||||
/* Store a line in a window's scrollback */
|
||||
static void buf_store(int level, const char *line)
|
||||
{
|
||||
static void buf_store(int level, const char *line){
|
||||
if (level < 0 || level >= WL_MAX) return;
|
||||
snprintf(win_buf[level].lines[win_buf[level].head], 512, "%s", line);
|
||||
win_buf[level].head = (win_buf[level].head + 1) % SCROLLBACK;
|
||||
@@ -171,34 +185,43 @@ static void redraw_window(void)
|
||||
if (max_scroll < 0) max_scroll = 0;
|
||||
if (scroll_offset > max_scroll) scroll_offset = max_scroll;
|
||||
|
||||
int show = count - scroll_offset;
|
||||
if (show > visible) show = visible;
|
||||
if (show < 0) show = 0;
|
||||
|
||||
/* Clear scroll region */
|
||||
for (int i = 1; i <= visible; i++)
|
||||
printf("\033[%d;1H\033[K", i);
|
||||
|
||||
/* Calculate start index in circular buffer */
|
||||
int end_pos = count - scroll_offset; /* logical end position */
|
||||
int start_pos = end_pos - show; /* logical start position */
|
||||
/* Figure out how many lines fit, accounting for wrapping */
|
||||
int end_pos = count - scroll_offset;
|
||||
int rows_used = 0;
|
||||
int start_pos = end_pos;
|
||||
|
||||
int buf_start;
|
||||
if (count <= SCROLLBACK)
|
||||
buf_start = start_pos;
|
||||
else
|
||||
buf_start = (win_buf[current_level].head - count + start_pos + SCROLLBACK) % SCROLLBACK;
|
||||
|
||||
/* Print lines aligned to bottom of scroll region */
|
||||
int first_row = visible - show + 1;
|
||||
for (int i = 0; i < show; i++) {
|
||||
int idx;
|
||||
while (start_pos > 0 && rows_used < visible) {
|
||||
int li = start_pos - 1;
|
||||
int buf_idx;
|
||||
if (count <= SCROLLBACK)
|
||||
idx = buf_start + i;
|
||||
buf_idx = li;
|
||||
else
|
||||
idx = (buf_start + i) % SCROLLBACK;
|
||||
printf("\033[%d;1H%s", first_row + i,
|
||||
win_buf[current_level].lines[idx]);
|
||||
buf_idx = (win_buf[current_level].head - count + li + SCROLLBACK) % SCROLLBACK;
|
||||
int line_cols = (int)display_cols(win_buf[current_level].lines[buf_idx],
|
||||
strlen(win_buf[current_level].lines[buf_idx]));
|
||||
int line_rows = line_cols / term_cols + 1;
|
||||
if (rows_used + line_rows > visible) break;
|
||||
rows_used += line_rows;
|
||||
start_pos--;
|
||||
}
|
||||
|
||||
int show = end_pos - start_pos;
|
||||
|
||||
/* Print lines from top, letting terminal wrap naturally */
|
||||
int row = visible - rows_used + 1;
|
||||
printf("\033[%d;1H", row);
|
||||
for (int i = 0; i < show; i++) {
|
||||
int li = start_pos + i;
|
||||
int buf_idx;
|
||||
if (count <= SCROLLBACK)
|
||||
buf_idx = li;
|
||||
else
|
||||
buf_idx = (win_buf[current_level].head - count + li + SCROLLBACK) % SCROLLBACK;
|
||||
printf("\033[K%s\n", win_buf[current_level].lines[buf_idx]);
|
||||
}
|
||||
|
||||
draw_statusbar();
|
||||
@@ -584,6 +607,7 @@ static void handle_line(char *line)
|
||||
}
|
||||
} else if (strcasecmp(target, nick) == 0) {
|
||||
wprintf(WL_MSG, "<%s> %s\n", sender, text);
|
||||
pm_nick_add(sender);
|
||||
} else {
|
||||
int lvl = chan_to_level(target);
|
||||
wprintf(lvl, "<%s> %s\n", sender, text);
|
||||
@@ -724,6 +748,25 @@ static void handle_line(char *line)
|
||||
int lvl = chan_to_level(chan);
|
||||
wprintf(lvl, "* %s changed topic to: %s\n", sender, topic);
|
||||
}
|
||||
} else if ((strcmp(cmd, "404") == 0 || strcmp(cmd, "482") == 0 ||
|
||||
strcmp(cmd, "473") == 0 || strcmp(cmd, "474") == 0 ||
|
||||
strcmp(cmd, "475") == 0) && params) {
|
||||
/* Channel error numerics: <nick> <channel> :<message> */
|
||||
char *p = params;
|
||||
char *sp = strchr(p, ' ');
|
||||
if (sp) {
|
||||
p = sp + 1;
|
||||
char *chan = p;
|
||||
char *text = strchr(p, ':');
|
||||
if (text) {
|
||||
sp = strchr(chan, ' ');
|
||||
if (sp) *sp = '\0';
|
||||
text++;
|
||||
int idx = chan_win_idx(chan);
|
||||
int lvl = idx >= 0 ? WL_CHAN + idx : WL_STATUS;
|
||||
wprintf(lvl, "* %s: %s\n", chan, text);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(cmd, "353") == 0 && params) {
|
||||
/* RPL_NAMREPLY: <nick> = <channel> :names... */
|
||||
char *colon = strchr(params, ':');
|
||||
@@ -828,10 +871,12 @@ static void handle_input(char *line)
|
||||
if (args && args[0]) {
|
||||
snprintf(query_target, sizeof(query_target), "%s", args);
|
||||
wprintf(WL_MSG, "* Now talking to %s\n", query_target);
|
||||
draw_statusbar();
|
||||
} else {
|
||||
if (query_target[0])
|
||||
wprintf(WL_MSG, "* No longer talking to %s\n", query_target);
|
||||
query_target[0] = '\0';
|
||||
draw_statusbar();
|
||||
}
|
||||
} else if (strcasecmp(cmd, "nick") == 0 && args) {
|
||||
snprintf(nick, sizeof(nick), "%s", args);
|
||||
@@ -858,6 +903,19 @@ static void handle_input(char *line)
|
||||
irc_send_raw("PRIVMSG %s :\x01" "ACTION %s\x01", query_target, slap);
|
||||
wprintf(WL_MSG, "* %s %s\n", nick, slap);
|
||||
}
|
||||
} else if (strcasecmp(cmd, "ctcp") == 0 && args) {
|
||||
char *target = args;
|
||||
char *ctcp_cmd = strchr(args, ' ');
|
||||
if (ctcp_cmd) {
|
||||
*ctcp_cmd++ = '\0';
|
||||
/* Uppercase the CTCP command */
|
||||
for (char *p = ctcp_cmd; *p && *p != ' '; p++)
|
||||
*p = (*p >= 'a' && *p <= 'z') ? *p - 32 : *p;
|
||||
irc_send_raw("PRIVMSG %s :\x01%s\x01", target, ctcp_cmd);
|
||||
wprintf(WL_STATUS, "* CTCP %s sent to %s\n", ctcp_cmd, target);
|
||||
} else {
|
||||
wprintf(current_level, "Usage: /ctcp <nick> <command>\n");
|
||||
}
|
||||
} else if (strcasecmp(cmd, "mode") == 0 && args) {
|
||||
irc_send_raw("MODE %s", args);
|
||||
} else if (strcasecmp(cmd, "whois") == 0 && args) {
|
||||
@@ -1083,6 +1141,7 @@ int main(int argc, char *argv[])
|
||||
int tab_idx = -1; /* current tab completion index */
|
||||
size_t tab_prefix_len = 0; /* length of prefix being completed */
|
||||
size_t tab_start = 0; /* byte position where completion word starts */
|
||||
size_t tab_end = 0; /* byte position where last completion ends */
|
||||
|
||||
for (;;) {
|
||||
FD_ZERO(&fds);
|
||||
@@ -1235,51 +1294,58 @@ int main(int argc, char *argv[])
|
||||
redraw_input(input_line, input_len, input_pos);
|
||||
} else if (ch == 0x09) {
|
||||
/* Tab: nick completion */
|
||||
int cidx = -1;
|
||||
if (current_level >= WL_CHAN)
|
||||
cidx = current_level - WL_CHAN;
|
||||
char (*nlist)[NICK_LEN] = NULL;
|
||||
int ncount = 0;
|
||||
|
||||
if (cidx >= 0 && win_chans[cidx].nick_count > 0) {
|
||||
/* Find word start */
|
||||
if (current_level >= WL_CHAN) {
|
||||
int cidx = current_level - WL_CHAN;
|
||||
nlist = win_chans[cidx].nicks;
|
||||
ncount = win_chans[cidx].nick_count;
|
||||
} else {
|
||||
nlist = pm_nicks;
|
||||
ncount = pm_nick_count;
|
||||
}
|
||||
|
||||
if (ncount > 0) {
|
||||
if (tab_idx < 0) {
|
||||
tab_start = input_pos;
|
||||
while (tab_start > 0 &&
|
||||
input_line[tab_start-1] != ' ')
|
||||
tab_start--;
|
||||
tab_prefix_len = input_pos - tab_start;
|
||||
tab_end = input_pos;
|
||||
tab_idx = 0;
|
||||
} else {
|
||||
input_pos = tab_end;
|
||||
tab_idx++;
|
||||
}
|
||||
|
||||
/* Find next matching nick */
|
||||
int found = 0;
|
||||
for (int tries = 0; tries < win_chans[cidx].nick_count; tries++) {
|
||||
int ni = (tab_idx + tries) % win_chans[cidx].nick_count;
|
||||
for (int tries = 0; tries < ncount; tries++) {
|
||||
int ni = (tab_idx + tries) % ncount;
|
||||
if (tab_prefix_len == 0 ||
|
||||
strncasecmp(win_chans[cidx].nicks[ni],
|
||||
strncasecmp(nlist[ni],
|
||||
input_line + tab_start,
|
||||
tab_prefix_len) == 0) {
|
||||
tab_idx = ni;
|
||||
/* Replace from tab_start to input_pos */
|
||||
const char *compl = win_chans[cidx].nicks[ni];
|
||||
const char *compl = nlist[ni];
|
||||
size_t clen = strlen(compl);
|
||||
/* Add ": " if at start of line */
|
||||
char suffix[4] = "";
|
||||
if (tab_start == 0)
|
||||
if (tab_start == 0 && current_level >= WL_CHAN)
|
||||
strcpy(suffix, ": ");
|
||||
else
|
||||
strcpy(suffix, " ");
|
||||
size_t slen = strlen(suffix);
|
||||
size_t tail = input_len - input_pos;
|
||||
size_t tail = input_len - tab_end;
|
||||
input_len = tab_start + clen + slen + tail;
|
||||
if (input_len >= sizeof(input_line) - 1)
|
||||
input_len = sizeof(input_line) - 1;
|
||||
memmove(input_line + tab_start + clen + slen,
|
||||
input_line + input_pos, tail);
|
||||
input_line + tab_end, tail);
|
||||
memcpy(input_line + tab_start, compl, clen);
|
||||
memcpy(input_line + tab_start + clen, suffix, slen);
|
||||
input_pos = tab_start + clen + slen;
|
||||
tab_end = tab_start + clen + slen;
|
||||
input_pos = tab_end;
|
||||
redraw_input(input_line, input_len, input_pos);
|
||||
tab_idx++;
|
||||
found = 1;
|
||||
|
||||
Reference in New Issue
Block a user