]> git.madduck.net Git - code/irssi/scripts-bc-bd.git/blob - nm.pl

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Merge branch 'windowlist-filter' into int
[code/irssi/scripts-bc-bd.git] / nm.pl
1 use Irssi;
2 use strict;
3
4 use vars qw($VERSION %IRSSI);
5
6 $VERSION="0.3.6";
7 %IRSSI = (
8         authors=> 'BC-bd',
9         contact=> 'bd@bc-bd.org',
10         name=> 'nm',
11         description=> 'right aligned nicks depending on longest nick',
12         license=> 'GPL v2',
13         url=> 'https://bc-bd.org/svn/repos/irssi/nm',
14 );
15
16 # $Id$
17 # nm.pl
18 # for irssi 0.8.4 by bd@bc-bd.org
19 #
20 # right aligned nicks depending on longest nick
21 #
22 # inspired by neatmsg.pl from kodgehopper <kodgehopper@netscape.net
23 # formats taken from www.irssi.de
24 # thanks to adrianel <adrinael@nuclearzone.org> for some hints
25 # thanks to Eric Wald <eswald@gmail.com> for the left alignment patch
26 # inspired by nickcolor.pl by Timo Sirainen and Ian Peters
27 # thanks to And1 <and1@meinungsverstaerker.de> for a small patch
28 # thanks to berber@tzi.de for the save/load patch
29 #
30 #########
31 # USAGE
32 ###
33
34 # use
35
36 #       /neatcolor help
37 #
38 # for help on available commands
39 #
40 #########
41 # OPTIONS
42 #########
43
44 my $help = "
45 /set neat_colorize <ON|OFF>
46     * ON  : colorize nicks
47     * OFF : do not colorize nicks
48
49 /set neat_colors <string>
50     Use these colors when colorizing nicks, eg:
51
52         /set neat_colors yYrR
53
54     See the file formats.txt on an explanation of what colors are
55     available.
56
57 /set neat_left_actions <ON|OFF>
58     * ON  : print nicks left-aligned on actions
59     * OFF : print nicks right-aligned on actions
60
61 /set neat_left_messages <ON|OFF>
62     * ON  : print nicks left-aligned on messages
63     * OFF : print nicks right-aligned on messages
64
65 /set neat_right_mode <ON|OFF>
66     * ON  : print the mode of the nick e.g @%+ after the nick
67     * OFF : print it left of the nick 
68
69 /set neat_maxlength <number>
70     * number : Maximum length of Nicks to display. Longer nicks are truncated.
71     * 0      : Do not truncate nicks.
72
73 /set neat_melength <number>
74     * number : number of spaces to substract from /me padding
75
76 /set neat_ignorechars <str>
77     * str : regular expression used to filter out unwanted characters in
78             nicks. this can be used to assign the same color for similar
79             nicks, e.g. foo and foo_:
80
81                 /set neat_ignorechars [_]
82
83 /set neat_allow_shrinking <ON|OFF>
84     * ON  : shrink padding when longest nick disappears
85     * OFF : do not shrink, only allow growing
86  
87 ";
88
89 #
90 ###
91 ################
92 ###
93 #
94 # Changelog
95 #
96 # Version 0.3.6
97 #  - added option to ignore certain characters from color hash building, see
98 #    https://bc-bd.org/trac/irssi/ticket/22
99 #  - added option to save and specify colors for nicks, see
100 #    https://bc-bd.org/trac/irssi/ticket/23
101 #  - added option to disallow shrinking, see
102 #    https://bc-bd.org/trac/irssi/ticket/12
103 #
104 # Version 0.3.5
105 #  - now also aligning own messages in queries
106 #
107 # Version 0.3.4
108 #  - fxed off by one error in nick_to_color, patch by jrib, see
109 #  https://bc-bd.org/trac/irssi/ticket/24
110 #
111 # Version 0.3.3
112 #  - added support for alignment in queries, see
113 #    https://bc-bd.org/trac/irssi/ticket/21
114 #
115 # Version 0.3.2
116 #  - integrated left alignment patch from Eric Wald <eswald@gmail.com>, see
117 #    https://bc-bd.org/trac/irssi/ticket/18
118 #
119 # Version 0.3.1
120 #  - /me padding, see https://bc-bd.org/trac/irssi/ticket/17
121 #
122 # Version 0.3.0
123 #  - integrate nick coloring support
124 #
125 # Version 0.2.1
126 #  - moved neat_maxlength check to reformat() (thx to Jerome De Greef <jdegreef@brutele.be>)
127 #
128 # Version 0.2.0
129 #  - by adrianel <adrinael@nuclearzone.org>
130 #     * reformat after setup reload
131 #     * maximum length of nicks
132 #
133 # Version 0.1.0
134 #  - got lost somewhere
135 #
136 # Version 0.0.2
137 #  - ugly typo fixed
138 #  
139 # Version 0.0.1
140 #  - initial release
141 #
142 ###
143 ################
144 ###
145 #
146 # BUGS
147 #
148 # Empty nicks, eg "<> message"
149 #       This seems to be triggered by some themes. As of now there is no known
150 #       fix other than changing themes, see
151 #       https://bc-bd.org/trac/irssi/ticket/19
152 #
153 # Well, it's a feature: due to the lacking support of extendable themes
154 # from irssi it is not possible to just change some formats per window.
155 # This means that right now all windows are aligned with the same nick
156 # length, which can be somewhat annoying.
157 # If irssi supports extendable themes, I will include per-server indenting
158 # and a setting where you can specify servers you don't want to be indented
159 #
160 ###
161 ################
162
163 my ($longestNick, %saved_colors, @colors, $alignment, $sign, %commands);
164
165 my $colorize = -1;
166
167 sub reformat() {
168         my $max = Irssi::settings_get_int('neat_maxlength');
169         my $actsign = Irssi::settings_get_bool('neat_left_actions')? '': '-';
170         $sign = Irssi::settings_get_bool('neat_left_messages')? '': '-';
171
172         if ($max && $max < $longestNick) {
173                 $longestNick = $max;
174         }
175
176         my $me = $longestNick - Irssi::settings_get_int('neat_melength');
177         $me = 0 if ($me < 0);
178
179         Irssi::command('^format own_action {ownaction $['.$actsign.$me.']0} $1');
180         Irssi::command('^format action_public {pubaction $['.$actsign.$me.']0}$1');
181         Irssi::command('^format action_private {pvtaction $['.$actsign.$me.']0}$1');
182         Irssi::command('^format action_private_query {pvtaction_query $['.$actsign.$me.']0} $2');
183
184         my $length = $sign . $longestNick;
185         if (Irssi::settings_get_bool('neat_right_mode') == 0) {
186                 Irssi::command('^format own_msg {ownmsgnick $2 {ownnick $['.$length.']0}}$1');
187                 Irssi::command('^format own_msg_channel {ownmsgnick $3 {ownnick $['.$length.']0}{msgchannel $1}}$2');
188                 Irssi::command('^format pubmsg_me {pubmsgmenick $2 {menick $['.$length.']0}}$1');
189                 Irssi::command('^format pubmsg_me_channel {pubmsgmenick $3 {menick $['.$length.']0}{msgchannel $1}}$2');
190                 Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $3 $['.$length.']1%n}$2');
191                 Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $4 $['.$length.']1{msgchannel $2}}$3');
192                 Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick $['.$length.']0}}$1');
193                 Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick $['.$length.']0}}$1');
194         } else {
195                 Irssi::command('^format own_msg {ownmsgnick {ownnick $['.$length.']0$2}}$1');
196                 Irssi::command('^format own_msg_channel {ownmsgnick {ownnick $['.$length.']0$3}{msgchannel $1}}$2');
197                 Irssi::command('^format pubmsg_me {pubmsgmenick {menick $['.$length.']0}$2}$1');
198                 Irssi::command('^format pubmsg_me_channel {pubmsgmenick {menick $['.$length.']0$3}{msgchannel $1}}$2');
199                 Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $0 $['.$length.']1$3%n}$2');
200                 Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $['.$length.']1$4{msgchannel $2}}$3');
201                 Irssi::command('^format pubmsg {pubmsgnick {pubnick $['.$length.']0$2}}$1');
202                 Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick $['.$length.']0$2}}$1');
203         }
204
205         # format queries
206         Irssi::command('^format own_msg_private_query {ownprivmsgnick {ownprivnick $['.$length.']2}}$1');
207         Irssi::command('^format msg_private_query {privmsgnick $['.$length.']0}$2');
208 };
209
210 sub findLongestNick {
211         $longestNick = 0;
212
213         # get own nick length
214         map {
215                 my $len = length($_->{nick});
216
217                 $longestNick = $len if ($len > $longestNick);
218         } Irssi::servers();
219
220         # find longest other nick
221         foreach (Irssi::channels()) {
222                 foreach ($_->nicks()) {
223                         my $len = length($_->{nick});
224
225                         $longestNick = $len if ($len > $longestNick);
226                 }
227         }
228
229         reformat();
230 }
231
232 # a new nick was created
233 sub sig_newNick
234 {
235         my ($channel, $nick) = @_;
236
237         my $len = length($nick->{nick});
238
239         if ($len > $longestNick) {
240                 $longestNick = $len;
241                 reformat();
242         }
243
244         return if (exists($saved_colors{$nick->{nick}}));
245
246         $saved_colors{$nick->{nick}} = "%".nick_to_color($nick->{nick});
247 }
248
249 # something changed
250 sub sig_changeNick
251 {
252         my ($channel, $nick, $old_nick) = @_;
253
254         # we only need to recalculate if this was the longest nick
255         if (length($old_nick) == $longestNick) {
256                 my $len = length($nick->{nick});
257
258                 # if the new nick is shorter we need to find the longest nick
259                 # again, if it is longer, it is the new longest nick
260                 if ($len < $longestNick) {
261                         # only look for a new longest nick if we are allowed to
262                         # shrink
263                         findLongestNick() if Irssi::settings_get_bool('neat_allow_shrinking');
264                 } else {
265                         $longestNick = $len;
266                 }
267
268                 reformat();
269         }
270
271         $saved_colors{$nick->{nick}} = $saved_colors{$nick->{old_nick}};
272         delete $saved_colors{$nick->{old_nick}}
273 }
274
275 sub sig_removeNick
276 {
277         my ($channel, $nick) = @_;
278
279         my $thisLen = length($nick->{nick});
280
281         # we only need to recalculate if this was the longest nick and we are
282         # allowed to shrink
283         if ($thisLen == $longestNick && Irssi::settings_get_bool('neat_allow_shrinking')) {
284                 findLongestNick();
285                 reformat();
286         }
287
288         # we do not remove a known color for a gone nick, as they may return
289 }
290
291 # based on simple_hash from nickcolor.pl
292 sub nick_to_color($) {
293         my ($string) = @_;
294         chomp $string;
295
296         my $ignore = Irssi::settings_get_str("neat_ignorechars");
297         $string =~ s/$ignore//g;
298
299         my $counter;
300         foreach my $char (split(//, $string)) {
301                 $counter += ord $char;
302         }
303
304         return $colors[$counter % ($#colors + 1)];
305 }
306
307 sub color_left($) {
308         Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1');
309         Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1');
310 }
311
312 sub color_right($) {
313         Irssi::command('^format pubmsg {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1');
314         Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1');
315 }
316
317 sub sig_public {
318         my ($server, $msg, $nick, $address, $target) = @_;
319
320         &$alignment($saved_colors{$nick});
321 }
322
323 sub sig_setup {
324         @colors = split(//, Irssi::settings_get_str('neat_colors'));
325
326         # check left or right alignment
327         if (Irssi::settings_get_bool('neat_right_mode') == 0) {
328                 $alignment = \&color_left;
329         } else {
330                 $alignment = \&color_right;
331         }
332         
333         # check if we switched coloring on or off
334         my $new = Irssi::settings_get_bool('neat_colorize');
335         if ($new != $colorize) {
336                 if ($new) {
337                         Irssi::signal_add('message public', 'sig_public');
338                 } else {
339                         if ($colorize >= 0) {
340                                 Irssi::signal_remove('message public', 'sig_public');
341                         }
342                 }
343         }
344         $colorize = $new;
345
346         reformat();
347         &$alignment('%w');
348 }
349
350 # make sure that every nick has an assigned color
351 sub assert_colors() {
352         foreach (Irssi::channels()) {
353                 foreach ($_->nicks()) {
354                         next if (exists($saved_colors{$_->{nick}}));
355
356                         $saved_colors{$_->{nick}} = "%".nick_to_color($_->{nick});
357                 }
358         }
359 }
360
361 # load colors from file
362 sub load_colors() {
363         open(FID, "<".$ENV{HOME}."/.irssi/saved_colors") || return;
364
365         while (<FID>) {
366                 chomp;
367                 my ($k, $v) = split(/:/);
368                 $saved_colors{$k} = $v;
369         }
370
371         close(FID);
372 }
373
374 # save colors to file
375 sub save_colors() {
376         open(FID, ">".$ENV{HOME}."/.irssi/saved_colors");
377
378         print FID $_.":".$saved_colors{$_}."\n" foreach (keys(%saved_colors));
379
380         close(FID);
381 }
382
383 # log a line to a window item
384 sub neat_log($@) {
385         my ($witem, @text) = @_;
386
387         $witem->print("nm.pl: ".$_) foreach(@text);
388 }
389
390 # show available colors
391 sub cmd_neatcolor_colors($) {
392         my ($witem, undef, undef) = @_;
393
394         neat_log($witem, "Available colors: ".join("", map { "%".$_.$_ } @colors));
395 }
396
397 # display the configured color for a nick
398 sub cmd_neatcolor_get() {
399         my ($witem, $nick, undef) = @_;
400
401         if (!exists($saved_colors{$nick})) {
402                 neat_log($witem, "Error: no such nick '$nick'");
403                 return;
404         }
405
406         neat_log($witem, "Color for ".$saved_colors{$nick}.$nick);
407 }
408
409 # display help
410 sub cmd_neatcolor_help() {
411         my ($witem, $cmd, undef) = @_;
412
413         if ($cmd) {
414                 if (!exists($commands{$cmd})) {
415                         neat_log($witem, "Error: no such command '$cmd'");
416                         return;
417                 }
418
419                 if (!exists($commands{$cmd}{verbose})) {
420                         neat_log($witem, "No additional help for '$cmd' available");
421                         return;
422                 }
423
424                 neat_log($witem, ( "", "Help for ".uc($cmd), "" ) );
425                 neat_log($witem, @{$commands{$cmd}{verbose}});
426                 return;
427         }
428
429         neat_log($witem, split(/\n/, $help));
430         neat_log($witem, "Available options for /neatcolor");
431         neat_log($witem, "    ".$_.": ".$commands{$_}{text}) foreach(sort(keys(%commands)));
432
433         my @verbose;
434         foreach (sort(keys(%commands))) {
435                 push(@verbose, $_) if exists($commands{$_}{verbose});
436         }
437
438         neat_log($witem, "Verbose help available for: '".join(", ", @verbose)."'");
439 }
440
441 # list configured nicks
442 sub cmd_neatcolor_list() {
443         my ($witem, undef, undef) = @_;
444
445         neat_log($witem, "Configured nicks: ".join(", ", map { $saved_colors{$_}.$_ } sort(keys(%saved_colors))));
446 }
447
448 # reset a nick to its default color
449 sub cmd_neatcolor_reset() {
450         my ($witem, $nick, undef) = @_;
451
452         if (!exists($saved_colors{$nick})) {
453                 neat_log($witem, "Error: no such nick '$nick'");
454                 return;
455         }
456
457         $saved_colors{$nick} = "%".nick_to_color($nick);
458         neat_log($witem, "Reset color for ".$saved_colors{$nick}.$nick);
459 }
460
461 # save configured colors to disk
462 sub cmd_neatcolor_save() {
463         my ($witem, undef, undef) = @_;
464
465         save_colors();
466
467         neat_log($witem, "color information saved");
468 }
469
470 # set a color for a nick
471 sub cmd_neatcolor_set() {
472         my ($witem, $nick, $color) = @_;
473
474         my @found = grep(/$color/, @colors);
475         if ($#found) {
476                 neat_log($witem, "Error: trying to set unknown color '%$color$color%n'");
477                 cmd_neatcolor_colors($witem);
478                 return;
479         }
480
481         if ($witem->{type} ne "CHANNEL" && $witem->{type} ne "QUERY") {
482                 neat_log($witem, "Warning: not a Channel/Query, can not check nick");
483         } else {
484                 my @nicks = grep(/^$nick$/i, map { $_->{nick} } ($witem->nicks()));
485
486                 if ($#nicks < 0) {
487                         neat_log($witem, "Warning: could not find nick '$nick' here");
488                 } else {
489                         if ($nicks[0] ne $nick) {
490                                 neat_log($witem, "Warning: using '$nicks[0]' instead of '$nick'");
491                                 $nick = $nicks[0];
492                         }
493                 }
494         }
495
496         $saved_colors{$nick} = "%".$color;
497         neat_log($witem, "Set color for $saved_colors{$nick}$nick");
498 }
499
500 %commands = (
501         colors => {
502                 text => "show available colors",
503                 verbose => [
504                         "COLORS",
505                         "",
506                         "displays all available colors",
507                         "",
508                         "You can restrict/define the list of available colors ".
509                         "with the help of the neat_colors setting"
510                 ],
511                 func => \&cmd_neatcolor_colors,
512         },
513         get => {
514                 text => "retrieve color for a nick",
515                 verbose => [
516                         "GET <nick>",
517                         "",
518                         "displays color used for <nick>"
519                 ],
520                 func => \&cmd_neatcolor_get,
521         },
522         help => {
523                 text => "print this help message",
524                 func => \&cmd_neatcolor_help,
525         },
526         list => {
527                 text => "list configured nick/color pairs",
528                 func => \&cmd_neatcolor_list,
529         },
530         reset => {
531                 text => "reset color to default",
532                 verbose => [
533                         "RESET <nick>",
534                         "",
535                         "resets the color used for <nick> to its internal default"
536                 ],
537                 func => \&cmd_neatcolor_reset,
538         },
539         save => {
540                 text => "save color information to disk",
541                 verbose => [
542                         "SAVE",
543                         "",
544                         "saves color information to disk, so that it survives ".
545                         "an irssi restart.",
546                         "",
547                         "Color information will be automatically saved on /quit",
548                 ],
549                 func => \&cmd_neatcolor_save,
550         },
551         set => {
552                 text => "set a specific color for a nick",
553                 verbose => [
554                         "SET <nick> <color>",
555                         "",
556                         "use <color> for <nick>",
557                         "",
558                         "This command will perform a couple of sanity checks, ".
559                         "when called from a CHANNEL/QUERY window",
560                         "",
561                         "EXAMPLE:",
562                         "  /neatcolor set bc-bd r",
563                         "",
564                         "use /neatcolor COLORS to see available colors"
565                 ],
566                 func => \&cmd_neatcolor_set,
567         },
568 );
569
570 # the main command callback that gets called for all neatcolor commands
571 sub cmd_neatcolor() {
572         my ($data, $server, $witem) = @_;
573         my ($cmd, $nick, $color) = split (/ /, $data);
574
575         $cmd = lc($cmd);
576
577         # make sure we have a valid witem to print text to
578         $witem = Irssi::active_win() unless ($witem);
579
580         if (!exists($commands{$cmd})) {
581                 neat_log($witem, "Error: unknown command '$cmd'");
582                 &{$commands{"help"}{"func"}} if (exists($commands{"help"}));
583                 return;
584         }
585
586         &{$commands{$cmd}{"func"}}($witem, $nick, $color);
587 }
588
589 Irssi::settings_add_bool('misc', 'neat_left_messages', 0);
590 Irssi::settings_add_bool('misc', 'neat_left_actions', 0);
591 Irssi::settings_add_bool('misc', 'neat_right_mode', 1);
592 Irssi::settings_add_int('misc', 'neat_maxlength', 0);
593 Irssi::settings_add_int('misc', 'neat_melength', 2);
594 Irssi::settings_add_bool('misc', 'neat_colorize', 1);
595 Irssi::settings_add_str('misc', 'neat_colors', 'rRgGyYbBmMcC');
596 Irssi::settings_add_str('misc', 'neat_ignorechars', '');
597 Irssi::settings_add_bool('misc', 'neat_allow_shrinking', 1);
598
599 Irssi::command_bind('neatcolor', 'cmd_neatcolor');
600
601 Irssi::signal_add('nicklist new', 'sig_newNick');
602 Irssi::signal_add('nicklist changed', 'sig_changeNick');
603 Irssi::signal_add('nicklist remove', 'sig_removeNick');
604
605 Irssi::signal_add('setup changed', 'sig_setup');
606 Irssi::signal_add_last('setup reread', 'sig_setup');
607
608 findLongestNick();
609 sig_setup;
610
611 load_colors();
612 assert_colors();
613
614 # we need to add this signal _after_ the colors have been loaded, to make sure
615 # no race condition exists wrt color saving
616 Irssi::signal_add('gui exit', 'save_colors');