addition of silc.css
[runtime.git] / apps / irssi / src / fe-common / core / hilight-text.c
1 /*
2  hilight-text.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "module-formats.h"
23 #include "signals.h"
24 #include "commands.h"
25 #include "levels.h"
26 #include "misc.h"
27 #include "lib-config/iconfig.h"
28 #include "settings.h"
29
30 #include "servers.h"
31 #include "channels.h"
32 #include "nicklist.h"
33
34 #include "hilight-text.h"
35 #include "nickmatch-cache.h"
36 #include "printtext.h"
37 #include "formats.h"
38
39 static NICKMATCH_REC *nickmatch;
40 static HILIGHT_REC *next_nick_hilight, *next_line_hilight;
41 static int next_hilight_start, next_hilight_end;
42 static int never_hilight_level, default_hilight_level;
43 GSList *hilights;
44
45 static void reset_cache(void)
46 {
47         GSList *tmp;
48
49         never_hilight_level = MSGLEVEL_ALL & ~default_hilight_level;
50         for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
51                 HILIGHT_REC *rec = tmp->data;
52
53                 if (never_hilight_level & rec->level)
54                         never_hilight_level &= ~rec->level;
55         }
56
57         nickmatch_rebuild(nickmatch);
58 }
59
60 static void hilight_add_config(HILIGHT_REC *rec)
61 {
62         CONFIG_NODE *node;
63
64         g_return_if_fail(rec != NULL);
65
66         node = iconfig_node_traverse("(hilights", TRUE);
67         node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
68
69         iconfig_node_set_str(node, "text", rec->text);
70         if (rec->level > 0) iconfig_node_set_int(node, "level", rec->level);
71         if (rec->color) iconfig_node_set_str(node, "color", rec->color);
72         if (rec->act_color) iconfig_node_set_str(node, "act_color", rec->act_color);
73         if (rec->priority > 0) iconfig_node_set_int(node, "priority", rec->priority);
74         iconfig_node_set_bool(node, "nick", rec->nick);
75         iconfig_node_set_bool(node, "word", rec->word);
76         if (rec->nickmask) iconfig_node_set_bool(node, "mask", TRUE);
77         if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
78         if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
79
80         if (rec->channels != NULL && *rec->channels != NULL) {
81                 node = config_node_section(node, "channels", NODE_TYPE_LIST);
82                 iconfig_node_add_list(node, rec->channels);
83         }
84 }
85
86 static void hilight_remove_config(HILIGHT_REC *rec)
87 {
88         CONFIG_NODE *node;
89
90         g_return_if_fail(rec != NULL);
91
92         node = iconfig_node_traverse("hilights", FALSE);
93         if (node != NULL) iconfig_node_list_remove(node, g_slist_index(hilights, rec));
94 }
95
96 static void hilight_destroy(HILIGHT_REC *rec)
97 {
98         g_return_if_fail(rec != NULL);
99
100 #ifdef HAVE_REGEX_H
101         if (rec->regexp_compiled) regfree(&rec->preg);
102 #endif
103         if (rec->channels != NULL) g_strfreev(rec->channels);
104         g_free_not_null(rec->color);
105         g_free_not_null(rec->act_color);
106         g_free(rec->text);
107         g_free(rec);
108 }
109
110 static void hilights_destroy_all(void)
111 {
112         g_slist_foreach(hilights, (GFunc) hilight_destroy, NULL);
113         g_slist_free(hilights);
114         hilights = NULL;
115 }
116
117 static void hilight_remove(HILIGHT_REC *rec)
118 {
119         g_return_if_fail(rec != NULL);
120
121         hilight_remove_config(rec);
122         hilights = g_slist_remove(hilights, rec);
123         hilight_destroy(rec);
124 }
125
126 static HILIGHT_REC *hilight_find(const char *text, char **channels)
127 {
128         GSList *tmp;
129         char **chan;
130
131         g_return_val_if_fail(text != NULL, NULL);
132
133         for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
134                 HILIGHT_REC *rec = tmp->data;
135
136                 if (g_strcasecmp(rec->text, text) != 0)
137                         continue;
138
139                 if ((channels == NULL && rec->channels == NULL))
140                         return rec; /* no channels - ok */
141
142                 if (channels != NULL && strcmp(*channels, "*") == 0)
143                         return rec; /* ignore channels */
144
145                 if (channels == NULL || rec->channels == NULL)
146                         continue; /* other doesn't have channels */
147
148                 if (strarray_length(channels) != strarray_length(rec->channels))
149                         continue; /* different amount of channels */
150
151                 /* check that channels match */
152                 for (chan = channels; *chan != NULL; chan++) {
153                         if (strarray_find(rec->channels, *chan) == -1)
154                                 break;
155                 }
156
157                 if (*chan == NULL)
158                         return rec; /* channels ok */
159         }
160
161         return NULL;
162 }
163
164 static int hilight_match_text(HILIGHT_REC *rec, const char *text,
165                               int *match_beg, int *match_end)
166 {
167         char *match;
168
169         if (rec->regexp) {
170 #ifdef HAVE_REGEX_H
171                 regmatch_t rmatch[1];
172
173                 if (rec->regexp_compiled &&
174                     regexec(&rec->preg, text, 1, rmatch, 0) == 0) {
175                         if (rmatch[0].rm_so > 0 &&
176                             match_beg != NULL && match_end != NULL) {
177                                 *match_beg = rmatch[0].rm_so;
178                                 *match_end = rmatch[0].rm_eo;
179                         }
180                         return TRUE;
181                 }
182 #endif
183         } else {
184                 match = rec->fullword ?
185                         stristr_full(text, rec->text) :
186                         stristr(text, rec->text);
187                 if (match != NULL) {
188                         if (match_beg != NULL && match_end != NULL) {
189                                 *match_beg = (int) (match-text);
190                                 *match_end = *match_beg + strlen(rec->text);
191                         }
192                         return TRUE;
193                 }
194         }
195
196         return FALSE;
197 }
198
199 #define hilight_match_level(rec, level) \
200         (level & (((rec)->level != 0 ? rec->level : default_hilight_level)))
201
202 #define hilight_match_channel(rec, channel) \
203         ((rec)->channels == NULL || ((channel) != NULL && \
204                 strarray_find((rec)->channels, (channel)) != -1))
205
206 HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
207                            const char *nick, const char *address,
208                            int level, const char *str,
209                            int *match_beg, int *match_end)
210 {
211         GSList *tmp;
212         CHANNEL_REC *chanrec;
213         NICK_REC *nickrec;
214
215         g_return_val_if_fail(str != NULL, NULL);
216
217         if ((never_hilight_level & level) == level)
218                 return NULL;
219
220         if (nick != NULL) {
221                 /* check nick mask hilights */
222                 chanrec = channel_find(server, channel);
223                 nickrec = chanrec == NULL ? NULL :
224                         nicklist_find(chanrec, nick);
225                 if (nickrec != NULL) {
226                         HILIGHT_REC *rec;
227
228                         if (nickrec->host == NULL)
229                                 nicklist_set_host(chanrec, nickrec, address);
230
231                         rec = nickmatch_find(nickmatch, nickrec);
232                         if (rec != NULL && hilight_match_level(rec, level))
233                                 return rec;
234                 }
235         }
236
237         for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
238                 HILIGHT_REC *rec = tmp->data;
239
240                 if (!rec->nickmask && hilight_match_level(rec, level) &&
241                     hilight_match_channel(rec, channel) &&
242                     hilight_match_text(rec, str, match_beg, match_end))
243                         return rec;
244         }
245
246         return NULL;
247 }
248
249 static char *hilight_get_act_color(HILIGHT_REC *rec)
250 {
251         g_return_val_if_fail(rec != NULL, NULL);
252
253         return g_strdup(rec->act_color != NULL ? rec->act_color :
254                         rec->color != NULL ? rec->color :
255                         settings_get_str("hilight_act_color"));
256 }
257
258 static char *hilight_get_color(HILIGHT_REC *rec)
259 {
260         const char *color;
261
262         g_return_val_if_fail(rec != NULL, NULL);
263
264         color = rec->color != NULL ? rec->color :
265                 settings_get_str("hilight_color");
266
267         return format_string_expand(color);
268 }
269
270 static void hilight_update_text_dest(TEXT_DEST_REC *dest, HILIGHT_REC *rec)
271 {
272         dest->level |= MSGLEVEL_HILIGHT;
273
274         if (rec->priority > 0)
275                 dest->hilight_priority = rec->priority;
276
277         dest->hilight_color = hilight_get_act_color(rec);
278 }
279
280 static void sig_print_text_stripped(TEXT_DEST_REC *dest, const char *str)
281 {
282         HILIGHT_REC *hilight;
283
284         g_return_if_fail(str != NULL);
285
286         if (next_nick_hilight != NULL) {
287                 if (!next_nick_hilight->nick) {
288                         /* non-nick hilight wanted */
289                         hilight = next_nick_hilight;
290                         next_nick_hilight = NULL;
291                         if (!hilight_match_text(hilight, str,
292                                                 &next_hilight_start,
293                                                 &next_hilight_end)) {
294                                 next_hilight_start = 0;
295                                 next_hilight_end = strlen(str);
296                         }
297                 } else {
298                         /* nick is highlighted, just set priority */
299                         hilight_update_text_dest(dest, next_nick_hilight);
300                         next_nick_hilight = NULL;
301                         return;
302                 }
303         } else {
304                 if (dest->level & (MSGLEVEL_NOHILIGHT|MSGLEVEL_HILIGHT))
305                         return;
306
307                 hilight = hilight_match(dest->server, dest->target,
308                                         NULL, NULL, dest->level, str,
309                                         &next_hilight_start,
310                                         &next_hilight_end);
311         }
312
313         if (hilight != NULL) {
314                 /* update the level / hilight info */
315                 hilight_update_text_dest(dest, hilight);
316
317                 next_line_hilight = hilight;
318         }
319 }
320
321 static void sig_print_text(TEXT_DEST_REC *dest, const char *str)
322 {
323         char *color, *newstr;
324         int next_hilight_len;
325
326         if (next_line_hilight == NULL)
327                 return;
328
329         color = hilight_get_color(next_line_hilight);
330         next_hilight_len = next_hilight_end-next_hilight_start;
331
332         if (!next_line_hilight->word) {
333                 /* hilight whole line */
334                 char *tmp = strip_codes(str);
335                 newstr = g_strconcat(color, tmp, NULL);
336                 g_free(tmp);
337         } else {
338                 /* hilight part of the line */
339                 GString *tmp;
340                 char *middle, *lastcolor;
341                 int pos, color_pos, color_len;
342
343                 tmp = g_string_new(NULL);
344
345                 /* start of the line */
346                 pos = strip_real_length(str, next_hilight_start, NULL, NULL);
347                 g_string_append(tmp, str);
348                 g_string_truncate(tmp, pos);
349
350                 /* color */
351                 g_string_append(tmp, color);
352
353                 /* middle of the line, stripped */
354                 middle = strip_codes(str+pos);
355                 pos = tmp->len;
356                 g_string_append(tmp, middle);
357                 g_string_truncate(tmp, pos+next_hilight_len);
358                 g_free(middle);
359
360                 /* end of the line */
361                 pos = strip_real_length(str, next_hilight_end,
362                                         &color_pos, &color_len);
363                 if (color_pos > 0)
364                         lastcolor = g_strndup(str+color_pos, color_len);
365                 else {
366                         /* no colors in line, change back to default */
367                         lastcolor = g_malloc0(3);
368                         lastcolor[0] = 4;
369                         lastcolor[1] = FORMAT_STYLE_DEFAULTS;
370                 }
371                 g_string_append(tmp, lastcolor);
372                 g_string_append(tmp, str+pos);
373                 g_free(lastcolor);
374
375                 newstr = tmp->str;
376                 g_string_free(tmp, FALSE);
377         }
378
379         next_line_hilight = NULL;
380         signal_emit("print text", 2, dest, newstr);
381
382         g_free(color);
383         g_free(newstr);
384
385         signal_stop();
386 }
387
388 char *hilight_match_nick(SERVER_REC *server, const char *channel,
389                          const char *nick, const char *address,
390                          int level, const char *msg)
391 {
392         HILIGHT_REC *rec;
393         char *color;
394
395         rec = hilight_match(server, channel, nick, address,
396                             level, msg, NULL, NULL);
397         color = rec == NULL || !rec->nick ? NULL :
398                 hilight_get_color(rec);
399
400         next_nick_hilight = rec;
401         return color;
402 }
403
404 static void read_hilight_config(void)
405 {
406         CONFIG_NODE *node;
407         HILIGHT_REC *rec;
408         GSList *tmp;
409         char *text, *color;
410
411         hilights_destroy_all();
412
413         node = iconfig_node_traverse("hilights", FALSE);
414         if (node == NULL) {
415                 reset_cache();
416                 return;
417         }
418
419         for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
420                 node = tmp->data;
421
422                 if (node->type != NODE_TYPE_BLOCK)
423                         continue;
424
425                 text = config_node_get_str(node, "text", NULL);
426                 if (text == NULL || *text == '\0')
427                         continue;
428
429                 rec = g_new0(HILIGHT_REC, 1);
430                 hilights = g_slist_append(hilights, rec);
431
432                 rec->text = g_strdup(text);
433
434                 color = config_node_get_str(node, "color", NULL);
435                 rec->color = color == NULL || *color == '\0' ? NULL :
436                         g_strdup(color);
437
438                 color = config_node_get_str(node, "act_color", NULL);
439                 rec->act_color = color == NULL || *color == '\0' ? NULL :
440                         g_strdup(color);
441
442                 rec->level = config_node_get_int(node, "level", 0);
443                 rec->priority = config_node_get_int(node, "priority", 0);
444                 rec->nick = config_node_get_bool(node, "nick", TRUE);
445                 rec->word = config_node_get_bool(node, "word", TRUE);
446
447                 rec->nickmask = config_node_get_bool(node, "mask", FALSE);
448                 rec->fullword = config_node_get_bool(node, "fullword", FALSE);
449                 rec->regexp = config_node_get_bool(node, "regexp", FALSE);
450
451 #ifdef HAVE_REGEX_H
452                 rec->regexp_compiled = !rec->regexp ? FALSE :
453                         regcomp(&rec->preg, rec->text,
454                                 REG_EXTENDED|REG_ICASE) == 0;
455 #endif
456
457                 node = config_node_section(node, "channels", -1);
458                 if (node != NULL) rec->channels = config_node_get_list(node);
459         }
460
461         reset_cache();
462 }
463
464 static void hilight_print(int index, HILIGHT_REC *rec)
465 {
466         char *chans, *levelstr;
467
468         chans = rec->channels == NULL ? NULL :
469                 g_strjoinv(",", rec->channels);
470         levelstr = rec->level == 0 ? NULL :
471                 bits2level(rec->level);
472         printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
473                     TXT_HILIGHT_LINE, index, rec->text,
474                     chans != NULL ? chans : "",
475                     levelstr != NULL ? levelstr : "",
476                     rec->nickmask ? " -mask" : "",
477                     rec->fullword ? " -full" : "",
478                     rec->regexp ? " -regexp" : "");
479         g_free_not_null(chans);
480         g_free_not_null(levelstr);
481 }
482
483 static void cmd_hilight_show(void)
484 {
485         GSList *tmp;
486         int index;
487
488         printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_HEADER);
489         index = 1;
490         for (tmp = hilights; tmp != NULL; tmp = tmp->next, index++) {
491                 HILIGHT_REC *rec = tmp->data;
492
493                 hilight_print(index, rec);
494         }
495         printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_FOOTER);
496 }
497
498 /* SYNTAX: HILIGHT [-nick | -word | -line] [-mask | -full | -regexp]
499                    [-color <color>] [-actcolor <color>] [-level <level>]
500                    [-channels <channels>] <text> */
501 static void cmd_hilight(const char *data)
502 {
503         GHashTable *optlist;
504         HILIGHT_REC *rec;
505         char *colorarg, *actcolorarg, *levelarg, *priorityarg, *chanarg, *text;
506         char **channels;
507         void *free_arg;
508
509         g_return_if_fail(data != NULL);
510
511         if (*data == '\0') {
512                 cmd_hilight_show();
513                 return;
514         }
515
516         if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
517                             PARAM_FLAG_GETREST, "hilight", &optlist, &text))
518                 return;
519
520         chanarg = g_hash_table_lookup(optlist, "channels");
521         levelarg = g_hash_table_lookup(optlist, "level");
522         priorityarg = g_hash_table_lookup(optlist, "priority");
523         colorarg = g_hash_table_lookup(optlist, "color");
524         actcolorarg = g_hash_table_lookup(optlist, "actcolor");
525
526         if (*text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
527
528         channels = (chanarg == NULL || *chanarg == '\0') ? NULL :
529                 g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1);
530
531         rec = hilight_find(text, channels);
532         if (rec == NULL) {
533                 rec = g_new0(HILIGHT_REC, 1);
534
535                 /* default to nick/word hilighting */
536                 rec->nick = TRUE;
537                 rec->word = TRUE;
538
539                 rec->text = g_strdup(text);
540                 rec->channels = channels;
541         } else {
542                 g_strfreev(channels);
543
544                 hilight_remove_config(rec);
545                 hilights = g_slist_remove(hilights, rec);
546         }
547
548         rec->level = (levelarg == NULL || *levelarg == '\0') ? 0 :
549                 level2bits(replace_chars(levelarg, ',', ' '));
550         rec->priority = priorityarg == NULL ? 0 : atoi(priorityarg);
551
552         if (g_hash_table_lookup(optlist, "line") != NULL) {
553                 rec->word = FALSE;
554                 rec->nick = FALSE;
555         }
556
557         if (g_hash_table_lookup(optlist, "word") != NULL) {
558                 rec->word = TRUE;
559                 rec->nick = FALSE;
560         }
561
562         if (g_hash_table_lookup(optlist, "nick") != NULL)
563                 rec->nick = TRUE;
564
565         rec->nickmask = g_hash_table_lookup(optlist, "mask") != NULL;
566         rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
567         rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
568
569         if (colorarg != NULL) {
570                 if (*colorarg != '\0')
571                         rec->color = g_strdup(colorarg);
572                 else
573                         g_free_and_null(rec->color);
574         }
575         if (actcolorarg != NULL) {
576                 if (*actcolorarg != '\0')
577                         rec->act_color = g_strdup(actcolorarg);
578                 else
579                         g_free_and_null(rec->act_color);
580         }
581
582 #ifdef HAVE_REGEX_H
583         if (rec->regexp_compiled)
584                 regfree(&rec->preg);
585         rec->regexp_compiled = !rec->regexp ? FALSE :
586                 regcomp(&rec->preg, rec->text, REG_EXTENDED|REG_ICASE) == 0;
587 #endif
588
589         hilights = g_slist_append(hilights, rec);
590         hilight_add_config(rec);
591
592         hilight_print(g_slist_index(hilights, rec)+1, rec);
593         cmd_params_free(free_arg);
594
595         reset_cache();
596 }
597
598 /* SYNTAX: DEHILIGHT <id>|<mask> */
599 static void cmd_dehilight(const char *data)
600 {
601         HILIGHT_REC *rec;
602         GSList *tmp;
603
604         if (is_numeric(data, ' ')) {
605                 /* with index number */
606                 tmp = g_slist_nth(hilights, atoi(data)-1);
607                 rec = tmp == NULL ? NULL : tmp->data;
608         } else {
609                 /* with mask */
610                 char *chans[2] = { "*", NULL };
611                 rec = hilight_find(data, chans);
612         }
613
614         if (rec == NULL)
615                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_NOT_FOUND, data);
616         else {
617                 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_REMOVED, rec->text);
618                 hilight_remove(rec);
619                 reset_cache();
620         }
621 }
622
623 static void hilight_nick_cache(GHashTable *list, CHANNEL_REC *channel,
624                                NICK_REC *nick)
625 {
626         GSList *tmp;
627         HILIGHT_REC *match;
628         char *nickmask;
629         int len, best_match;
630
631         if (nick->host == NULL)
632                 return; /* don't check until host is known */
633
634         nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
635
636         best_match = 0; match = NULL;
637         for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
638                 HILIGHT_REC *rec = tmp->data;
639
640                 if (rec->nickmask &&
641                     hilight_match_channel(rec, channel->name) &&
642                     match_wildcards(rec->text, nickmask)) {
643                         len = strlen(rec->text);
644                         if (best_match < len) {
645                                 best_match = len;
646                                 match = rec;
647                         }
648                 }
649         }
650         g_free_not_null(nickmask);
651
652         if (match != NULL)
653                 g_hash_table_insert(list, nick, match);
654 }
655
656 static void read_settings(void)
657 {
658         default_hilight_level = level2bits(settings_get_str("hilight_level"));
659 }
660
661 void hilight_text_init(void)
662 {
663         settings_add_str("lookandfeel", "hilight_color", "%Y");
664         settings_add_str("lookandfeel", "hilight_act_color", "%M");
665         settings_add_str("lookandfeel", "hilight_level", "PUBLIC DCCMSGS");
666
667         next_nick_hilight = NULL;
668         next_line_hilight = NULL;
669
670         read_settings();
671
672         nickmatch = nickmatch_init(hilight_nick_cache);
673         read_hilight_config();
674
675         signal_add_first("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped);
676         signal_add_first("print text", (SIGNAL_FUNC) sig_print_text);
677         signal_add("setup reread", (SIGNAL_FUNC) read_hilight_config);
678         signal_add("setup changed", (SIGNAL_FUNC) read_settings);
679
680         command_bind("hilight", NULL, (SIGNAL_FUNC) cmd_hilight);
681         command_bind("dehilight", NULL, (SIGNAL_FUNC) cmd_dehilight);
682         command_set_options("hilight", "-color -actcolor -level -priority -channels nick word line mask full regexp");
683 }
684
685 void hilight_text_deinit(void)
686 {
687         hilights_destroy_all();
688         nickmatch_deinit(nickmatch);
689
690         signal_remove("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped);
691         signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
692         signal_remove("setup reread", (SIGNAL_FUNC) read_hilight_config);
693         signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
694
695         command_unbind("hilight", (SIGNAL_FUNC) cmd_hilight);
696         command_unbind("dehilight", (SIGNAL_FUNC) cmd_dehilight);
697 }