hirc

IRC client
Log | Files | Refs

commit 78df2834982ffbb261c999a4444ab034f87dc382
parent 79bb64341eaa7728fcebd8e780b09092af72e2f5
Author: hhvn <dev@hhvn.uk>
Date:   Mon, 11 Apr 2022 15:30:14 +0100

Seperate format functions into own file

Diffstat:
MMakefile | 3++-
Msrc/commands.c | 2+-
Msrc/config.c | 3+++
Asrc/format.c | 741+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/hirc.h | 9++++++---
Msrc/struct.h | 2+-
Msrc/ui.c | 766++-----------------------------------------------------------------------------
7 files changed, 771 insertions(+), 755 deletions(-)

diff --git a/Makefile b/Makefile @@ -20,7 +20,8 @@ MANDIR = $(PREFIX)/share/man BIN = hirc SRC = src/main.c src/mem.c src/handle.c src/hist.c \ src/nick.c src/chan.c src/serv.c src/ui.c \ - src/complete.c src/commands.c src/config.c + src/format.c src/complete.c src/commands.c \ + src/config.c OBJ = $(SRC:.c=.o) MAN = hirc.1 COMMIT = $(shell git log HEAD...HEAD~1 --pretty=format:%h) diff --git a/src/commands.c b/src/commands.c @@ -1511,7 +1511,7 @@ command_grep) { /* Traverse until we hit a message already generated by /grep */ for (; p && !(p->options & HIST_GREP); p = p->prev) { if (!raw && !p->format) - p->format = estrdup(ui_hformat(&windows[Win_main], p)); + p->format = estrdup(format(&windows[Win_main], NULL, p)); if (!raw && !p->rformat) { p->rformat = emalloc(strlen(p->format) + 1); /* since only one or zero characters are added to diff --git a/src/config.c b/src/config.c @@ -1319,6 +1319,9 @@ char * config_gets(char *name) { int i; + if (!name) + return NULL; + for (i=0; config[i].name; i++) { if (strcmp(config[i].name, name) == 0 && config[i].valtype == Val_string) diff --git a/src/format.c b/src/format.c @@ -0,0 +1,741 @@ +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include "hirc.h" + +struct { + char *cmd; + char *format; +} formatmap[] = { + /* SELF_ commands from UI */ + {"SELF_ERROR", "format.ui.error"}, + {"SELF_UI", "format.ui.misc"}, + {"SELF_CONNECTLOST", "format.ui.connectlost"}, + {"SELF_CONNECTING", "format.ui.connecting"}, + {"SELF_CONNECTED", "format.ui.connected"}, + {"SELF_LOOKUPFAIL", "format.ui.lookupfail"}, + {"SELF_CONNECTFAIL", "format.ui.connectfail"}, +#ifndef TLS + {"SELF_TLSNOTCOMPILED", "format.ui.tls.notcompiled"}, +#else + {"SELF_TLS_VERSION", "format.ui.tls.version"}, + {"SELF_TLS_NAMES", "format.ui.tls.names"}, +#endif /* TLS */ + {"SELF_KEYBIND_START", "format.ui.keybind.start"}, + {"SELF_KEYBIND_LIST", "format.ui.keybind"}, + {"SELF_KEYBIND_END", "format.ui.keybind.end"}, + {"SELF_GREP_START", "format.ui.grep.start"}, + {"SELF_GREP_END", "format.ui.grep.end"}, + {"SELF_ALIAS_START", "format.ui.alias.start"}, + {"SELF_ALIAS_LIST", "format.ui.alias"}, + {"SELF_ALIAS_END", "format.ui.alias.end"}, + {"SELF_HELP_START", "format.ui.help.start"}, + {"SELF_HELP", "format.ui.help"}, + {"SELF_HELP_END", "format.ui.help.end"}, + {"SELF_AUTOCMDS_START", "format.ui.autocmds.start"}, + {"SELF_AUTOCMDS_LIST", "format.ui.autocmds"}, + {"SELF_AUTOCMDS_END", "format.ui.autocmds.end"}, + {"SELF_LOG_RESTORE", "format.ui.logrestore"}, + {"SELF_UNREAD", "format.ui.unread"}, + {"SELF_IGNORES_START", "format.ui.ignores.start"}, + {"SELF_IGNORES_LIST", "format.ui.ignores"}, + {"SELF_IGNORES_ADDED", "format.ui.ignores.added"}, + {"SELF_IGNORES_END", "format.ui.ignores.end"}, + /* Real commands/numerics from server */ + {"PRIVMSG", "format.privmsg"}, + {"NOTICE", "format.notice"}, + {"JOIN", "format.join"}, + {"PART", "format.part"}, + {"KICK", "format.kick"}, + {"QUIT", "format.quit"}, + {"NICK", "format.nick"}, + {"TOPIC", "format.topic"}, + {"INVITE", "format.invite"}, + {"PONG", "format.pong"}, + {"ERROR", "format.error"}, + /* START: misc/rpl-ui-gen.awk */ + {"200", "format.rpl.tracelink"}, + {"201", "format.rpl.traceconnecting"}, + {"202", "format.rpl.tracehandshake"}, + {"203", "format.rpl.traceunknown"}, + {"204", "format.rpl.traceoperator"}, + {"205", "format.rpl.traceuser"}, + {"206", "format.rpl.traceserver"}, + {"208", "format.rpl.tracenewtype"}, + {"209", "format.rpl.traceclass"}, + {"211", "format.rpl.statslinkinfo"}, + {"212", "format.rpl.statscommands"}, + {"213", "format.rpl.statscline"}, + {"214", "format.rpl.statsnline"}, + {"215", "format.rpl.statsiline"}, + {"216", "format.rpl.statskline"}, + {"218", "format.rpl.statsyline"}, + {"219", "format.rpl.endofstats"}, + {"221", "format.rpl.umodeis"}, + {"231", "format.rpl.serviceinfo"}, + {"233", "format.rpl.service"}, + {"235", "format.rpl.servlistend"}, + {"241", "format.rpl.statslline"}, + {"242", "format.rpl.statsuptime"}, + {"243", "format.rpl.statsoline"}, + {"244", "format.rpl.statshline"}, + {"251", "format.rpl.luserclient"}, + {"252", "format.rpl.luserop"}, + {"253", "format.rpl.luserunknown"}, + {"254", "format.rpl.luserchannels"}, + {"255", "format.rpl.luserme"}, + {"256", "format.rpl.adminme"}, + {"257", "format.rpl.adminloc1"}, + {"258", "format.rpl.adminloc2"}, + {"259", "format.rpl.adminemail"}, + {"261", "format.rpl.tracelog"}, + {"300", "format.rpl.none"}, + {"301", "format.rpl.away"}, + {"302", "format.rpl.userhost"}, + {"303", "format.rpl.ison"}, + {"305", "format.rpl.unaway"}, + {"306", "format.rpl.nowaway"}, + {"311", "format.rpl.whoisuser"}, + {"312", "format.rpl.whoisserver"}, + {"313", "format.rpl.whoisoperator"}, + {"314", "format.rpl.whowasuser"}, + {"315", "format.rpl.endofwho"}, + {"316", "format.rpl.whoischanop"}, + {"317", "format.rpl.whoisidle"}, + {"318", "format.rpl.endofwhois"}, + {"319", "format.rpl.whoischannels"}, + {"321", "format.rpl.liststart"}, + {"322", "format.rpl.list"}, + {"323", "format.rpl.listend"}, + {"324", "format.rpl.channelmodeis"}, + {"331", "format.rpl.notopic"}, + {"332", "format.rpl.topic"}, + {"341", "format.rpl.inviting"}, + {"342", "format.rpl.summoning"}, + {"351", "format.rpl.version"}, + {"352", "format.rpl.whoreply"}, + {"353", "format.rpl.namreply"}, + {"362", "format.rpl.closing"}, + {"364", "format.rpl.links"}, + {"365", "format.rpl.endoflinks"}, + {"366", "format.rpl.endofnames"}, + {"367", "format.rpl.banlist"}, + {"368", "format.rpl.endofbanlist"}, + {"369", "format.rpl.endofwhowas"}, + {"371", "format.rpl.info"}, + {"372", "format.rpl.motd"}, + {"373", "format.rpl.infostart"}, + {"374", "format.rpl.endofinfo"}, + {"375", "format.rpl.motdstart"}, + {"376", "format.rpl.endofmotd"}, + {"381", "format.rpl.youreoper"}, + {"382", "format.rpl.rehashing"}, + {"391", "format.rpl.time"}, + {"392", "format.rpl.usersstart"}, + {"393", "format.rpl.users"}, + {"394", "format.rpl.endofusers"}, + {"395", "format.rpl.nousers"}, + {"401", "format.err.nosuchnick"}, + {"402", "format.err.nosuchserver"}, + {"403", "format.err.nosuchchannel"}, + {"404", "format.err.cannotsendtochan"}, + {"405", "format.err.toomanychannels"}, + {"406", "format.err.wasnosuchnick"}, + {"407", "format.err.toomanytargets"}, + {"409", "format.err.noorigin"}, + {"411", "format.err.norecipient"}, + {"412", "format.err.notexttosend"}, + {"413", "format.err.notoplevel"}, + {"414", "format.err.wildtoplevel"}, + {"421", "format.err.unknowncommand"}, + {"422", "format.err.nomotd"}, + {"423", "format.err.noadmininfo"}, + {"424", "format.err.fileerror"}, + {"431", "format.err.nonicknamegiven"}, + {"432", "format.err.erroneusnickname"}, + {"433", "format.err.nicknameinuse"}, + {"436", "format.err.nickcollision"}, + {"441", "format.err.usernotinchannel"}, + {"442", "format.err.notonchannel"}, + {"443", "format.err.useronchannel"}, + {"444", "format.err.nologin"}, + {"445", "format.err.summondisabled"}, + {"446", "format.err.usersdisabled"}, + {"451", "format.err.notregistered"}, + {"461", "format.err.needmoreparams"}, + {"462", "format.err.alreadyregistred"}, + {"463", "format.err.nopermforhost"}, + {"464", "format.err.passwdmismatch"}, + {"465", "format.err.yourebannedcreep"}, + {"466", "format.err.youwillbebanned"}, + {"467", "format.err.keyset"}, + {"471", "format.err.channelisfull"}, + {"472", "format.err.unknownmode"}, + {"473", "format.err.inviteonlychan"}, + {"474", "format.err.bannedfromchan"}, + {"475", "format.err.badchannelkey"}, + {"481", "format.err.noprivileges"}, + {"482", "format.err.chanoprivsneeded"}, + {"483", "format.err.cantkillserver"}, + {"491", "format.err.nooperhost"}, + {"492", "format.err.noservicehost"}, + {"501", "format.err.umodeunknownflag"}, + {"502", "format.err.usersdontmatch"}, + /* END: misc/rpl-ui-gen.awk */ + /* Modern stuff */ + {"001", "format.rpl.welcome"}, + {"002", "format.rpl.yourhost"}, + {"003", "format.rpl.created"}, + {"004", "format.rpl.myinfo"}, + {"005", "format.rpl.isupport"}, + {"006", "format.rpl.map"}, /* I'm not so sure if 006 and 007 */ + {"007", "format.rpl.mapend"}, /* are really exclusive to /map */ + {"265", "format.rpl.localusers"}, + {"266", "format.rpl.globalusers"}, + {"320", "format.rpl.whoisspecial"}, + {"330", "format.rpl.whoisaccount"}, + {"333", "format.rpl.topicwhotime"}, + {"338", "format.rpl.whoisactually"}, + {"378", "format.rpl.whoishost"}, + {"379", "format.rpl.whoismodes"}, + {"671", "format.rpl.whoissecure"}, + /* Pseudo commands for specific formatting */ + {"MODE-NICK-SELF", "format.mode.nick.self"}, + {"MODE-NICK", "format.mode.nick"}, + {"MODE-CHANNEL", "format.mode.channel"}, + {"PRIVMSG-ACTION", "format.action"}, + {"PRIVMSG-CTCP", "format.ctcp.request"}, + {"NOTICE-CTCP", "format.ctcp.answer"}, + {NULL, NULL}, +}; + +char * +format_get_bufact(int activity) { + switch (activity) { + case Activity_status: + return format(NULL, config_gets("format.ui.buflist.activity.status"), NULL); + case Activity_error: + return format(NULL, config_gets("format.ui.buflist.activity.error"), NULL); + case Activity_message: + return format(NULL, config_gets("format.ui.buflist.activity.message"), NULL); + case Activity_hilight: + return format(NULL, config_gets("format.ui.buflist.activity.hilight"), NULL); + default: + return format(NULL, config_gets("format.ui.buflist.activity.none"), NULL); + } + + return NULL; /* shouldn't be possible *shrug*/ +} + +char * +format_get(struct Window *window, struct History *hist) { + char *cmd, *p1, *p2; + int i; + + if (!hist) + return NULL; + + if (!hist->params) + goto raw; + + cmd = *(hist->params); + p1 = *(hist->params+1); + p2 = *(hist->params+2); + + if (strcmp_n(cmd, "MODE") == 0) { + if (p1 && serv_ischannel(hist->origin->server, p1)) + cmd = "MODE-CHANNEL"; + else if (hist->from && nick_isself(hist->from) && strcmp_n(hist->from->nick, p1) == 0) + cmd = "MODE-NICK-SELF"; + else + cmd = "MODE-NICK"; + } else if (strcmp_n(cmd, "PRIVMSG") == 0) { + /* ascii 1 is ^A */ + if (*p2 == 1 && strncmp(p2 + 1, "ACTION", strlen("ACTION")) == 0) + cmd = "PRIVMSG-ACTION"; + else if (*p2 == 1) + cmd = "PRIVMSG-CTCP"; + } else if (strcmp_n(cmd, "NOTICE") == 0 && *p2 == 1) { + cmd = "NOTICE-CTCP"; + } + + for (i=0; formatmap[i].cmd; i++) + if (formatmap[i].format && strcmp_n(formatmap[i].cmd, cmd) == 0) + return formatmap[i].format; + + if (isdigit(*cmd) && isdigit(*(cmd+1)) && isdigit(*(cmd+2)) && !*(cmd+3)) + return "format.rpl.other"; + +raw: + return "format.other"; +} + +static char * +format_get_content(char *sstr, int nesting) { + static char ret[8192]; + int layer, rc; + + for (layer = 0, rc = 0; sstr && *sstr && rc < sizeof(ret); sstr++) { + switch (*sstr) { + case '}': + if (nesting && layer) { + ret[rc++] = '}'; + layer--; + } else { + goto end; + } + break; + case '{': + if (nesting) + layer++; + ret[rc++] = '{'; + break; + default: + ret[rc++] = *sstr; + break; + } + } + +end: + ret[rc] = '\0'; + return ret; +} + +char * +format_(struct Window *window, char *format, struct History *hist, int recursive) { + static char *ret; + struct Nick *nick; + size_t rs = BUFSIZ; + size_t rc, pc; + int escape, i; + long long pn; + int rhs = 0; + int divider = 0; + char **params; + char *content, *p, *p2; + char *ts, *save; + char colourbuf[2][3]; + char priv[2]; + char chs[2]; + size_t len; + enum { + sub_raw, + sub_cmd, + sub_nick, + sub_ident, + sub_host, + sub_priv, + sub_channel, + sub_topic, + sub_server, + sub_time, + }; + struct { + char *name; + char *val; + } subs[] = { + [sub_raw] = {"raw", NULL}, + [sub_cmd] = {"cmd", NULL}, + [sub_nick] = {"nick", NULL}, + [sub_ident] = {"ident", NULL}, + [sub_host] = {"host", NULL}, + [sub_priv] = {"priv", NULL}, + [sub_channel] = {"channel", NULL}, + [sub_topic] = {"topic", NULL}, + [sub_server] = {"server", NULL}, + [sub_time] = {"time", NULL}, + {NULL, NULL}, + }; + + if (!format) + format = config_gets(format_get(window, hist)); + if (!format) + return NULL; + + pfree(&ret); + ret = emalloc(rs); + + subs[sub_channel].val = selected.channel ? selected.channel->name : NULL; + subs[sub_topic].val = selected.channel ? selected.channel->topic : NULL; + subs[sub_server].val = selected.server ? selected.server->name : NULL; + + if (hist) { + subs[sub_raw].val = hist->raw; + subs[sub_nick].val = hist->from ? hist->from->nick : NULL; + subs[sub_ident].val = hist->from ? hist->from->ident : NULL; + subs[sub_host].val = hist->from ? hist->from->host : NULL; + + if (hist->from) { + priv[0] = hist->from->priv; + priv[priv[0] != ' '] = '\0'; + subs[sub_priv].val = priv; + } + + if (hist->origin) { + if (hist->origin->channel) { + if (!recursive) + divider = config_getl("divider.toggle"); + subs[sub_channel].val = hist->origin->channel->name; + subs[sub_topic].val = hist->origin->channel->topic; + } + if (hist->origin->server) { + subs[sub_server].val = hist->origin->server->name; + } + } + + len = snprintf(subs[sub_time].val, 0, "%lld", (long long)hist->timestamp) + 1; + subs[sub_time].val = emalloc(len); + snprintf(subs[sub_time].val, len, "%lld", (long long)hist->timestamp); + + params = hist->params; + subs[sub_cmd].val = *params; + params++; + } + + if (!recursive && hist && config_getl("timestamp.toggle")) { + ts = estrdup(format_(NULL, config_gets("format.ui.timestamp"), hist, 1)); + } else { + ts = ""; + } + + for (escape = 0, rc = 0; format && *format && rc < rs; ) { +outcont: + if (rc > rs / 2) { + rs *= 2; + ret = erealloc(ret, rs); + } + + if (!escape && *format == '$' && *(format+1) == '{' && strchr(format, '}')) { + escape = 0; + content = format_get_content(format+2, 0); + + for (p = content; *p && isdigit(*p); p++); + /* If all are digits, *p == '\0' */ + if (!*p && hist) { + pn = strtol(content, NULL, 10) - 1; + if (pn >= 0 && param_len(params) > pn) { + if (**(params+pn) == 1 && strncmp((*(params+pn))+1, "ACTION", strlen("ACTION")) == 0 && strchr(*(params+pn), ' ')) + rc += snprintf(&ret[rc], rs - rc, "%s", struntil(strchr(*(params+pn), ' ') + 1, 1)); + else if (**(params+pn) == 1) + rc += snprintf(&ret[rc], rs - rc, "%s", struntil((*(params+pn)) + 1, 1)); + else + rc += snprintf(&ret[rc], rs - rc, "%s", *(params+pn)); + format = strchr(format, '}') + 1; + continue; + } + } + /* All are digits except a trailing '-' */ + if (*p == '-' && *(p+1) == '\0' && hist) { + pn = strtol(content, NULL, 10) - 1; + if (pn >= 0 && param_len(params) > pn) { + for (; *(params+pn) != NULL; pn++) { + if (**(params+pn) == 1 && strncmp((*(params+pn))+1, "ACTION", strlen("ACTION")) == 0 && strchr(*(params+pn), ' ')) { + rc += snprintf(&ret[rc], rs - rc, "%s%s", + struntil(strchr(*(params+pn), ' ') + 1, 1), + *(params+pn+1) ? " " : ""); + } else if (**(params+pn) == 1) { + rc += snprintf(&ret[rc], rs - rc, "%s%s", + struntil((*(params+pn)) + 1, 1), + *(params+pn+1) ? " " : ""); + } else { + rc += snprintf(&ret[rc], rs - rc, "%s%s", + *(params+pn), *(params+pn+1) ? " " : ""); + } + } + format = strchr(format, '}') + 1; + continue; + } + } + + for (i=0; subs[i].name; i++) { + if (strcmp_n(subs[i].name, content) == 0) { + if (subs[i].val) + rc += snprintf(&ret[rc], rs - rc, "%s", subs[i].val); + format = strchr(format, '}') + 1; + goto outcont; /* unfortunately, need to use a goto as we are already in a loop */ + } + } + } + + if (!escape && *format == '%' && *(format+1) == '{' && strchr(format, '}')) { + escape = 0; + content = format_get_content(format+2, 0); + + switch (*content) { + case 'b': + case 'B': + ret[rc++] = 2; /* ^B */ + format = strchr(format, '}') + 1; + continue; + case 'c': + case 'C': + if (*(content+1) == ':' && isdigit(*(content+2))) { + content += 2; + memset(colourbuf, 0, sizeof(colourbuf)); + colourbuf[0][0] = *content; + content++; + if (isdigit(*content)) { + colourbuf[0][1] = *content; + content += 1; + } + if (*content == ',' && isdigit(*(content+1))) { + colourbuf[1][0] = *(content+1); + content += 2; + } + if (colourbuf[1][0] && isdigit(*content)) { + colourbuf[1][1] = *(content); + content += 1; + } + if (*content == '\0') { + rc += snprintf(&ret[rc], rs - rc, "%c%02d,%02d", 3 /* ^C */, + atoi(colourbuf[0]), colourbuf[1][0] ? atoi(colourbuf[1]) : 99); + format = strchr(format, '}') + 1; + continue; + } + } + break; + case 'i': + case 'I': + if (*(content+1) == '\0') { + ret[rc++] = 9; /* ^I */ + format = strchr(format, '}') + 1; + continue; + } + break; + case 'o': + case 'O': + if (*(content+1) == '\0') { + ret[rc++] = 15; /* ^O */ + format = strchr(format, '}') + 1; + continue; + } + break; + case 'r': + case 'R': + if (*(content+1) == '\0') { + ret[rc++] = 18; /* ^R */ + format = strchr(format, '}') + 1; + continue; + } + break; + case 'u': + case 'U': + if (*(content+1) == '\0') { + ret[rc++] = 21; /* ^U */ + format = strchr(format, '}') + 1; + continue; + } + break; + case '=': + if (*(content+1) == '\0' && divider) { + rhs = 1; + ret[rc] = '\0'; + /* strlen(ret) - ui_strlenc(window, ret, NULL) should get + * the length of hidden characters. Add this onto the + * margin to pad out properly. */ + /* Save ret for use in snprintf */ + save = estrdup(ret); + rc = snprintf(ret, rs, "%1$*3$s%2$s", save, config_gets("divider.string"), + config_getl("divider.margin") + (strlen(ret) - ui_strlenc(window, ret, NULL))); + pfree(&save); + format = strchr(format, '}') + 1; + continue; + } else if (*(content+1) == '\0') { + ret[rc++] = ' '; + format = strchr(format, '}') + 1; + continue; + } + break; + } + + /* pad, nick, split, rdate and time, must then continue as they modify content + * + * These styling formatters are quite ugly and repetitive. + * %{nick:...} was implemented first, and has the most (all of them :)) comments */ + if (strncmp(content, "pad:", strlen("pad:")) == 0 && strchr(content, ',')) { + pn = strtol(content + strlen("pad:"), NULL, 10); + content = estrdup(format_get_content(strchr(format+2+strlen("pad:"), ',') + 1, 1)); + save = ret; + ret = NULL; + format_(NULL, content, hist, 1); + rc += snprintf(&save[rc], rs - rc, "%1$*2$s", ret, pn); + pfree(&ret); + ret = save; + format = strchr(format+2+strlen("pad:"), ',') + strlen(content) + 2; + + pfree(&content); + continue; + } + + if (strncmp(content, "rdate:", strlen("rdate:")) == 0) { + content = estrdup(format_get_content(format+2+strlen("rdate:"), 1)); + save = ret; + ret = NULL; + format_(NULL, content, hist, 1); + pn = strtoll(ret, NULL, 10); + rc += snprintf(&save[rc], rs - rc, "%s", strrdate((time_t)pn)); + format += 3 + strlen("rdate:") + strlen(content); + + pfree(&ret); + ret = save; + pfree(&content); + continue; + } + + if (strncmp(content, "time:", strlen("time:")) == 0 && strchr(content, ',')) { + content = estrdup(format_get_content(strchr(format+2+strlen("time:"), ',') + 1, 1)); + save = ret; + ret = NULL; + format_(NULL, content, hist, 1); + pn = strtoll(ret, NULL, 10); + p = struntil(format+2+strlen("time:"), ','); + + rc += strftime(&save[rc], rs - rc, p, localtime((time_t *)&pn)); + format = strchr(format+2+strlen("time:"), ',') + strlen(content) + 2; + + pfree(&ret); + ret = save; + pfree(&content); + continue; + } + + /* second comma ptr - second comma ptr = distance. + * If the distance is 2, then there is one non-comma char between. */ + p = strchr(content, ','); + if (p) + p2 = strchr(p + 1, ','); + if (strncmp(content, "split:", strlen("split:")) == 0 && p2 - p == 2) { + pn = strtol(content + strlen("split:"), NULL, 10); + chs[0] = *(strchr(content, ',') + 1); + chs[1] = '\0'; + content = estrdup(format_get_content( + strchr( + strchr(format+2+strlen("split:"), ',') + 1, + ',') + 1, + 1)); + save = ret; + ret = NULL; + format_(NULL, content, hist, 1); + rc += snprintf(&save[rc], rs - rc, "%s", strntok(ret, chs, pn)); + format = strchr( + strchr(format+2+strlen("split:"), ',') + 1, + ',') + strlen(content) + 2; + + pfree(&ret); + ret = save; + pfree(&content); + continue; + } + + if (hist && !recursive && strncmp(content, "nick:", strlen("nick:")) == 0) { + content = estrdup(format_get_content(format+2+strlen("nick:"), 1)); + save = ret; + ret = NULL; + format_(NULL, content, hist, 1); + nick = nick_create(ret, ' ', hist->origin ? hist->origin->server : NULL); + rc += snprintf(&save[rc], rs - rc, "%c%02d", 3 /* ^C */, nick_getcolour(nick)); + format += 3 + strlen("nick:") + strlen(content); + + pfree(&ret); + ret = save; + nick_free(nick); + pfree(&content); + continue; + } + } + + if (escape && *format == 'n') { + ret[rc++] = '\n'; + rc += snprintf(&ret[rc], rs - rc, "%1$*3$s%2$s", "", config_gets("divider.string"), + ui_strlenc(NULL, ts, NULL) + config_getl("divider.margin")); + escape = 0; + format++; + continue; + } + + if (escape && (*format == '%' || *format == '$') && *(format+1) == '{' && strchr(format, '}')) + escape = 0; + + if (escape) { + ret[rc++] = '\\'; + escape = 0; + } + + if (*format == '\\') { + escape = 1; + format++; + } else { + ret[rc++] = *format; + format++; + } + } + + ret[rc] = '\0'; + if (!recursive && divider && !rhs) { + save = estrdup(ret); + rc = snprintf(ret, rs, "%1$*4$s%2$s%3$s", "", config_gets("divider.string"), save, config_getl("divider.margin")); + pfree(&save); + } + + save = estrdup(ret); + rc = snprintf(ret, rs, "%s%s", ts, save); + pfree(&save); + + if (!recursive && window) { + for (p = ret, pc = 0; p && p <= (ret + rs); p++) { + /* lifted from ui_strlenc */ + switch (*p) { + case 2: /* ^B */ + case 9: /* ^I */ + case 15: /* ^O */ + case 18: /* ^R */ + case 21: /* ^U */ + break; + case 3: /* ^C */ + if (*p && isdigit(*(p+1))) + p += 1; + if (*p && isdigit(*(p+1))) + p += 1; + if (*p && *(p+1) == ',' && isdigit(*(p+2))) + p += 2; + if (*p && *(p-1) == ',' && isdigit(*(p+1))) + p += 1; + break; + default: + /* naive utf-8 handling: + * the 2-nth byte always + * follows 10xxxxxxx, so + * don't count it. */ + if ((*p & 0xC0) != 0x80) + pc++; + + if (*p == '\n') { + p++; + pc = 0; + } + + if (pc == window->w) { + save = estrdup(p); + + if (divider) { + p += snprintf(p, rs - ((size_t)(p - ret)), "%1$*4$s %2$s%3$s", + "", config_gets("divider.string"), save, + config_getl("divider.margin") + ui_strlenc(NULL, ts, NULL)); + } else { + p += snprintf(p, rs - ((size_t)(p - ret)), "%1$*3$s %2$s", "", save, ui_strlenc(NULL, ts, NULL)); + } + + pfree(&save); + pc = 0; + } + } + } + } + + if (subs[sub_time].val) + pfree(&subs[sub_time].val); + if (ts[0] != '\0') + pfree(&ts); + + return ret; +} diff --git a/src/hirc.h b/src/hirc.h @@ -149,7 +149,6 @@ void ui_redraw(void); void ui_draw_input(void); void ui_draw_nicklist(void); void ui_draw_buflist(void); -char * ui_hformat(struct Window *window, struct History *hist); void ui_draw_main(void); int ui_buflist_count(int *ret_servers, int *ret_channels, int *ret_privs); int ui_buflist_get(int num, struct Server **server, struct Channel **chan); @@ -157,8 +156,6 @@ int ui_get_pair(short fg, short bg); int ui_wprintc(struct Window *window, int lines, char *format, ...); int ui_strlenc(struct Window *window, char *s, int *lines); void ui_select(struct Server *server, struct Channel *channel); -char * ui_format_(struct Window *window, char *format, struct History *hist, int recursive); -#define ui_format(window, format, hist) ui_format_(window, format, hist, 0) char * ui_rectrl(char *str); char * ui_unctrl(char *str); int ui_bind(char *binding, char *cmd); @@ -175,6 +172,12 @@ void ui_tls_error_(char *file, int line, const char *func, struct tls *ctx, cha #define ui_tls_error(ctx, str) ui_tls_error_(__FILE__, __LINE__, __func__, ctx, str) #endif /* TLS */ +/* format.c */ +char * format_get_bufact(int activity); +char * format_get(struct Window *window, struct History *hist); +char * format_(struct Window *window, char *format, struct History *hist, int recursive); +#define format(window, format, hist) format_(window, format, hist, 0) + /* commands.c */ void command_eval(struct Server *server, char *str); int command_getopt(char **str, struct CommandOpts *opts); diff --git a/src/struct.h b/src/struct.h @@ -71,7 +71,7 @@ struct History { char *raw; char **_params; /* contains all params, free from here */ char **params; /* contains params without perfix, don't free */ - char *format; /* cached ui_format */ + char *format; /* cached format */ char *rformat; /* cached format without mirc codes */ struct HistInfo *origin; struct Nick *from; diff --git a/src/ui.c b/src/ui.c @@ -64,212 +64,6 @@ struct Window windows[Win_last] = { }; struct { - char *cmd; - char *format; -} formatmap[] = { - /* SELF_ commands from UI */ - {"SELF_ERROR", "format.ui.error"}, - {"SELF_UI", "format.ui.misc"}, - {"SELF_CONNECTLOST", "format.ui.connectlost"}, - {"SELF_CONNECTING", "format.ui.connecting"}, - {"SELF_CONNECTED", "format.ui.connected"}, - {"SELF_LOOKUPFAIL", "format.ui.lookupfail"}, - {"SELF_CONNECTFAIL", "format.ui.connectfail"}, -#ifndef TLS - {"SELF_TLSNOTCOMPILED", "format.ui.tls.notcompiled"}, -#else - {"SELF_TLS_VERSION", "format.ui.tls.version"}, - {"SELF_TLS_NAMES", "format.ui.tls.names"}, -#endif /* TLS */ - {"SELF_KEYBIND_START", "format.ui.keybind.start"}, - {"SELF_KEYBIND_LIST", "format.ui.keybind"}, - {"SELF_KEYBIND_END", "format.ui.keybind.end"}, - {"SELF_GREP_START", "format.ui.grep.start"}, - {"SELF_GREP_END", "format.ui.grep.end"}, - {"SELF_ALIAS_START", "format.ui.alias.start"}, - {"SELF_ALIAS_LIST", "format.ui.alias"}, - {"SELF_ALIAS_END", "format.ui.alias.end"}, - {"SELF_HELP_START", "format.ui.help.start"}, - {"SELF_HELP", "format.ui.help"}, - {"SELF_HELP_END", "format.ui.help.end"}, - {"SELF_AUTOCMDS_START", "format.ui.autocmds.start"}, - {"SELF_AUTOCMDS_LIST", "format.ui.autocmds"}, - {"SELF_AUTOCMDS_END", "format.ui.autocmds.end"}, - {"SELF_LOG_RESTORE", "format.ui.logrestore"}, - {"SELF_UNREAD", "format.ui.unread"}, - {"SELF_IGNORES_START", "format.ui.ignores.start"}, - {"SELF_IGNORES_LIST", "format.ui.ignores"}, - {"SELF_IGNORES_ADDED", "format.ui.ignores.added"}, - {"SELF_IGNORES_END", "format.ui.ignores.end"}, - /* Real commands/numerics from server */ - {"PRIVMSG", "format.privmsg"}, - {"NOTICE", "format.notice"}, - {"JOIN", "format.join"}, - {"PART", "format.part"}, - {"KICK", "format.kick"}, - {"QUIT", "format.quit"}, - {"NICK", "format.nick"}, - {"TOPIC", "format.topic"}, - {"INVITE", "format.invite"}, - {"PONG", "format.pong"}, - {"ERROR", "format.error"}, - /* START: misc/rpl-ui-gen.awk */ - {"200", "format.rpl.tracelink"}, - {"201", "format.rpl.traceconnecting"}, - {"202", "format.rpl.tracehandshake"}, - {"203", "format.rpl.traceunknown"}, - {"204", "format.rpl.traceoperator"}, - {"205", "format.rpl.traceuser"}, - {"206", "format.rpl.traceserver"}, - {"208", "format.rpl.tracenewtype"}, - {"209", "format.rpl.traceclass"}, - {"211", "format.rpl.statslinkinfo"}, - {"212", "format.rpl.statscommands"}, - {"213", "format.rpl.statscline"}, - {"214", "format.rpl.statsnline"}, - {"215", "format.rpl.statsiline"}, - {"216", "format.rpl.statskline"}, - {"218", "format.rpl.statsyline"}, - {"219", "format.rpl.endofstats"}, - {"221", "format.rpl.umodeis"}, - {"231", "format.rpl.serviceinfo"}, - {"233", "format.rpl.service"}, - {"235", "format.rpl.servlistend"}, - {"241", "format.rpl.statslline"}, - {"242", "format.rpl.statsuptime"}, - {"243", "format.rpl.statsoline"}, - {"244", "format.rpl.statshline"}, - {"251", "format.rpl.luserclient"}, - {"252", "format.rpl.luserop"}, - {"253", "format.rpl.luserunknown"}, - {"254", "format.rpl.luserchannels"}, - {"255", "format.rpl.luserme"}, - {"256", "format.rpl.adminme"}, - {"257", "format.rpl.adminloc1"}, - {"258", "format.rpl.adminloc2"}, - {"259", "format.rpl.adminemail"}, - {"261", "format.rpl.tracelog"}, - {"300", "format.rpl.none"}, - {"301", "format.rpl.away"}, - {"302", "format.rpl.userhost"}, - {"303", "format.rpl.ison"}, - {"305", "format.rpl.unaway"}, - {"306", "format.rpl.nowaway"}, - {"311", "format.rpl.whoisuser"}, - {"312", "format.rpl.whoisserver"}, - {"313", "format.rpl.whoisoperator"}, - {"314", "format.rpl.whowasuser"}, - {"315", "format.rpl.endofwho"}, - {"316", "format.rpl.whoischanop"}, - {"317", "format.rpl.whoisidle"}, - {"318", "format.rpl.endofwhois"}, - {"319", "format.rpl.whoischannels"}, - {"321", "format.rpl.liststart"}, - {"322", "format.rpl.list"}, - {"323", "format.rpl.listend"}, - {"324", "format.rpl.channelmodeis"}, - {"331", "format.rpl.notopic"}, - {"332", "format.rpl.topic"}, - {"341", "format.rpl.inviting"}, - {"342", "format.rpl.summoning"}, - {"351", "format.rpl.version"}, - {"352", "format.rpl.whoreply"}, - {"353", "format.rpl.namreply"}, - {"362", "format.rpl.closing"}, - {"364", "format.rpl.links"}, - {"365", "format.rpl.endoflinks"}, - {"366", "format.rpl.endofnames"}, - {"367", "format.rpl.banlist"}, - {"368", "format.rpl.endofbanlist"}, - {"369", "format.rpl.endofwhowas"}, - {"371", "format.rpl.info"}, - {"372", "format.rpl.motd"}, - {"373", "format.rpl.infostart"}, - {"374", "format.rpl.endofinfo"}, - {"375", "format.rpl.motdstart"}, - {"376", "format.rpl.endofmotd"}, - {"381", "format.rpl.youreoper"}, - {"382", "format.rpl.rehashing"}, - {"391", "format.rpl.time"}, - {"392", "format.rpl.usersstart"}, - {"393", "format.rpl.users"}, - {"394", "format.rpl.endofusers"}, - {"395", "format.rpl.nousers"}, - {"401", "format.err.nosuchnick"}, - {"402", "format.err.nosuchserver"}, - {"403", "format.err.nosuchchannel"}, - {"404", "format.err.cannotsendtochan"}, - {"405", "format.err.toomanychannels"}, - {"406", "format.err.wasnosuchnick"}, - {"407", "format.err.toomanytargets"}, - {"409", "format.err.noorigin"}, - {"411", "format.err.norecipient"}, - {"412", "format.err.notexttosend"}, - {"413", "format.err.notoplevel"}, - {"414", "format.err.wildtoplevel"}, - {"421", "format.err.unknowncommand"}, - {"422", "format.err.nomotd"}, - {"423", "format.err.noadmininfo"}, - {"424", "format.err.fileerror"}, - {"431", "format.err.nonicknamegiven"}, - {"432", "format.err.erroneusnickname"}, - {"433", "format.err.nicknameinuse"}, - {"436", "format.err.nickcollision"}, - {"441", "format.err.usernotinchannel"}, - {"442", "format.err.notonchannel"}, - {"443", "format.err.useronchannel"}, - {"444", "format.err.nologin"}, - {"445", "format.err.summondisabled"}, - {"446", "format.err.usersdisabled"}, - {"451", "format.err.notregistered"}, - {"461", "format.err.needmoreparams"}, - {"462", "format.err.alreadyregistred"}, - {"463", "format.err.nopermforhost"}, - {"464", "format.err.passwdmismatch"}, - {"465", "format.err.yourebannedcreep"}, - {"466", "format.err.youwillbebanned"}, - {"467", "format.err.keyset"}, - {"471", "format.err.channelisfull"}, - {"472", "format.err.unknownmode"}, - {"473", "format.err.inviteonlychan"}, - {"474", "format.err.bannedfromchan"}, - {"475", "format.err.badchannelkey"}, - {"481", "format.err.noprivileges"}, - {"482", "format.err.chanoprivsneeded"}, - {"483", "format.err.cantkillserver"}, - {"491", "format.err.nooperhost"}, - {"492", "format.err.noservicehost"}, - {"501", "format.err.umodeunknownflag"}, - {"502", "format.err.usersdontmatch"}, - /* END: misc/rpl-ui-gen.awk */ - /* Modern stuff */ - {"001", "format.rpl.welcome"}, - {"002", "format.rpl.yourhost"}, - {"003", "format.rpl.created"}, - {"004", "format.rpl.myinfo"}, - {"005", "format.rpl.isupport"}, - {"006", "format.rpl.map"}, /* I'm not so sure if 006 and 007 */ - {"007", "format.rpl.mapend"}, /* are really exclusive to /map */ - {"265", "format.rpl.localusers"}, - {"266", "format.rpl.globalusers"}, - {"320", "format.rpl.whoisspecial"}, - {"330", "format.rpl.whoisaccount"}, - {"333", "format.rpl.topicwhotime"}, - {"338", "format.rpl.whoisactually"}, - {"378", "format.rpl.whoishost"}, - {"379", "format.rpl.whoismodes"}, - {"671", "format.rpl.whoissecure"}, - /* Pseudo commands for specific formatting */ - {"MODE-NICK-SELF", "format.mode.nick.self"}, - {"MODE-NICK", "format.mode.nick"}, - {"MODE-CHANNEL", "format.mode.channel"}, - {"PRIVMSG-ACTION", "format.action"}, - {"PRIVMSG-CTCP", "format.ctcp.request"}, - {"NOTICE-CTCP", "format.ctcp.answer"}, - {NULL, NULL}, -}; - -struct { wchar_t string[INPUT_MAX]; unsigned counter; char *history[INPUT_HIST_MAX]; @@ -564,32 +358,32 @@ ui_redraw(void) { windows[Win_dummy].h = LINES; windows[Win_dummy].w = COLS; - format = ui_format(NULL, config_gets("format.ui.separator.horizontal"), NULL); + format = format(NULL, config_gets("format.ui.separator.horizontal"), NULL); for (i = x; i <= COLS - rx; i++) { wmove(windows[Win_dummy].window, LINES - 2, i); ui_wprintc(&windows[Win_dummy], 1, "%s", format); } if (x) { - format = ui_format(NULL, config_gets("format.ui.separator.vertical"), NULL); + format = format(NULL, config_gets("format.ui.separator.vertical"), NULL); for (i = 0; i <= LINES; i++) { wmove(windows[Win_dummy].window, i, x - 1); ui_wprintc(&windows[Win_dummy], 1, "%s", format); } - format = ui_format(NULL, config_gets("format.ui.separator.split.left"), NULL); + format = format(NULL, config_gets("format.ui.separator.split.left"), NULL); wmove(windows[Win_dummy].window, LINES - 2, x - 1); ui_wprintc(&windows[Win_dummy], 1, "%s", format); } if (rx) { - format = ui_format(NULL, config_gets("format.ui.separator.vertical"), NULL); + format = format(NULL, config_gets("format.ui.separator.vertical"), NULL); for (i = 0; i <= LINES; i++) { wmove(windows[Win_dummy].window, i, COLS - rx); ui_wprintc(&windows[Win_dummy], 1, "%s", format); } - format = ui_format(NULL, config_gets("format.ui.separator.split.right"), NULL); + format = format(NULL, config_gets("format.ui.separator.split.right"), NULL); wmove(windows[Win_dummy].window, LINES - 2, COLS - rx); ui_wprintc(&windows[Win_dummy], 1, "%s", format); } @@ -659,7 +453,7 @@ ui_draw_nicklist(void) { for (i=0, p = selected.channel->nicks; p && p->next && p->next->next && i < windows[Win_nicklist].scroll; i++) p = p->next; if (i != 0) { - ui_wprintc(&windows[Win_nicklist], 1, "%s\n", ui_format(NULL, config_gets("format.ui.nicklist.more"), NULL)); + ui_wprintc(&windows[Win_nicklist], 1, "%s\n", format(NULL, config_gets("format.ui.nicklist.more"), NULL)); y++; p = p->next; windows[Win_nicklist].scroll = i; @@ -671,7 +465,7 @@ ui_draw_nicklist(void) { } if (p) - ui_wprintc(&windows[Win_nicklist], 1, "%s\n", ui_format(NULL, config_gets("format.ui.nicklist.more"), NULL)); + ui_wprintc(&windows[Win_nicklist], 1, "%s\n", format(NULL, config_gets("format.ui.nicklist.more"), NULL)); } int @@ -744,24 +538,6 @@ ui_buflist_get(int num, struct Server **server, struct Channel **chan) { return -1; } -static char * -ui_format_activity(int activity) { - switch (activity) { - case Activity_status: - return ui_format(NULL, config_gets("format.ui.buflist.activity.status"), NULL); - case Activity_error: - return ui_format(NULL, config_gets("format.ui.buflist.activity.error"), NULL); - case Activity_message: - return ui_format(NULL, config_gets("format.ui.buflist.activity.message"), NULL); - case Activity_hilight: - return ui_format(NULL, config_gets("format.ui.buflist.activity.hilight"), NULL); - default: - return ui_format(NULL, config_gets("format.ui.buflist.activity.none"), NULL); - } - - return NULL; /* shouldn't be possible *shrug*/ -} - void ui_draw_buflist(void) { struct Server *sp; @@ -780,7 +556,7 @@ ui_draw_buflist(void) { return; if (scroll > 0) { - ui_wprintc(&windows[Win_buflist], 1, "%s\n", ui_format(NULL, config_gets("format.ui.buflist.more"), NULL)); + ui_wprintc(&windows[Win_buflist], 1, "%s\n", format(NULL, config_gets("format.ui.buflist.more"), NULL)); } else if (scroll < i) { if (selected.history == main_buf) wattron(windows[Win_buflist].window, A_BOLD); @@ -795,9 +571,9 @@ ui_draw_buflist(void) { wattron(windows[Win_buflist].window, A_BOLD); if (sp->status == ConnStatus_notconnected) - indicator = ui_format(NULL, config_gets("format.ui.buflist.old"), NULL); + indicator = format(NULL, config_gets("format.ui.buflist.old"), NULL); else - indicator = ui_format_activity(sp->history->activity); + indicator = format_get_bufact(sp->history->activity); ui_wprintc(&windows[Win_buflist], 1, "%02d: %s─ %s%s\n", i, sp->next ? "├" : "└", indicator, sp->name); wattrset(windows[Win_buflist].window, A_NORMAL); @@ -810,9 +586,9 @@ ui_draw_buflist(void) { wattron(windows[Win_buflist].window, A_BOLD); if (chp->old) - indicator = ui_format(NULL, config_gets("format.ui.buflist.old"), NULL); + indicator = format(NULL, config_gets("format.ui.buflist.old"), NULL); else - indicator = ui_format_activity(chp->history->activity); + indicator = format_get_bufact(chp->history->activity); ui_wprintc(&windows[Win_buflist], 1, "%02d: %s %s─ %s%s\n", i, sp->next ? "│" : " ", chp->next || sp->privs ? "├" : "└", indicator, chp->name); @@ -827,9 +603,9 @@ ui_draw_buflist(void) { wattron(windows[Win_buflist].window, A_BOLD); if (prp->old) - indicator = ui_format(NULL, config_gets("format.ui.buflist.old"), NULL); + indicator = format(NULL, config_gets("format.ui.buflist.old"), NULL); else - indicator = ui_format_activity(prp->history->activity); + indicator = format_get_bufact(prp->history->activity); ui_wprintc(&windows[Win_buflist], 1, "%02d: %s %s─ %s%s\n", i, sp->next ? "│" : " ", prp->next ? "├" : "└", indicator, prp->name); @@ -841,7 +617,7 @@ ui_draw_buflist(void) { if (i <= ui_buflist_count(NULL, NULL, NULL)) { wmove(windows[Win_buflist].window, windows[Win_buflist].h - 1, 0); - ui_wprintc(&windows[Win_buflist], 1, "%s\n", ui_format(NULL, config_gets("format.ui.buflist.more"), NULL)); + ui_wprintc(&windows[Win_buflist], 1, "%s\n", format(NULL, config_gets("format.ui.buflist.more"), NULL)); wclrtoeol(windows[Win_buflist].window); } } @@ -1030,49 +806,6 @@ ui_strlenc(struct Window *window, char *s, int *lines) { return ret; } -char * -ui_hformat(struct Window *window, struct History *hist) { - char *cmd, *p1, *p2; - int i; - - if (!hist) - return NULL; - - if (!hist->params) - goto raw; - - cmd = *(hist->params); - p1 = *(hist->params+1); - p2 = *(hist->params+2); - - if (strcmp_n(cmd, "MODE") == 0) { - if (p1 && serv_ischannel(hist->origin->server, p1)) - cmd = "MODE-CHANNEL"; - else if (hist->from && nick_isself(hist->from) && strcmp_n(hist->from->nick, p1) == 0) - cmd = "MODE-NICK-SELF"; - else - cmd = "MODE-NICK"; - } else if (strcmp_n(cmd, "PRIVMSG") == 0) { - /* ascii 1 is ^A */ - if (*p2 == 1 && strncmp(p2 + 1, "ACTION", strlen("ACTION")) == 0) - cmd = "PRIVMSG-ACTION"; - else if (*p2 == 1) - cmd = "PRIVMSG-CTCP"; - } else if (strcmp_n(cmd, "NOTICE") == 0 && *p2 == 1) { - cmd = "NOTICE-CTCP"; - } - - for (i=0; formatmap[i].cmd; i++) - if (formatmap[i].format && strcmp_n(formatmap[i].cmd, cmd) == 0) - return ui_format(window, config_gets(formatmap[i].format), hist); - - if (isdigit(*cmd) && isdigit(*(cmd+1)) && isdigit(*(cmd+2)) && !*(cmd+3)) - return ui_format(window, config_gets("format.rpl.other"), hist); - -raw: - return ui_format(window, config_gets("format.other"), hist); -} - void ui_draw_main(void) { struct History *p, *hp; @@ -1093,7 +826,7 @@ ui_draw_main(void) { if (i < windows[Win_main].scroll) i++; if (!p->format) - p->format = estrdup(ui_hformat(&windows[Win_main], p)); + p->format = estrdup(format(&windows[Win_main], NULL, p)); } if (windows[Win_main].scroll > 0) @@ -1119,7 +852,7 @@ ui_draw_main(void) { if (selected.channel && selected.channel->topic) { wmove(windows[Win_main].window, 0, 0); - ui_wprintc(&windows[Win_main], 0, "%s\n", ui_format(&windows[Win_main], config_gets("format.ui.topic"), NULL)); + ui_wprintc(&windows[Win_main], 0, "%s\n", format(&windows[Win_main], config_gets("format.ui.topic"), NULL)); } } @@ -1174,471 +907,6 @@ ui_select(struct Server *server, struct Channel *channel) { ui_redraw(); } -static char * -ui_format_get_content(char *sstr, int nesting) { - static char ret[8192]; - int layer, rc; - - for (layer = 0, rc = 0; sstr && *sstr && rc < sizeof(ret); sstr++) { - switch (*sstr) { - case '}': - if (nesting && layer) { - ret[rc++] = '}'; - layer--; - } else { - goto end; - } - break; - case '{': - if (nesting) - layer++; - ret[rc++] = '{'; - break; - default: - ret[rc++] = *sstr; - break; - } - } - -end: - ret[rc] = '\0'; - return ret; -} - -char * -ui_format_(struct Window *window, char *format, struct History *hist, int recursive) { - static char *ret; - struct Nick *nick; - size_t rs = BUFSIZ; - size_t rc, pc; - int escape, i; - long long pn; - int rhs = 0; - int divider = 0; - char **params; - char *content, *p, *p2; - char *ts, *save; - char colourbuf[2][3]; - char priv[2]; - char chs[2]; - size_t len; - enum { - sub_raw, - sub_cmd, - sub_nick, - sub_ident, - sub_host, - sub_priv, - sub_channel, - sub_topic, - sub_server, - sub_time, - }; - struct { - char *name; - char *val; - } subs[] = { - [sub_raw] = {"raw", NULL}, - [sub_cmd] = {"cmd", NULL}, - [sub_nick] = {"nick", NULL}, - [sub_ident] = {"ident", NULL}, - [sub_host] = {"host", NULL}, - [sub_priv] = {"priv", NULL}, - [sub_channel] = {"channel", NULL}, - [sub_topic] = {"topic", NULL}, - [sub_server] = {"server", NULL}, - [sub_time] = {"time", NULL}, - {NULL, NULL}, - }; - - pfree(&ret); - ret = emalloc(rs); - - subs[sub_channel].val = selected.channel ? selected.channel->name : NULL; - subs[sub_topic].val = selected.channel ? selected.channel->topic : NULL; - subs[sub_server].val = selected.server ? selected.server->name : NULL; - - if (hist) { - subs[sub_raw].val = hist->raw; - subs[sub_nick].val = hist->from ? hist->from->nick : NULL; - subs[sub_ident].val = hist->from ? hist->from->ident : NULL; - subs[sub_host].val = hist->from ? hist->from->host : NULL; - - if (hist->from) { - priv[0] = hist->from->priv; - priv[priv[0] != ' '] = '\0'; - subs[sub_priv].val = priv; - } - - if (hist->origin) { - if (hist->origin->channel) { - if (!recursive) - divider = config_getl("divider.toggle"); - subs[sub_channel].val = hist->origin->channel->name; - subs[sub_topic].val = hist->origin->channel->topic; - } - if (hist->origin->server) { - subs[sub_server].val = hist->origin->server->name; - } - } - - len = snprintf(subs[sub_time].val, 0, "%lld", (long long)hist->timestamp) + 1; - subs[sub_time].val = emalloc(len); - snprintf(subs[sub_time].val, len, "%lld", (long long)hist->timestamp); - - params = hist->params; - subs[sub_cmd].val = *params; - params++; - } - - if (!recursive && hist && config_getl("timestamp.toggle")) { - ts = estrdup(ui_format_(NULL, config_gets("format.ui.timestamp"), hist, 1)); - } else { - ts = ""; - } - - for (escape = 0, rc = 0; format && *format && rc < rs; ) { -outcont: - if (rc > rs / 2) { - rs *= 2; - ret = erealloc(ret, rs); - } - - if (!escape && *format == '$' && *(format+1) == '{' && strchr(format, '}')) { - escape = 0; - content = ui_format_get_content(format+2, 0); - - for (p = content; *p && isdigit(*p); p++); - /* If all are digits, *p == '\0' */ - if (!*p && hist) { - pn = strtol(content, NULL, 10) - 1; - if (pn >= 0 && param_len(params) > pn) { - if (**(params+pn) == 1 && strncmp((*(params+pn))+1, "ACTION", strlen("ACTION")) == 0 && strchr(*(params+pn), ' ')) - rc += snprintf(&ret[rc], rs - rc, "%s", struntil(strchr(*(params+pn), ' ') + 1, 1)); - else if (**(params+pn) == 1) - rc += snprintf(&ret[rc], rs - rc, "%s", struntil((*(params+pn)) + 1, 1)); - else - rc += snprintf(&ret[rc], rs - rc, "%s", *(params+pn)); - format = strchr(format, '}') + 1; - continue; - } - } - /* All are digits except a trailing '-' */ - if (*p == '-' && *(p+1) == '\0' && hist) { - pn = strtol(content, NULL, 10) - 1; - if (pn >= 0 && param_len(params) > pn) { - for (; *(params+pn) != NULL; pn++) { - if (**(params+pn) == 1 && strncmp((*(params+pn))+1, "ACTION", strlen("ACTION")) == 0 && strchr(*(params+pn), ' ')) { - rc += snprintf(&ret[rc], rs - rc, "%s%s", - struntil(strchr(*(params+pn), ' ') + 1, 1), - *(params+pn+1) ? " " : ""); - } else if (**(params+pn) == 1) { - rc += snprintf(&ret[rc], rs - rc, "%s%s", - struntil((*(params+pn)) + 1, 1), - *(params+pn+1) ? " " : ""); - } else { - rc += snprintf(&ret[rc], rs - rc, "%s%s", - *(params+pn), *(params+pn+1) ? " " : ""); - } - } - format = strchr(format, '}') + 1; - continue; - } - } - - for (i=0; subs[i].name; i++) { - if (strcmp_n(subs[i].name, content) == 0) { - if (subs[i].val) - rc += snprintf(&ret[rc], rs - rc, "%s", subs[i].val); - format = strchr(format, '}') + 1; - goto outcont; /* unfortunately, need to use a goto as we are already in a loop */ - } - } - } - - if (!escape && *format == '%' && *(format+1) == '{' && strchr(format, '}')) { - escape = 0; - content = ui_format_get_content(format+2, 0); - - switch (*content) { - case 'b': - case 'B': - ret[rc++] = 2; /* ^B */ - format = strchr(format, '}') + 1; - continue; - case 'c': - case 'C': - if (*(content+1) == ':' && isdigit(*(content+2))) { - content += 2; - memset(colourbuf, 0, sizeof(colourbuf)); - colourbuf[0][0] = *content; - content++; - if (isdigit(*content)) { - colourbuf[0][1] = *content; - content += 1; - } - if (*content == ',' && isdigit(*(content+1))) { - colourbuf[1][0] = *(content+1); - content += 2; - } - if (colourbuf[1][0] && isdigit(*content)) { - colourbuf[1][1] = *(content); - content += 1; - } - if (*content == '\0') { - rc += snprintf(&ret[rc], rs - rc, "%c%02d,%02d", 3 /* ^C */, - atoi(colourbuf[0]), colourbuf[1][0] ? atoi(colourbuf[1]) : 99); - format = strchr(format, '}') + 1; - continue; - } - } - break; - case 'i': - case 'I': - if (*(content+1) == '\0') { - ret[rc++] = 9; /* ^I */ - format = strchr(format, '}') + 1; - continue; - } - break; - case 'o': - case 'O': - if (*(content+1) == '\0') { - ret[rc++] = 15; /* ^O */ - format = strchr(format, '}') + 1; - continue; - } - break; - case 'r': - case 'R': - if (*(content+1) == '\0') { - ret[rc++] = 18; /* ^R */ - format = strchr(format, '}') + 1; - continue; - } - break; - case 'u': - case 'U': - if (*(content+1) == '\0') { - ret[rc++] = 21; /* ^U */ - format = strchr(format, '}') + 1; - continue; - } - break; - case '=': - if (*(content+1) == '\0' && divider) { - rhs = 1; - ret[rc] = '\0'; - /* strlen(ret) - ui_strlenc(window, ret, NULL) should get - * the length of hidden characters. Add this onto the - * margin to pad out properly. */ - /* Save ret for use in snprintf */ - save = estrdup(ret); - rc = snprintf(ret, rs, "%1$*3$s%2$s", save, config_gets("divider.string"), - config_getl("divider.margin") + (strlen(ret) - ui_strlenc(window, ret, NULL))); - pfree(&save); - format = strchr(format, '}') + 1; - continue; - } else if (*(content+1) == '\0') { - ret[rc++] = ' '; - format = strchr(format, '}') + 1; - continue; - } - break; - } - - /* pad, nick, split, rdate and time, must then continue as they modify content - * - * These styling formatters are quite ugly and repetitive. - * %{nick:...} was implemented first, and has the most (all of them :)) comments */ - if (strncmp(content, "pad:", strlen("pad:")) == 0 && strchr(content, ',')) { - pn = strtol(content + strlen("pad:"), NULL, 10); - content = estrdup(ui_format_get_content(strchr(format+2+strlen("pad:"), ',') + 1, 1)); - save = ret; - ret = NULL; - ui_format_(NULL, content, hist, 1); - rc += snprintf(&save[rc], rs - rc, "%1$*2$s", ret, pn); - pfree(&ret); - ret = save; - format = strchr(format+2+strlen("pad:"), ',') + strlen(content) + 2; - - pfree(&content); - continue; - } - - if (strncmp(content, "rdate:", strlen("rdate:")) == 0) { - content = estrdup(ui_format_get_content(format+2+strlen("rdate:"), 1)); - save = ret; - ret = NULL; - ui_format_(NULL, content, hist, 1); - pn = strtoll(ret, NULL, 10); - rc += snprintf(&save[rc], rs - rc, "%s", strrdate((time_t)pn)); - format += 3 + strlen("rdate:") + strlen(content); - - pfree(&ret); - ret = save; - pfree(&content); - continue; - } - - if (strncmp(content, "time:", strlen("time:")) == 0 && strchr(content, ',')) { - content = estrdup(ui_format_get_content(strchr(format+2+strlen("time:"), ',') + 1, 1)); - save = ret; - ret = NULL; - ui_format_(NULL, content, hist, 1); - pn = strtoll(ret, NULL, 10); - p = struntil(format+2+strlen("time:"), ','); - - rc += strftime(&save[rc], rs - rc, p, localtime((time_t *)&pn)); - format = strchr(format+2+strlen("time:"), ',') + strlen(content) + 2; - - pfree(&ret); - ret = save; - pfree(&content); - continue; - } - - /* second comma ptr - second comma ptr = distance. - * If the distance is 2, then there is one non-comma char between. */ - p = strchr(content, ','); - if (p) - p2 = strchr(p + 1, ','); - if (strncmp(content, "split:", strlen("split:")) == 0 && p2 - p == 2) { - pn = strtol(content + strlen("split:"), NULL, 10); - chs[0] = *(strchr(content, ',') + 1); - chs[1] = '\0'; - content = estrdup(ui_format_get_content( - strchr( - strchr(format+2+strlen("split:"), ',') + 1, - ',') + 1, - 1)); - save = ret; - ret = NULL; - ui_format_(NULL, content, hist, 1); - rc += snprintf(&save[rc], rs - rc, "%s", strntok(ret, chs, pn)); - format = strchr( - strchr(format+2+strlen("split:"), ',') + 1, - ',') + strlen(content) + 2; - - pfree(&ret); - ret = save; - pfree(&content); - continue; - } - - if (hist && !recursive && strncmp(content, "nick:", strlen("nick:")) == 0) { - content = estrdup(ui_format_get_content(format+2+strlen("nick:"), 1)); - save = ret; - ret = NULL; - ui_format_(NULL, content, hist, 1); - nick = nick_create(ret, ' ', hist->origin ? hist->origin->server : NULL); - rc += snprintf(&save[rc], rs - rc, "%c%02d", 3 /* ^C */, nick_getcolour(nick)); - format += 3 + strlen("nick:") + strlen(content); - - pfree(&ret); - ret = save; - nick_free(nick); - pfree(&content); - continue; - } - } - - if (escape && *format == 'n') { - ret[rc++] = '\n'; - rc += snprintf(&ret[rc], rs - rc, "%1$*3$s%2$s", "", config_gets("divider.string"), - ui_strlenc(NULL, ts, NULL) + config_getl("divider.margin")); - escape = 0; - format++; - continue; - } - - if (escape && (*format == '%' || *format == '$') && *(format+1) == '{' && strchr(format, '}')) - escape = 0; - - if (escape) { - ret[rc++] = '\\'; - escape = 0; - } - - if (*format == '\\') { - escape = 1; - format++; - } else { - ret[rc++] = *format; - format++; - } - } - - ret[rc] = '\0'; - if (!recursive && divider && !rhs) { - save = estrdup(ret); - rc = snprintf(ret, rs, "%1$*4$s%2$s%3$s", "", config_gets("divider.string"), save, config_getl("divider.margin")); - pfree(&save); - } - - save = estrdup(ret); - rc = snprintf(ret, rs, "%s%s", ts, save); - pfree(&save); - - if (!recursive && window) { - for (p = ret, pc = 0; p && p <= (ret + rs); p++) { - /* lifted from ui_strlenc */ - switch (*p) { - case 2: /* ^B */ - case 9: /* ^I */ - case 15: /* ^O */ - case 18: /* ^R */ - case 21: /* ^U */ - break; - case 3: /* ^C */ - if (*p && isdigit(*(p+1))) - p += 1; - if (*p && isdigit(*(p+1))) - p += 1; - if (*p && *(p+1) == ',' && isdigit(*(p+2))) - p += 2; - if (*p && *(p-1) == ',' && isdigit(*(p+1))) - p += 1; - break; - default: - /* naive utf-8 handling: - * the 2-nth byte always - * follows 10xxxxxxx, so - * don't count it. */ - if ((*p & 0xC0) != 0x80) - pc++; - - if (*p == '\n') { - p++; - pc = 0; - } - - if (pc == window->w) { - save = estrdup(p); - - if (divider) { - p += snprintf(p, rs - ((size_t)(p - ret)), "%1$*4$s %2$s%3$s", - "", config_gets("divider.string"), save, - config_getl("divider.margin") + ui_strlenc(NULL, ts, NULL)); - } else { - p += snprintf(p, rs - ((size_t)(p - ret)), "%1$*3$s %2$s", "", save, ui_strlenc(NULL, ts, NULL)); - } - - pfree(&save); - pc = 0; - } - } - } - } - - if (subs[sub_time].val) - pfree(&subs[sub_time].val); - if (ts[0] != '\0') - pfree(&ts); - - return ret; -} - char * ui_rectrl(char *str) { static char ret[8192];