It is good for me to keep coming back to programming in C every so often as it keeps me grounded and reminds me that, actually, I have no idea what I’m doing.
Even though I barely use IRC (I can rarely think of a good reason for talking to another human being about anything; I can talk to myself in my own head for ages; make of that what you will), for quite some time I’ve thought adding highlighting of mentions of my nick into sic would be a particularly useful and interesting thing to do. But then I took a look at the code. And even though it’s less than 250 LoC and, even though I’m not qualified to assess it, no doubt incredibly well written, I couldn’t figure out what was going on as comments were bit sparse.
Eventually I figured it out.
I drew a great, great, deal of inspiration of what I wanted to do from this forum post by itsme86.
Here’s what the function that parses the server response was like originally in sic, well, I say originally, but the below also filters out part and join messages that I added in (Life is all about small achievements!).
/* From: http://tools.suckless.org/sic/ */
parsesrv(char *cmd) {
char *usr, *par, *txt, *pnick;
usr = host;
if(!cmd || !*cmd)
return;
if(cmd[0] == ':') {
usr = cmd + 1;
cmd = skip(usr, ' ');
if(cmd[0] == '\0')
return;
skip(usr, '!');
}
skip(cmd, '\r');
par = skip(cmd, ' ');
txt = skip(par, ':');
trim(par);
if(!strcmp("PONG", cmd) || !strcmp("QUIT", cmd) || !strcmp("JOIN", cmd) || !strcmp("PART", cmd))
return;
if(!strcmp("PRIVMSG", cmd))
pout(par, "<\x1b[32m%s\x1b[0m> %s", usr, txt);
else if(!strcmp("PING", cmd))
sout("PONG %s", txt);
else {
pout(usr, ">< \x1b[1;34m%s\x1b[0m (\x1b[1;32m%s\x1b[0m): %s", cmd, par, txt);
if(!strcmp("NICK", cmd) && !strcmp(usr, nick))
strlcpy(nick, txt, sizeof nick);
}
}
Here are the changes I made:
parsesrv(char *cmd) {
char *usr, *par, *txt, *pnick;
char txthighlight[4096];
usr = host;
if(!cmd || !*cmd)
return;
if(cmd[0] == ':') {
usr = cmd + 1;
cmd = skip(usr, ' ');
if(cmd[0] == '\0')
return;
skip(usr, '!');
}
skip(cmd, '\r');
par = skip(cmd, ' ');
txt = skip(par, ':');
trim(par);
/* Add highlighting of nick to txt */
/* Very, very strongly influenced by: http://www.linuxquestions.org/questions/programming-9/replace-a-substring-with-another-string-in-c-170076/ */
if ((pnick = strstr(txt, nick))) {
strncpy(txthighlight, txt, pnick-txt);
/* Technically, should null terminate the end of this new string, but won't since it's only temporary and never printed */
/* Adds red underlining to nick */
sprintf(txthighlight+(pnick-txt), "\x1b[31;4m%s\x1b[0m%s", nick, pnick+strlen(nick));
/* Copy back to txt so can use below */
strcpy(txt, txthighlight);
}
if(!strcmp("PONG", cmd) || !strcmp("QUIT", cmd) || !strcmp("JOIN", cmd) || !strcmp("PART", cmd))
return;
if(!strcmp("PRIVMSG", cmd))
/* I thought this was just for private messages, but it seems not! */
pout(par, "<\x1b[32m%s\x1b[0m> %s", usr, txt);
else if(!strcmp("PING", cmd))
sout("PONG %s", txt);
else {
pout(usr, ">< \x1b[1;34m%s\x1b[0m (\x1b[1;32m%s\x1b[0m): %s", cmd, par, txt);
if(!strcmp("NICK", cmd) && !strcmp(usr, nick))
strlcpy(nick, txt, sizeof nick);
}
}
And with a bit of explanation (more for myself than anything else):
char txthighlight[4096];
Added a new string which is the highlighted version of txt
.
if ((pnick = strstr(txt, nick))) {
strstr
checks if nick
is in txt
and if it is returns a pointer, pnick
, to the start of it. The seemingly superfluous extra brackets are there to tell the compiler that we really do mean this as an assignment and it is not an equality check (==
) and so this avoids a compiler warning.
strncpy(txthighlight, txt, pnick-txt);
This copies the txt
string to txthighlight
, but only up until the pointer by limiting by the appropriate number of bytes; “Pointer to the start of nick” subtract “pointer to the start of string” gives the number of bytes to limit by.
At this point, txthighlight
should be null terminated by doing txthighlight[pnick-txt] = "\0"
, but I decided against doing this as I couldn’t figure out how to prevent a “assignment makes integer from pointer without a cast” warning from the compiler. And since txthighlight
is never printed, is only temporary and will be appended to by the rest of txt
I decided to go for no compiler warnings over a perhaps more correct approach.
sprintf(txthighlight+(pnick-txt), "\x1b[31;4m%s\x1b[0m%s", nick, pnick+strlen(nick));
sprintf
prints to a string. Here we are printing onto the end of the txthighlight
string by offsetting by “pnick-txt”. pnick+strlen(nick)
gets a pointer to the end of the nick and so gets everything afterwards from the original string.
strcpy(txt, txthighlight);
This copies back to txt
so we can keep the rest of parsesrv
the same.
I’m not entirely happy/sure about the lack of a null pointer on txthighlight
, but, as far as I can tell, it seems to work ok.