jcs
/wallops
/amendments
/109
chatter: Implement tab completion of nicks
jcs made amendment 109 2 months ago
--- chatter.c Mon Sep 16 10:34:36 2024
+++ chatter.c Mon Sep 16 11:56:02 2024
@@ -40,6 +40,7 @@ void chatter_focus_tab(struct chatter *chatter, struct
void chatter_draw_grow_icon(struct chatter *chatter);
void chatter_autoscroll(struct chatter *chatter, TEHandle te,
ControlHandle scroller, bool force);
+void chatter_set_input(struct chatter *chatter, char *input, size_t len);
short chatter_wait_type(struct focusable *focusable);
void chatter_key_down(struct focusable *focusable, EventRecord *event);
void chatter_mouse_down(struct focusable *focusable, EventRecord *event);
@@ -53,6 +54,7 @@ bool chatter_close(struct focusable *focusable);
bool chatter_quit(struct focusable *focusable);
void chatter_use_shadow(struct chatter *chatter);
void chatter_reveal_shadow(struct chatter *chatter);
+bool chatter_tab_complete(struct chatter *chatter);
struct chatter *
chatter_init(const char *server, const unsigned short port,
@@ -344,6 +346,16 @@ chatter_layout_tab(struct chatter *chatter, struct cha
}
void
+chatter_set_input(struct chatter *chatter, char *input, size_t len)
+{
+ TESetText(input, len, chatter->input_te);
+ TEPinScroll(-SHRT_MAX, SHRT_MAX, chatter->input_te);
+ TEIdle(chatter->input_te);
+ /* this is needed to get TEIdle working again */
+ TESelView(chatter->input_te);
+}
+
+void
chatter_focus_tab(struct chatter *chatter, struct chatter_tab *tab)
{
RgnHandle clip;
@@ -425,6 +437,9 @@ chatter_focus_tab(struct chatter *chatter, struct chat
if (chatter->focusable)
chatter_update_menu(chatter->focusable);
+
+ /* invalidate any current tab completion */
+ chatter->tab_comp_match[0] = chatter->tab_comp_input[0] = '\0';
}
void
@@ -1023,24 +1038,30 @@ chatter_key_down(struct focusable *focusable, EventRec
char k;
k = (event->message & charCodeMask);
+
+ if (k == '\t') {
+ chatter_tab_complete(chatter);
+ return;
+ }
+
+ if (chatter->tab_comp_input[0] != '\0') {
+ chatter->tab_comp_input[0] = '\0';
+ chatter->tab_comp_match[0] = '\0';
+ }
+
if (k == '\r') {
HLock(chatter->input_te);
te = *(chatter->input_te);
HLock(te->hText);
memcpy(chatter->input, *(te->hText),
MIN(te->teLength, sizeof(chatter->input) - 1));
- chatter->input[MIN(te->teLength, sizeof(chatter->input) - 1)] = '\0';
- EraseRect(&te->viewRect);
- TESetText("", 0, chatter->input_te);
- TEPinScroll(-SHRT_MAX, SHRT_MAX, chatter->input_te);
- ValidRect(&te->viewRect);
- TEIdle(chatter->input_te);
HUnlock(te->hText);
HUnlock(chatter->input_te);
+ chatter->input[MIN(te->teLength, sizeof(chatter->input) - 1)] = '\0';
+ EraseRect(&te->viewRect);
+ chatter_set_input(chatter, "", 0);
irc_process_input(tab->conn, tab->channel,
(tab->query_nick[0] ? tab->query_nick : NULL), chatter->input);
- /* this is needed to get TEIdle working again */
- TESelView(chatter->input_te);
} else if (te->teLength < IRC_MAX_MSG_SIZE - 1) {
TEKey(k, chatter->input_te);
TESelView(chatter->input_te);
@@ -1476,4 +1497,72 @@ chatter_reveal_shadow(struct chatter *chatter)
CopyBits(&chatter->shadow, &chatter->win->portBits,
&chatter->shadow.bounds, &chatter->shadow.bounds, srcCopy, nil);
ValidRect(&chatter->win->portRect);
-}
+}
+
+bool
+chatter_tab_complete(struct chatter *chatter)
+{
+ TERec *te;
+ size_t size;
+ long n;
+ struct irc_channel *channel;
+ struct irc_channel_nick *nick;
+ char *skip_until = NULL;
+
+ if (!chatter->current_tab || !chatter->current_tab->channel)
+ return false;
+
+ if (chatter->tab_comp_input[0] == '\0') {
+ /* new search, store original input */
+ HLock(chatter->input_te);
+ te = *(chatter->input_te);
+ HLock(te->hText);
+ size = MIN(te->teLength, sizeof(chatter->tab_comp_input) - 1);
+ memcpy(chatter->tab_comp_input, *(te->hText), size);
+ chatter->tab_comp_input[size] = '\0';
+ } else
+ size = strlen(chatter->tab_comp_input);
+
+ /* if previous match, skip nicks in chain until we see it again */
+ if (chatter->tab_comp_match[0] != '\0')
+ skip_until = chatter->tab_comp_match;
+
+ channel = chatter->current_tab->channel;
+ nick = &channel->nicks[channel->first_nick];
+ while (nick) {
+ if (strncasecmp(nick->nick, chatter->tab_comp_input, size) != 0)
+ goto next_nick;
+
+ /* but don't let us win */
+ if (strcasecmp(nick->nick, channel->connection->nick) == 0)
+ goto next_nick;
+
+ /* if we had a last match, skip until we see it again */
+ if (skip_until) {
+ if (strcmp(nick->nick, skip_until) == 0)
+ skip_until = NULL;
+ goto next_nick;
+ }
+
+ strlcpy(chatter->tab_comp_match, nick->nick,
+ sizeof(chatter->tab_comp_match));
+ size = snprintf(chatter->input, sizeof(chatter->input), "%s: ",
+ nick->nick);
+ chatter_set_input(chatter, chatter->input, size);
+ return true;
+
+next_nick:
+ if (nick->next_nick == -1)
+ break;
+ nick = &channel->nicks[nick->next_nick];
+ }
+
+ /* no match (or cycled through all matches), return to original input */
+ if (chatter->tab_comp_match[0] != '\0') {
+ chatter_set_input(chatter, chatter->tab_comp_input,
+ strlen(chatter->tab_comp_input));
+ chatter->tab_comp_match[0] = chatter->tab_comp_input[0] = '\0';
+ }
+
+ return false;
+}
\ No newline at end of file
--- chatter.h Mon Sep 16 09:16:13 2024
+++ chatter.h Mon Sep 16 11:20:22 2024
@@ -92,6 +92,8 @@ struct chatter {
short shadow_refcnt;
TEHandle input_te;
char input[IRC_MAX_MSG_SIZE];
+ char tab_comp_input[member_size(struct irc_user, nick)];
+ char tab_comp_match[member_size(struct irc_user, nick)];
bool quitting;
bool need_tab_bar_redraw;
short ntabs;