diff --git a/main.c b/main.c index d749d5a..5663958 100644 --- a/main.c +++ b/main.c @@ -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: : */ + 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: = :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 \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;