LemonBoy 3fcd3cd2b9 Remove the regexp_compiled field.
It was made redundant by the introduction of the pointer to the GRegex
structure.
Silence the compiler warning in textbuffer.c about preg being
initialized by setting it to NULL.
2017-01-02 17:50:14 +01:00

736 lines
19 KiB
C

/*
hilight-text.c : irssi
Copyright (C) 1999-2000 Timo Sirainen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "module.h"
#include "module-formats.h"
#include "signals.h"
#include "commands.h"
#include "levels.h"
#include "misc.h"
#include "lib-config/iconfig.h"
#include "settings.h"
#include "servers.h"
#include "channels.h"
#include "nicklist.h"
#include "hilight-text.h"
#include "nickmatch-cache.h"
#include "printtext.h"
#include "formats.h"
static NICKMATCH_REC *nickmatch;
static int never_hilight_level, default_hilight_level;
GSList *hilights;
static void reset_level_cache(void)
{
GSList *tmp;
never_hilight_level = MSGLEVEL_ALL & ~default_hilight_level;
for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
HILIGHT_REC *rec = tmp->data;
if (never_hilight_level & rec->level)
never_hilight_level &= ~rec->level;
}
}
static void reset_cache(void)
{
reset_level_cache();
nickmatch_rebuild(nickmatch);
}
static void hilight_add_config(HILIGHT_REC *rec)
{
CONFIG_NODE *node;
g_return_if_fail(rec != NULL);
node = iconfig_node_traverse("(hilights", TRUE);
node = iconfig_node_section(node, NULL, NODE_TYPE_BLOCK);
iconfig_node_set_str(node, "text", rec->text);
if (rec->level > 0) iconfig_node_set_int(node, "level", rec->level);
if (rec->color) iconfig_node_set_str(node, "color", rec->color);
if (rec->act_color) iconfig_node_set_str(node, "act_color", rec->act_color);
if (rec->priority > 0) iconfig_node_set_int(node, "priority", rec->priority);
iconfig_node_set_bool(node, "nick", rec->nick);
iconfig_node_set_bool(node, "word", rec->word);
if (rec->nickmask) iconfig_node_set_bool(node, "mask", TRUE);
if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
if (rec->case_sensitive) iconfig_node_set_bool(node, "matchcase", TRUE);
if (rec->servertag) iconfig_node_set_str(node, "servertag", rec->servertag);
if (rec->channels != NULL && *rec->channels != NULL) {
node = iconfig_node_section(node, "channels", NODE_TYPE_LIST);
iconfig_node_add_list(node, rec->channels);
}
}
static void hilight_remove_config(HILIGHT_REC *rec)
{
CONFIG_NODE *node;
g_return_if_fail(rec != NULL);
node = iconfig_node_traverse("hilights", FALSE);
if (node != NULL) iconfig_node_list_remove(node, g_slist_index(hilights, rec));
}
static void hilight_destroy(HILIGHT_REC *rec)
{
g_return_if_fail(rec != NULL);
if (rec->preg != NULL) g_regex_unref(rec->preg);
if (rec->channels != NULL) g_strfreev(rec->channels);
g_free_not_null(rec->color);
g_free_not_null(rec->act_color);
g_free(rec->text);
g_free(rec);
}
static void hilights_destroy_all(void)
{
g_slist_foreach(hilights, (GFunc) hilight_destroy, NULL);
g_slist_free(hilights);
hilights = NULL;
}
static void hilight_init_rec(HILIGHT_REC *rec)
{
if (rec->preg != NULL)
g_regex_unref(rec->preg);
rec->preg = g_regex_new(rec->text, G_REGEX_CASELESS, 0, NULL);
}
void hilight_create(HILIGHT_REC *rec)
{
if (g_slist_find(hilights, rec) != NULL) {
hilight_remove_config(rec);
hilights = g_slist_remove(hilights, rec);
}
hilights = g_slist_append(hilights, rec);
hilight_add_config(rec);
hilight_init_rec(rec);
signal_emit("hilight created", 1, rec);
}
void hilight_remove(HILIGHT_REC *rec)
{
g_return_if_fail(rec != NULL);
hilight_remove_config(rec);
hilights = g_slist_remove(hilights, rec);
signal_emit("hilight destroyed", 1, rec);
hilight_destroy(rec);
}
static HILIGHT_REC *hilight_find(const char *text, char **channels)
{
GSList *tmp;
char **chan;
g_return_val_if_fail(text != NULL, NULL);
for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
HILIGHT_REC *rec = tmp->data;
if (g_ascii_strcasecmp(rec->text, text) != 0)
continue;
if ((channels == NULL && rec->channels == NULL))
return rec; /* no channels - ok */
if (channels != NULL && g_strcmp0(*channels, "*") == 0)
return rec; /* ignore channels */
if (channels == NULL || rec->channels == NULL)
continue; /* other doesn't have channels */
if (g_strv_length(channels) != g_strv_length(rec->channels))
continue; /* different amount of channels */
/* check that channels match */
for (chan = channels; *chan != NULL; chan++) {
if (strarray_find(rec->channels, *chan) == -1)
break;
}
if (*chan == NULL)
return rec; /* channels ok */
}
return NULL;
}
static int hilight_match_text(HILIGHT_REC *rec, const char *text,
int *match_beg, int *match_end)
{
char *match;
if (rec->regexp) {
GMatchInfo *match;
if (rec->preg != NULL) {
g_regex_match (rec->preg, text, 0, &match);
if (g_match_info_matches(match)) {
return g_match_info_fetch_pos(match, 0, match_beg, match_end);
}
}
} else {
if (rec->case_sensitive) {
match = rec->fullword ?
strstr_full(text, rec->text) :
strstr(text, rec->text);
} else {
match = rec->fullword ?
stristr_full(text, rec->text) :
stristr(text, rec->text);
}
if (match != NULL) {
if (match_beg != NULL && match_end != NULL) {
*match_beg = (int) (match-text);
*match_end = *match_beg + strlen(rec->text);
}
return TRUE;
}
}
return FALSE;
}
#define hilight_match_level(rec, level) \
(level & (((rec)->level != 0 ? rec->level : default_hilight_level)))
#define hilight_match_channel(rec, channel) \
((rec)->channels == NULL || ((channel) != NULL && \
strarray_find((rec)->channels, (channel)) != -1))
HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
const char *nick, const char *address,
int level, const char *str,
int *match_beg, int *match_end)
{
GSList *tmp;
CHANNEL_REC *chanrec;
NICK_REC *nickrec;
g_return_val_if_fail(str != NULL, NULL);
if ((never_hilight_level & level) == level)
return NULL;
if (nick != NULL) {
/* check nick mask hilights */
chanrec = channel_find(server, channel);
nickrec = chanrec == NULL ? NULL :
nicklist_find(chanrec, nick);
if (nickrec != NULL) {
HILIGHT_REC *rec;
if (nickrec->host == NULL)
nicklist_set_host(chanrec, nickrec, address);
rec = nickmatch_find(nickmatch, nickrec);
if (rec != NULL && hilight_match_level(rec, level))
return rec;
}
}
for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
HILIGHT_REC *rec = tmp->data;
if (!rec->nickmask && hilight_match_level(rec, level) &&
hilight_match_channel(rec, channel) &&
(rec->servertag == NULL ||
(server != NULL && g_ascii_strcasecmp(rec->servertag, server->tag) == 0)) &&
hilight_match_text(rec, str, match_beg, match_end))
return rec;
}
return NULL;
}
static char *hilight_get_act_color(HILIGHT_REC *rec)
{
g_return_val_if_fail(rec != NULL, NULL);
return g_strdup(rec->act_color != NULL ? rec->act_color :
rec->color != NULL ? rec->color :
settings_get_str("hilight_act_color"));
}
char *hilight_get_color(HILIGHT_REC *rec)
{
const char *color;
g_return_val_if_fail(rec != NULL, NULL);
color = rec->color != NULL ? rec->color :
settings_get_str("hilight_color");
return format_string_expand(color, NULL);
}
void hilight_update_text_dest(TEXT_DEST_REC *dest, HILIGHT_REC *rec)
{
dest->level |= MSGLEVEL_HILIGHT;
if (rec->priority > 0)
dest->hilight_priority = rec->priority;
g_free_and_null(dest->hilight_color);
if (rec->act_color != NULL && g_strcmp0(rec->act_color, "%n") == 0)
dest->level |= MSGLEVEL_NO_ACT;
else
dest->hilight_color = hilight_get_act_color(rec);
}
static void hilight_print(int index, HILIGHT_REC *rec);
static void sig_print_text(TEXT_DEST_REC *dest, const char *text,
const char *stripped)
{
HILIGHT_REC *hilight;
char *color, *newstr;
int old_level, hilight_start, hilight_end, hilight_len;
int nick_match;
if (dest->level & MSGLEVEL_NOHILIGHT)
return;
hilight_start = hilight_end = 0;
hilight = hilight_match(dest->server, dest->target, dest->nick,
dest->address, dest->level, stripped,
&hilight_start, &hilight_end);
if (hilight == NULL)
return;
nick_match = hilight->nick && (dest->level & (MSGLEVEL_PUBLIC|MSGLEVEL_ACTIONS)) == MSGLEVEL_PUBLIC;
old_level = dest->level;
if (!nick_match || (dest->level & MSGLEVEL_HILIGHT)) {
/* Remove NO_ACT, this means explicitly defined hilights will bypass
* /IGNORE ... NO_ACT.
* (It's still possible to use /hilight -actcolor %n to hide
* hilight/beep).
*/
dest->level &= ~MSGLEVEL_NO_ACT;
/* update the level / hilight info */
hilight_update_text_dest(dest, hilight);
}
if (nick_match)
return; /* fe-messages.c should have taken care of this */
if (old_level & MSGLEVEL_HILIGHT) {
/* nick is highlighted, just set priority */
return;
}
color = hilight_get_color(hilight);
hilight_len = hilight_end-hilight_start;
if (!hilight->word) {
/* hilight whole line */
char *tmp = strip_codes(text);
newstr = g_strconcat(color, tmp, NULL);
g_free(tmp);
} else {
/* hilight part of the line */
GString *tmp;
char *middle;
int pos, color_pos, color_len;
tmp = g_string_new(NULL);
/* start of the line */
pos = strip_real_length(text, hilight_start, NULL, NULL);
g_string_append(tmp, text);
g_string_truncate(tmp, pos);
/* color */
g_string_append(tmp, color);
/* middle of the line, stripped */
middle = strip_codes(text+pos);
pos = tmp->len;
g_string_append(tmp, middle);
g_string_truncate(tmp, pos+hilight_len);
g_free(middle);
/* end of the line */
pos = strip_real_length(text, hilight_end,
&color_pos, &color_len);
if (color_pos > 0)
g_string_append_len(tmp, text+color_pos, color_len);
else {
/* no colors in line, change back to default */
g_string_append_c(tmp, 4);
g_string_append_c(tmp, FORMAT_STYLE_DEFAULTS);
}
g_string_append(tmp, text+pos);
newstr = tmp->str;
g_string_free(tmp, FALSE);
}
signal_emit("print text", 3, dest, newstr, stripped);
g_free(color);
g_free(newstr);
signal_stop();
}
HILIGHT_REC *hilight_match_nick(SERVER_REC *server, const char *channel,
const char *nick, const char *address,
int level, const char *msg)
{
HILIGHT_REC *rec;
rec = hilight_match(server, channel, nick, address,
level, msg, NULL, NULL);
return (rec == NULL || !rec->nick) ? NULL : rec;
}
static void read_hilight_config(void)
{
CONFIG_NODE *node;
HILIGHT_REC *rec;
GSList *tmp;
char *text, *color;
hilights_destroy_all();
node = iconfig_node_traverse("hilights", FALSE);
if (node == NULL) {
reset_cache();
return;
}
tmp = config_node_first(node->value);
for (; tmp != NULL; tmp = config_node_next(tmp)) {
node = tmp->data;
if (node->type != NODE_TYPE_BLOCK)
continue;
text = config_node_get_str(node, "text", NULL);
if (text == NULL || *text == '\0')
continue;
rec = g_new0(HILIGHT_REC, 1);
hilights = g_slist_append(hilights, rec);
rec->text = g_strdup(text);
color = config_node_get_str(node, "color", NULL);
rec->color = color == NULL || *color == '\0' ? NULL :
g_strdup(color);
color = config_node_get_str(node, "act_color", NULL);
rec->act_color = color == NULL || *color == '\0' ? NULL :
g_strdup(color);
rec->level = config_node_get_int(node, "level", 0);
rec->priority = config_node_get_int(node, "priority", 0);
rec->nick = config_node_get_bool(node, "nick", TRUE);
rec->word = config_node_get_bool(node, "word", TRUE);
rec->case_sensitive = config_node_get_bool(node, "matchcase", FALSE);
rec->nickmask = config_node_get_bool(node, "mask", FALSE);
rec->fullword = config_node_get_bool(node, "fullword", FALSE);
rec->regexp = config_node_get_bool(node, "regexp", FALSE);
rec->servertag = config_node_get_str(node, "servertag", NULL);
hilight_init_rec(rec);
node = iconfig_node_section(node, "channels", -1);
if (node != NULL) rec->channels = config_node_get_list(node);
}
reset_cache();
}
static void hilight_print(int index, HILIGHT_REC *rec)
{
char *chans, *levelstr;
GString *options;
options = g_string_new(NULL);
if (rec->nick && rec->word) { /* default case, no option */ }
else if (rec->nick)
g_string_append(options, "-nick ");
else if (rec->word)
g_string_append(options, "-word ");
else
g_string_append(options, "-line ");
if (rec->nickmask) g_string_append(options, "-mask ");
if (rec->fullword) g_string_append(options, "-full ");
if (rec->case_sensitive) g_string_append(options, "-matchcase ");
if (rec->regexp) {
g_string_append(options, "-regexp ");
if (rec->preg == NULL)
g_string_append(options, "[INVALID!] ");
}
if (rec->priority != 0)
g_string_append_printf(options, "-priority %d ", rec->priority);
if (rec->servertag != NULL)
g_string_append_printf(options, "-network %s ", rec->servertag);
if (rec->color != NULL)
g_string_append_printf(options, "-color %s ", rec->color);
if (rec->act_color != NULL)
g_string_append_printf(options, "-actcolor %s ", rec->act_color);
chans = rec->channels == NULL ? NULL :
g_strjoinv(",", rec->channels);
levelstr = rec->level == 0 ? NULL :
bits2level(rec->level);
if (levelstr != NULL)
levelstr = g_strconcat(levelstr, " ", NULL);
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
TXT_HILIGHT_LINE, index, rec->text,
chans != NULL ? chans : "",
levelstr != NULL ? levelstr : "",
options->str);
g_free_not_null(chans);
g_free_not_null(levelstr);
g_string_free(options, TRUE);
}
static void cmd_hilight_show(void)
{
GSList *tmp;
int index;
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_HEADER);
index = 1;
for (tmp = hilights; tmp != NULL; tmp = tmp->next, index++) {
HILIGHT_REC *rec = tmp->data;
hilight_print(index, rec);
}
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_FOOTER);
}
/* SYNTAX: HILIGHT [-nick | -word | -line] [-mask | -full | -matchcase | -regexp]
[-color <color>] [-actcolor <color>] [-level <level>]
[-network <network>] [-channels <channels>] <text> */
static void cmd_hilight(const char *data)
{
GHashTable *optlist;
HILIGHT_REC *rec;
char *colorarg, *actcolorarg, *levelarg, *priorityarg, *chanarg, *text, *servertag;
char **channels;
void *free_arg;
g_return_if_fail(data != NULL);
if (*data == '\0') {
cmd_hilight_show();
return;
}
if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
PARAM_FLAG_GETREST, "hilight", &optlist, &text))
return;
chanarg = g_hash_table_lookup(optlist, "channels");
levelarg = g_hash_table_lookup(optlist, "level");
priorityarg = g_hash_table_lookup(optlist, "priority");
colorarg = g_hash_table_lookup(optlist, "color");
actcolorarg = g_hash_table_lookup(optlist, "actcolor");
servertag = g_hash_table_lookup(optlist, "network");
if (*text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
channels = (chanarg == NULL || *chanarg == '\0') ? NULL :
g_strsplit(chanarg, ",", -1);
rec = hilight_find(text, channels);
if (rec == NULL) {
rec = g_new0(HILIGHT_REC, 1);
/* default to nick/word hilighting */
rec->nick = TRUE;
rec->word = TRUE;
rec->text = g_strdup(text);
rec->channels = channels;
} else {
g_strfreev(channels);
}
rec->level = (levelarg == NULL || *levelarg == '\0') ? 0 :
level2bits(replace_chars(levelarg, ',', ' '), NULL);
rec->priority = priorityarg == NULL ? 0 : atoi(priorityarg);
if (g_hash_table_lookup(optlist, "line") != NULL) {
rec->word = FALSE;
rec->nick = FALSE;
}
if (g_hash_table_lookup(optlist, "word") != NULL) {
rec->word = TRUE;
rec->nick = FALSE;
}
if (g_hash_table_lookup(optlist, "nick") != NULL)
rec->nick = TRUE;
rec->nickmask = g_hash_table_lookup(optlist, "mask") != NULL;
rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
rec->case_sensitive = g_hash_table_lookup(optlist, "matchcase") != NULL;
if (colorarg != NULL) {
g_free_and_null(rec->color);
if (*colorarg != '\0')
rec->color = g_strdup(colorarg);
}
if (actcolorarg != NULL) {
g_free_and_null(rec->act_color);
if (*actcolorarg != '\0')
rec->act_color = g_strdup(actcolorarg);
}
if (servertag != NULL) {
g_free_and_null(rec->servertag);
if (*servertag != '\0')
rec->servertag = g_strdup(servertag);
}
hilight_create(rec);
hilight_print(g_slist_index(hilights, rec)+1, rec);
cmd_params_free(free_arg);
reset_cache();
}
/* SYNTAX: DEHILIGHT <id>|<mask> */
static void cmd_dehilight(const char *data)
{
HILIGHT_REC *rec;
GSList *tmp;
if (is_numeric(data, ' ')) {
/* with index number */
tmp = g_slist_nth(hilights, atoi(data)-1);
rec = tmp == NULL ? NULL : tmp->data;
} else {
/* with mask */
char *chans[2] = { "*", NULL };
rec = hilight_find(data, chans);
}
if (rec == NULL)
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_NOT_FOUND, data);
else {
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_REMOVED, rec->text);
hilight_remove(rec);
reset_cache();
}
}
static void hilight_nick_cache(GHashTable *list, CHANNEL_REC *channel,
NICK_REC *nick)
{
GSList *tmp;
HILIGHT_REC *match;
char *nickmask;
int len, best_match;
if (nick->host == NULL)
return; /* don't check until host is known */
nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
best_match = 0; match = NULL;
for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
HILIGHT_REC *rec = tmp->data;
if (rec->nickmask &&
hilight_match_channel(rec, channel->name) &&
match_wildcards(rec->text, nickmask)) {
len = strlen(rec->text);
if (best_match < len) {
best_match = len;
match = rec;
}
}
}
g_free_not_null(nickmask);
if (match != NULL)
g_hash_table_insert(list, nick, match);
}
static void read_settings(void)
{
default_hilight_level = settings_get_level("hilight_level");
reset_level_cache();
}
void hilight_text_init(void)
{
settings_add_str("lookandfeel", "hilight_color", "%Y");
settings_add_str("lookandfeel", "hilight_act_color", "%M");
settings_add_level("lookandfeel", "hilight_level", "PUBLIC DCCMSGS");
read_settings();
nickmatch = nickmatch_init(hilight_nick_cache);
read_hilight_config();
signal_add_first("print text", (SIGNAL_FUNC) sig_print_text);
signal_add("setup reread", (SIGNAL_FUNC) read_hilight_config);
signal_add("setup changed", (SIGNAL_FUNC) read_settings);
command_bind("hilight", NULL, (SIGNAL_FUNC) cmd_hilight);
command_bind("dehilight", NULL, (SIGNAL_FUNC) cmd_dehilight);
command_set_options("hilight", "-color -actcolor -level -priority -network -channels nick word line mask full regexp matchcase");
}
void hilight_text_deinit(void)
{
hilights_destroy_all();
nickmatch_deinit(nickmatch);
signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
signal_remove("setup reread", (SIGNAL_FUNC) read_hilight_config);
signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
command_unbind("hilight", (SIGNAL_FUNC) cmd_hilight);
command_unbind("dehilight", (SIGNAL_FUNC) cmd_dehilight);
}