]>
git.sthu.org Git - shutils.git/blob - dotfiles/irssi/scripts/adv_windowlist.pl
1 use strict
; # use warnings;
6 # open STDERR, '>', '/home/ailin/wlstatwarnings';
9 # FIXME COULD SOMEONE PLEASE TELL ME HOW TO SHUT UP
12 # Variable "*" will not stay shared at (eval *) line *.
13 # Variable "*" will not stay shared at (eval *) line *.
15 # Can't locate package Irssi::Nick for @Irssi::Irc::Nick::ISA at (eval *) line *.
22 # if you don't know how to operate folds, type zn
26 use Irssi
(); # which is the minimum required version of Irssi ?
29 use vars
qw($VERSION %IRSSI);
33 original_authors => q(BC-bd, Veli, Timo Sirainen, ).
34 q(Wouter Coekaerts, Jean-Yves Lefort), # (decadix)
35 original_contact => q(bd@bc-bd.org, veli@piipiip.net, tss@iki.fi, ).
36 q(wouter@coekaerts.be, jylefort@brutele.be),
38 contact => q(Nei @ anti@conference.jabber.teamidiot.de),
39 url => "http://anti.teamidiot.de/",
41 description => q(Adds a permanent advanced window list on the right or ).
43 description2 => q(Based on chanact.pl which was apparently based on ).
44 q(lightbar.c and nicklist.pl with various other ideas ).
45 q(from random scripts.),
46 license => q(GNU GPLv2 or later),
51 # {{{ *** D O C U M E N T A T I O N ***
56 # {{{ original comment
58 # # Adds new powerful and customizable [Act: ...] item (chanelnames,modes,alias).
59 # # Lets you give alias characters to windows so that you can select those with
62 # # for irssi 0.8.2 by bd@bc-bd.org
64 # # inspired by chanlist.pl by 'cumol@hammerhart.de'
70 # # veli@piipiip.net /window_alias code
71 # # qrczak@knm.org.pl chanact_abbreviate_names
72 # # qerub@home.se Extra chanact_show_mode and chanact_chop_status
76 # {{{ FURTHER THANKS TO
78 # # buu, fxn, Somni, Khisanth, integral, tybalt89 for much support in any aspect perl
79 # # and the channel in general ( #perl @ freenode ) and especially the ir_* functions
81 # # Valentin 'senneth' Batz ( vb@g-23.org ) for the pointer to grep.pl, continuous support
82 # # and help in digging up ir_strip_codes
84 # # OnetrixNET technology networks for the debian environment
86 # # Monkey-Pirate.com / Spaceman Spiff for the webspace
91 # {{{ M A I N P R O B L E M
94 # It is impossible to place the awl on a statusbar together with other items,
95 # because I do not know how to calculate the size that it is going to get
96 # granted, and therefore I cannot do the linebreaks properly.
97 # This is what is missing to make a nice script out of awl.
98 # If you have any ideas, please contact me ASAP :).
106 # Please help me find a solution to this:
107 # this be your statusbar, it is using up the maximum term size
108 # [[1=1]#abc [2=2]#defghi]
110 # now consider this example:i
111 # "ascii" characters are marked with ., utf-8 characters with *
112 # [[1=1]#... [2=2]#...***]
114 # you should think that this is how it would be displayed? WRONG!
115 # [[1=1]#... [2=2]#...*** ]
117 # this is what Irssi does.. I believe my length calculating code to be correct,
118 # however, I'd love to be proven wrong (or receive any other fix, too, of
127 # copy the script to ~/.irssi/scripts/
134 # Hint: to get rid of the old [Act:] display
135 # /statusbar window remove act
138 # /statusbar window add -after lag -priority 10 act
144 # {{{ /set awl_display_nokey <string>
145 # /set awl_display_key <string>
146 # /set awl_display_nokey_active <string>
147 # /set awl_display_key_active <string>
148 # * string : Format String for one window. The following $'s are expanded:
150 # $N : Number of the Window
152 # $H : Start highlighting
153 # $S : Stop highlighting
154 # /+++++++++++++++++++++++++++++++++,
155 # | **** I M P O R T A N T : **** |
157 # | don't forget to use $S if you |
158 # | used $H before! |
160 # '+++++++++++++++++++++++++++++++++/
161 # XXX NOTE ON *_active: there is a BUG somewhere in the length
162 # XXX calculation. currently it's best to NOT remove $H/$S from those
163 # XXX settings if you use it in the non-active settings.
165 # {{{ /set awl_separator <string>
166 # * string : Charater to use between the channel entries
167 # you'll need to escape " " space and "$" like this:
168 # "/set awl_separator \ "
169 # "/set awl_separator \$"
171 # "/set awl_separator %{"
172 # "/set awl_separator %}"
173 # "/set awl_separator %%"
174 # (reason being, that the separator is used inside a {format })
176 # {{{ /set awl_prefer_name <ON|OFF>
177 # * this setting decides whether awl will use the active_name (OFF) or the
178 # window name as the name/caption in awl_display_*.
179 # That way you can rename windows using /window name myownname.
181 # {{{ /set awl_hide_data <num>
182 # * num : hide the window if its data_level is below num
183 # set it to 0 to basically disable this feature,
184 # 1 if you don't want windows without activity to be shown
185 # 2 to show only those windows with channel text or hilight
186 # 3 to show only windows with hilight
188 # {{{ /set awl_maxlines <num>
189 # * num : number of lines to use for the window list (0 to disable, negative
192 # {{{ /set awl_columns <num>
193 # * num : number of columns to use in screen mode (0 for unlimited)
195 # {{{ /set awl_block <num>
196 # * num : width of a column in screen mode (negative values = block display)
197 # /+++++++++++++++++++++++++++++++++,
198 # | ****** W A R N I N G ! ****** |
200 # | If your block display looks |
201 # | DISTORTED, you need to add the |
202 # | following line to your .theme |
204 # | abstracts = { : |
206 # | sb_act_none = "%n$*"; |
208 # '+++++++++++++++++++++++++++++++++/
209 #.02:08:26. < shi> Irssi::current_theme()->get_format <.. can this be used?
211 # {{{ /set awl_sbar_maxlength <ON|OFF>
212 # * if you enable the maxlength setting, the block width will be used as a
213 # maximum length for the non-block statusbar mode too.
215 # {{{ /set awl_height_adjust <num>
216 # * num : how many lines to leave empty in screen mode
218 # {{{ /set awl_sort <-data_level|-last_line|refnum>
219 # * you can change the window sort order with this variable
220 # -data_level : sort windows with hilight first
221 # -last_line : sort windows in order of activity
222 # refnum : sort windows by window number
224 # {{{ /set awl_placement <top|bottom>
225 # /set awl_position <num>
226 # * these settings correspond to /statusbar because awl will create
228 # (see /help statusbar to learn more)
230 # {{{ /set awl_all_disable <ON|OFF>
231 # * if you set awl_all_disable to ON, awl will also remove the
232 # last statusbar it created if it is empty.
233 # As you might guess, this only makes sense with awl_hide_data > 0 ;)
235 # {{{ /set awl_automode <sbar|screen|emulate_lightbar>
236 # * this setting defines whether the window list is shown in statusbars or
237 # whether the screen hack is used (from nicklist.pl)
243 # {{{ /awl paste <ON|OFF|TOGGLE>
244 # * enables or disables the screen hack windowlist. This is useful when you
245 # want to mark & copy text that you want to paste somewhere (hence the
246 # name). (ON means AWL disabled!)
247 # This is nicely bound to a function key for example.
250 # * redraws the screen hack windowlist. There are many occasions where the
251 # screen hack windowlist can get destroyed so you can use this command to
259 # if you fiddle with my mess, provide me with your fixes so I can benefit as well
261 # Nei =^.^= ( anti@conference.jabber.teamidiot.de )
268 #use Class::Classless;
273 # {{{ global variables
275 my $replaces = '[=]'; # AARGH!!! (chars that are always surrounded by weird
276 # colour codes by Irssi)
278 my $actString = []; # statusbar texts
279 my $currentLines = 0;
280 my $resetNeeded; # layout/screen has changed, redo everything
281 my $needRemake; # "normal" changes
283 sub GLOB_QUEUE_TIMER () { 100 }
284 my $globTime = undef; # timer to limit remake() calls
288 my $DISABLE_SCREEN_TEMP;
289 my $currentColumns = 0;
291 my ($screenHeight, $screenWidth);
292 my $screenansi = bless {
293 NAME => 'Screen::ANSI',
296 dcs => sub { "\033P" },
297 st => sub { "\033\\"},
299 }, 'Class::Classless::X';
300 #my $terminfo = new Term::Info 'xterm'; # xterm here, make this modular
302 my $terminfo = bless { # xterm here, make this modular
303 NAME => 'Term::Info::xterm',
307 civis => sub { "\033[?25l" },
309 sc => sub { "\0337" },
310 # cup=\E[%i%p1%d;%p2%dH,
311 cup => sub { shift;shift; "\033[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' },
313 el => sub { "\033[K" },
315 rc => sub { "\0338" },
317 cnorm => sub { "\033[?25h" },
319 setab => sub { shift;shift; "\033[4" . $_[0] . 'm' },
321 setaf => sub { shift;shift; "\033[3" . $_[0] . 'm' },
323 bold => sub { "\033[1m" },
325 blink => sub { "\033[5m" },
327 rev => sub { "\033[7m" },
329 op => sub { "\033[39;49m" },
331 }, 'Class::Classless::X';
347 my %statusbars; # currently active statusbars
349 # maybe I should just tie the array ?
354 no strict 'refs'; # :P
355 *{set$l} = sub { awl($l, @_) };
357 Irssi::command('statusbar ' . (set$_) . ' reset');
358 Irssi::command('statusbar ' . (set$_) . ' enable');
359 if (lc Irssi::settings_get_str(set 'placement') eq 'top') {
360 Irssi::command('statusbar ' . (set$_) . ' placement top');
362 if ((my $x = int Irssi::settings_get_int(set 'position')) != 0) {
363 Irssi::command('statusbar ' . (set$_) . ' position ' . $x);
365 Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment left barstart');
366 Irssi::command('statusbar ' . (set$_) . ' add ' . (set$_));
367 Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment right barend');
368 Irssi::command('statusbar ' . (set$_) . ' disable');
369 Irssi::statusbar_item_register(set$_, '$0', set$_);
370 $statusbars{$_} = {};
374 sub remove_statusbar {
376 Irssi::command('statusbar ' . (set$_) . ' reset');
377 Irssi::statusbar_item_unregister(set$_); # XXX does this actually work ?
378 # DO NOT REMOVE the sub before you have unregistered it :))
383 delete $statusbars{$_};
388 my $temp = $currentLines;
389 $currentLines = @$actString;
390 #Irssi::print("current lines: $temp new lines: $currentLines");
391 my $currMaxLines = Irssi::settings_get_int(set 'maxlines');
392 if ($currMaxLines > 0 and @$actString > $currMaxLines) {
393 $currentLines = $currMaxLines;
395 elsif ($currMaxLines < 0) {
396 $currentLines = abs($currMaxLines);
398 return if ($temp == $currentLines);
399 if ($currentLines > $temp) {
400 for ($temp .. ($currentLines - 1)) {
402 Irssi::command('statusbar ' . (set$_) . ' enable');
406 for ($_ = ($temp - 1); $_ >= $currentLines; $_--) {
407 Irssi::command('statusbar ' . (set$_) . ' disable');
408 remove_statusbar($_);
413 # FIXME implement $get_size_only check, and user $item->{min|max-size} ??
415 my ($line, $item, $get_size_only) = @_;
422 my $text = $actString->[$line]; # DO NOT set the actual $actString->[$line] to '' here or
423 $text = '' unless defined $text; # you'll screw up the statusbar counter ($currentLines)
424 $item->default_handler($get_size_only, $text, '', 1);
427 # remove old statusbars
430 my ($textDest, $cont, $cont_stripped) = @_;
431 if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq ''
432 and !defined($textDest->{'server'})
434 my $name = quotemeta(set '');
435 if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = {}; }
436 Irssi::signal_stop();
441 Irssi::signal_add_first('print text' => 'get_old_status');
442 Irssi::command('statusbar');
443 Irssi::signal_remove('print text' => 'get_old_status');
444 remove_statusbar(keys %killBar);
456 my ($textDest, undef, $cont_stripped) = @_;
457 if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq ''
458 and !defined($textDest->{'server'})
460 if ($cont_stripped =~ m/((?:meta-)+)(.)\s+change_window (\d+)/) {
461 my ($level, $key, $window) = ($1, $2, $3);
462 my $numlevel = ($level =~ y/-//) - 1;
463 $keymap{$window} = ('-' x $numlevel) . "$key";
465 Irssi::signal_stop();
471 Irssi::signal_remove('command bind' => 'watch_keymap');
472 Irssi::signal_add_first('print text' => 'get_keymap');
473 Irssi::command('bind'); # stolen from grep
474 Irssi::signal_remove('print text' => 'get_keymap');
475 Irssi::signal_add('command bind' => 'watch_keymap');
476 Irssi::timeout_add_once(100, 'eventChanged', undef);
479 # watch keymap changes
481 Irssi::timeout_add_once(1000, 'update_keymap', undef);
488 # {{{ format handling
490 # a bad way do do expansions but who cares
492 my ($string, %format) = @_;
494 $string =~ s/\$$exp/$repl/g while (($exp, $repl) = each(%format));
499 # fe-common::core::formats.c:format_expand_styles
500 # delete format_backs format_fores bold_fores other stuff
501 (map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8:|FnN>#[')),
503 (map { $_ => $_ } (split //, '{}%')),
505 sub ir_strip_codes { # strip %codes
507 $o =~ s/(%(.))/exists $strip_table{$2} ? $strip_table{$2} : $1/gex;
511 sub ir_parse_special {
512 my $o; my $i = shift;
513 #if ($_[0]) { # for the future?!?
515 # $o = $_[0]->parse_special($i);
521 my $win = shift || Irssi::active_win();
522 my $server = Irssi::active_server();
523 if (ref $win and ref $win->{'active'}) {
524 $o = $win->{'active'}->parse_special($i);
526 elsif (ref $win and ref $win->{'active_server'}) {
527 $o = $win->{'active_server'}->parse_special($i);
529 elsif (ref $server) {
530 $o = $server->parse_special($i);
533 $o = Irssi::parse_special($i);
537 sub ir_parse_special_protected {
538 my $o; my $i = shift;
540 ( \\. ) | # skip over escapes (maybe)
541 ( \$[^% $\]+ ) # catch special variables
544 elsif ($2) { my $i2 = $2; ir_fe(ir_parse_special($i2, @_)) }
551 sub sb_ctfe { # Irssi::current_theme->format_expand wrapper
552 Irssi::current_theme->format_expand(
555 Irssi::EXPAND_FLAG_IGNORE_REPLACES
557 ($_[0]?0:Irssi::EXPAND_FLAG_IGNORE_EMPTY)
561 sub sb_expand { # expand {format }s (and apply parse_special for $vars)
569 ); # does this get us the actual length of that s*ty bar :P ?
572 # unicode cludge, d*mn broken Irssi
573 # screw it, this will fail from broken joining anyway (and cause warnings)
574 my $term_type = 'term_type';
575 if (Irssi::version > 20040819) { # this is probably wrong, but I don't know
576 # when the setting name got changed
577 $term_type = 'term_charset';
579 #if (lc Irssi::settings_get_str($term_type) eq '8bit'
580 # or Irssi::settings_get_str($term_type) =~ /^iso/i
582 # length(sb_strip(shift))
585 my $temp = sb_strip(shift);
586 # try to get the displayed width
589 require Text::CharWidth;
590 $length = Text::CharWidth::mbswidth($temp);
596 if (lc Irssi::settings_get_str($term_type) eq 'utf-8') {
597 # try to switch on utf8
601 #$temp = Encode::decode_utf8($temp); # thanks for the hint, but I have my
602 # # reasons for _utf8_on
603 Encode::_utf8_on($temp);
606 # there is nothing more I can do
612 # !!! G*DD*MN Irssi is adding an additional layer of backslashitis per { } layer
613 # !!! AND I still don't know what I need to escape.
614 # !!! and NOONE else seems to know or care either.
615 # !!! f*ck open source. I mean it.
616 # XXX any Irssi::print debug statement leads to SEGFAULT - why ?
618 # major parts of the idea by buu (#perl @ freenode)
619 # thanks to fxn and Somni for debugging
620 # while ($_[0] =~ /(.)/g) {
621 # my $c = $1; # XXX sooo... goto kills $1
622 # if ($q eq '%') { goto ESC; }
624 ## <freenode:#perl:tybalt89> s/%(.)|(\{)|(\})|(\\|\$)/$1?$1:$2?($level++,$2):$3?($level>$min_level&&$level--,$3):'\\'x(2**$level-1).$4/ge; # untested...
626 my $min_level = $_[1] || 0; my $level = $min_level;
633 ( \$(?=[^\\]) ) | # $5
636 if ($1) { $1 } # %. escape
637 elsif ($2) { $level++; $2 } # { nesting start
638 elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end
639 elsif ($4) { '\\'x(2**$level) } # \ needs \\escaping
640 elsif ($5) { '\\'x(2**$level-1) . '$' . '\\'x(2**$level-1) } # and $ needs even more because of "parse_special"
641 else { '\\'x(2**$level-1) . '$' } # $ needs \$ escaping
646 # my $min_level = $_[1] || 0; my $level = $min_level;
654 # if ($1) { $1 } # %. escape
655 # elsif ($2) { $level++; $2 } # { nesting start
656 # elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end
657 # else { '\\'x(2**($level-1)-1) . $4 } # \ or $ needs \\escaping
662 sub ir_fe { # try to fix format stuff
664 # XXX why do I have to use two/four % here instead of one/two ??
665 # answer: you screwed up in ir_escape
666 $x =~ s/([%{}])/%$1/g;
667 #$x =~ s/(\\|\$|[ ])/\\$1/g; # XXX HOW CAN I HANDLE THE SPACES CORRECTLY XXX
668 $x =~ s/(\\|\$)/\\$1/g;
669 #$x =~ s/(\$(?=.))|(\$)/$1?"\\\$\\":"\\\$"/ge; # I think this should be here
670 # # (logic), but it doesn't work
672 #$x =~ s/\\/\\\\/g; # that's right, escape escapes
675 sub ir_ve { # escapes special vars but leave colours alone
677 #$x =~ s/([%{}])/%$1/g;
678 $x =~ s/(\\|\$|[ ])/\\$1/g;
684 my ($i, $j, $k) = (0, 0, 0);
686 # fe-common::core::formats.c:format_expand_styles
688 (map { $_ => $terminfo->setab($i++) } (split //, '01234567' )),
690 (map { $_ => $terminfo->setaf($j++) } (split //, 'krgybmcw' )),
692 (map { $_ => $terminfo->bold() .
693 $terminfo->setaf($k++) } (split //, 'KRGYBMCW')),
695 #(map { $_ => $terminfo->op() } (split //, 'nN')),
696 (map { $_ => $terminfo->op() } (split //, 'n')),
697 (map { $_ => "\033[0m" } (split //, 'N')), # XXX quick and DIRTY
699 F => $terminfo->blink(),
701 8 => $terminfo->rev(),
703 (map { $_ => $terminfo->bold() } (split //, '9_')),
705 (map { $_ => '' } (split //, ':|>#[')),
707 (map { $_ => $_ } (split //, '{}%')),
710 sub formats_to_ansi_basic {
712 $o =~ s/(%(.))/exists $ansi_table{$2} ? $ansi_table{$2} : $1/gex;
716 sub lc1459 ($) { my $x = shift; $x =~ y/A-Z][\^/a-z}{|~/; $x }
717 Irssi::settings_add_str(setc, 'banned_channels', '');
718 Irssi::settings_add_bool(setc, 'banned_channels_on', 0);
719 my %banned_channels = map { lc1459($_) => undef }
720 split ' ', Irssi::settings_get_str('banned_channels');
721 Irssi::settings_add_str(setc, 'fancy_abbrev', 'fancy');
729 #my $xx = $callcount; Irssi::print("starting remake [ $xx ]");
730 my ($hilight, $number, $display);
731 my $separator = '{sb_act_sep ' . Irssi::settings_get_str(set 'separator') .
733 my $custSort = Irssi::settings_get_str(set 'sort');
735 if ($custSort =~ /^[-!](.*)/) {
743 ( (int($a->{$custSort}) <=> int($b->{$custSort})) * $custSortDir )
745 ($a->{'refnum'} <=> $b->{'refnum'})
748 my $block = Irssi::settings_get_int(set 'block');
749 my $columns = $currentColumns;
750 my $oldActString = $actString if $SCREEN_MODE;
751 $actString = $SCREEN_MODE ? [' A W L'] : [];
752 my $line = $SCREEN_MODE ? 1 : 0;
753 my $width = $SCREEN_MODE
755 $screenWidth - abs($block)*$columns + 1
757 ([Irssi::windows]->[0]{'width'} - sb_length('{sb x}'));
758 my $height = $screenHeight - abs(Irssi::settings_get_int(set
760 my ($numPad, $keyPad) = (0, 0);
762 if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength')
766 if (Irssi::settings_get_str('fancy_abbrev') !~ /^(no|off|head)/i) {
767 my @nameList = map { ref $_ ? $_->get_active_name : '' } @wins;
768 for (my $i = 0; $i < @nameList - 1; ++$i) {
769 my ($x, $y) = ($nameList[$i], $nameList[$i + 1]);
770 for ($x, $y) { s/^[+#!=]// }
771 my $res = Algorithm::LCSS::LCSS($x, $y);
773 #Irssi::print("common pattern $x $y : $res");
774 #Irssi::print("found at $nameList[$i] ".index($nameList[$i],
776 $abbrevList{$nameList[$i]} = int (index($nameList[$i], $res) +
778 #Irssi::print("found at ".$nameList[$i+1]." ".index($nameList[$i+1],
780 $abbrevList{$nameList[$i+1]} = int (index($nameList[$i+1], $res) +
785 if ($SCREEN_MODE or ($block < 0)) {
786 $numPad = length((sort { length($b) <=> length($a) } keys %keymap)[0]);
787 $keyPad = length((sort { length($b) <=> length($a) } values %keymap)[0]);
791 print STDERR $screenansi->dcs().
795 if (@$oldActString < 1) {
796 print STDERR $screenansi->dcs().
797 $terminfo->cup(0, $width).
803 foreach my $win (@wins) {
804 unless ($SCREEN_MODE) {
805 $actString->[$line] = '' unless defined $actString->[$line]
806 or Irssi::settings_get_bool(set 'all_disable');
809 # all stolen from chanact, what does this code do and why do we need it ?
812 my $name = $win->get_active_name;
813 $name = '*' if (Irssi::settings_get_bool('banned_channels_on') and exists
814 $banned_channels{lc1459($name)});
815 $name = $win->{'name'} if $name ne '*' and $win->{'name'} ne ''
816 and Irssi::settings_get_bool(set 'prefer_name');
817 my $active = $win->{'active'};
818 my $colour = $win->{'hilight_color'};
819 if (!defined $colour) { $colour = ''; }
821 if ($win->{'data_level'} < Irssi::settings_get_int(set 'hide_data')) {
823 if ($win->{'data_level'} == 0) { $hilight = '{sb_act_none '; }
824 elsif ($win->{'data_level'} == 1) { $hilight = '{sb_act_text '; }
825 elsif ($win->{'data_level'} == 2) { $hilight = '{sb_act_msg '; }
826 elsif ($colour ne '') { $hilight = "{sb_act_hilight_color $colour "; }
827 elsif ($win->{'data_level'} == 3) { $hilight = '{sb_act_hilight '; }
828 else { $hilight = '{sb_act_special '; }
830 $number = $win->{'refnum'};
831 my @display = ('display_nokey');
832 if (defined $keymap{$number} and $keymap{$number} ne '') {
833 unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display;
835 if (Irssi::active_win->{'refnum'} == $number) {
836 unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy } @display;
838 #Irssi::print("win $number [@display]: " . join '.', split //, join '<<', map {
839 # Irssi::settings_get_str(set $_) } @display);
840 $display = (grep { $_ }
841 map { Irssi::settings_get_str(set $_) }
843 #Irssi::print("win $number : " . join '.', split //, $display);
845 if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength')
848 my $baseLength = sb_length(ir_escape(ir_ve(ir_parse_special_protected(sb_ctfe(
849 '{sb_background}' . expand($display,
851 N => $number . (' 'x($numPad - length($number))),
852 Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}),
854 S => '}{sb_background}'
856 my $diff = abs($block) - (length($name) + $baseLength);
857 if ($diff < 0) { # too long
858 if (abs($diff) >= length($name)) { $name = '' } # forget it
859 elsif (abs($diff) + 1 >= length($name)) { $name = substr($name,
862 my $middle = exists $abbrevList{$name} ?
863 (($abbrevList{$name} + (2*(length($name) / 2)))/3) :
864 ((Irssi::settings_get_str('fancy_abbrev') =~ /^head/i) ?
866 (length($name) / 2));
867 my $cut = int($middle - (abs($diff) / 2) + .55);
868 $cut = 1 if $cut < 1;
869 $cut = length($name) - abs($diff) - 1 if $cut > (length($name) -
871 $name = substr($name, 0, $cut) . '~' . substr($name, $cut +
875 elsif ($SCREEN_MODE or ($block < 0)) {
876 $name .= (' ' x $diff);
880 my $add = ir_ve(ir_parse_special_protected(sb_ctfe('{sb_background}' . expand($display,
882 N => $number . (' 'x($numPad - length($number))),
883 Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}),
885 S => '}{sb_background}'
888 $actString->[$line] = $add;
889 if ((!defined $oldActString->[$line]
890 or $oldActString->[$line] ne $actString->[$line])
892 $line <= ($columns * $height)
894 print STDERR $screenansi->dcs().
895 $terminfo->cup(($line-1) % $height+1, $width + (
896 abs($block) * int(($line-1) / $height))).
897 formats_to_ansi_basic(sb_expand(ir_escape($actString->[$line]))).
904 #$temp =~ s/\{\S+?(?:\s(.*?))?\}/$1/g;
905 #$temp =~ s/\\\\\\\\/\\/g; # XXX I'm actually guessing here, someone point me
906 # # XXX to docs please
907 $actString->[$line] = '' unless defined $actString->[$line];
909 # XXX how can I check whether the content still fits in the bar? this would
910 # XXX allow awlstatus to reside on a statusbar together with other items...
911 if (sb_length(ir_escape($actString->[$line] . $add)) >= $width) {
912 # XXX doesn't correctly handle utf-8 multibyte ... help !!?
913 $actString->[$line] .= ' ' x ($width - sb_length(ir_escape(
914 $actString->[$line])));
917 $actString->[$line] .= $add . $separator;
918 # XXX if I use these prints, output layout gets screwed up... why ?
919 #Irssi::print("line $line: ".$actString->[$line]);
920 #Irssi::print("temp $line: ".$temp);
925 while ($line <= ($columns * $height)) {
926 print STDERR $screenansi->dcs().
927 $terminfo->cup(($line-1) % $height+1, $width + (
928 abs($block) * int(($line-1) / $height))).
933 print STDERR $screenansi->dcs().
939 # XXX the Irssi::print statements lead to the MOST WEIRD results
940 # e.g.: the loop gets executed TWICE for p > 0 ?!?
941 for (my $p = 0; $p < @$actString; $p++) { # wrap each line in {sb }, escape it
942 my $x = $actString->[$p]; # properly, etc.
943 $x =~ s/\Q$separator\E([ ]*)$/$1/;
944 #Irssi::print("[$p]".'current:'.join'.',split//,sb_strip(ir_escape($x,0)));
945 #Irssi::print("assumed length before:".sb_length(ir_escape($x,0)));
947 #Irssi::print("[$p]".'new:'.join'.',split//,sb_expand(ir_escape($x,0)));
948 #Irssi::print("[$p]".'new:'.join'.',split//,ir_escape($x,0));
949 #Irssi::print("assumed length after:".sb_length(ir_escape($x,0)));
951 #Irssi::print("[$p]".'REALnew:'.join'.',split//,sb_strip($x));
952 $actString->[$p] = $x;
953 # XXX any Irssi::print debug statement leads to SEGFAULT (sometimes) - why ?
956 #Irssi::print("remake [ $xx ] finished");
959 sub awlHasChanged () {
961 my $temp = ($SCREEN_MODE ?
962 "\\\n" . Irssi::settings_get_int(set 'block').
963 Irssi::settings_get_int(set 'height_adjust')
964 : "!\n" . Irssi::settings_get_str(set 'placement').
965 Irssi::settings_get_int(set 'position')).
966 Irssi::settings_get_str(set 'automode');
967 if ($temp ne $resetNeeded) { wlreset(); return; }
968 #Irssi::print("awl has changed, calls to remake so far: $callcount");
973 ($SCREEN_MODE and !$DISABLE_SCREEN_TEMP)
975 ($needRemake and Irssi::settings_get_bool(set 'all_disable'))
977 (!Irssi::settings_get_bool(set 'all_disable') and $currentLines < 1)
983 unless ($SCREEN_MODE) {
984 # XXX Irssi crashes if I try to do this without timer, why ? What's the minimum
985 # XXX delay I need to use in the timer ?
986 Irssi::timeout_add_once(100, 'syncLines', undef);
988 for (keys %statusbars) {
989 Irssi::statusbar_items_redraw(set$_);
993 Irssi::timeout_add_once(100, 'syncColumns', undef);
997 sub eventChanged () { # Implement a change queue/blocker -.-)
998 if (defined $globTime) {
999 Irssi::timeout_remove($globTime);
1000 } # delay the update further
1001 $globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awlHasChanged', undef);
1009 sub screenFullRedraw {
1011 if (!ref $window or $window->{'refnum'} == Irssi::active_win->{'refnum'}) {
1017 sub screenSize { # from nicklist.pl
1018 $screenResizing = 1;
1020 system 'screen -x '.$ENV{'STY'}.' -X fit';
1022 my ($row, $col) = split ' ', `stty size`;
1024 $screenWidth = $col-1;
1025 $screenHeight = $row-1;
1027 # on some recent systems, "screen -X fit; screen -X width -w 50" doesn't work, needs a sleep in between the 2 commands
1028 # so we wait a second before setting the width
1029 Irssi::timeout_add_once(100, sub {
1030 my ($new_irssi_width) = @_;
1031 $new_irssi_width -= abs(Irssi::settings_get_int(set
1032 'block'))*$currentColumns - 1;
1033 system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $new_irssi_width;
1034 # and then we wait another second for the resizing, and then redraw.
1035 Irssi::timeout_add_once(10,sub {$screenResizing = 0; screenFullRedraw()}, []);
1040 my ($unloadMode) = @_;
1041 Irssi::signal_remove('gui print text finished' => 'screenFullRedraw');
1042 Irssi::signal_remove('gui page scrolled' => 'screenFullRedraw');
1043 Irssi::signal_remove('window changed' => 'screenFullRedraw');
1044 Irssi::signal_remove('window changed automatic' => 'screenFullRedraw');
1046 Irssi::signal_remove('terminal resized' => 'resizeTerm');
1048 system 'screen -x '.$ENV{'STY'}.' -X fit';
1052 return if (@$actString == 0);
1053 my $temp = $currentColumns;
1054 #Irssi::print("current columns $temp");
1055 my $height = $screenHeight - abs(Irssi::settings_get_int(set
1057 $currentColumns = int(($#$actString-1) / $height) + 1;
1058 #Irssi::print("objects in actstring:".scalar(@$actString).", screen height:".
1060 my $currMaxColumns = Irssi::settings_get_int(set 'columns');
1061 if ($currMaxColumns > 0 and $currentColumns > $currMaxColumns) {
1062 $currentColumns = $currMaxColumns;
1064 elsif ($currMaxColumns < 0) {
1065 $currentColumns = abs($currMaxColumns);
1067 return if ($temp == $currentColumns);
1073 if ($SCREEN_MODE and !$screenResizing) {
1074 $screenResizing = 1;
1075 Irssi::timeout_add_once(10, 'screenSize', undef);
1077 Irssi::timeout_add_once(100, 'eventChanged', undef);
1085 Irssi::settings_add_str(setc, set 'display_nokey', '[$N]$H$C$S');
1086 Irssi::settings_add_str(setc, set 'display_key', '[$Q=$N]$H$C$S');
1087 Irssi::settings_add_str(setc, set 'display_nokey_active', '');
1088 Irssi::settings_add_str(setc, set 'display_key_active', '');
1089 Irssi::settings_add_str(setc, set 'separator', "\\ ");
1090 Irssi::settings_add_bool(setc, set 'prefer_name', 0);
1091 Irssi::settings_add_int(setc, set 'hide_data', 0);
1092 Irssi::settings_add_int(setc, set 'maxlines', 9);
1093 Irssi::settings_add_int(setc, set 'columns', 1);
1094 Irssi::settings_add_int(setc, set 'block', 20);
1095 Irssi::settings_add_bool(setc, set 'sbar_maxlength', 0);
1096 Irssi::settings_add_int(setc, set 'height_adjust', 2);
1097 Irssi::settings_add_str(setc, set 'sort', 'refnum');
1098 Irssi::settings_add_str(setc, set 'placement', 'bottom');
1099 Irssi::settings_add_int(setc, set 'position', 0);
1100 Irssi::settings_add_bool(setc, set 'all_disable', 0);
1101 Irssi::settings_add_str(setc, set 'automode', 'sbar');
1110 $currentLines = 0; # 1; # mhmmmm .. we actually enable one line down there so
1114 # Register statusbar
1116 #Irssi::command('statusbar wl0 enable');
1117 my $was_screen_mode = $SCREEN_MODE;
1118 if ($SCREEN_MODE = (Irssi::settings_get_str(set 'automode') =~ /screen/i)
1122 if (!defined $ENV{'STY'}) {
1123 Irssi::print('Screen mode can only be used in GNU screen but no '.
1124 'screen was found.', MSGLEVEL_CLIENTERROR);
1125 $SCREEN_MODE = undef;
1128 Irssi::signal_add_last('gui print text finished' => 'screenFullRedraw');
1129 Irssi::signal_add_last('gui page scrolled' => 'screenFullRedraw');
1130 Irssi::signal_add('window changed' => 'screenFullRedraw');
1131 Irssi::signal_add('window changed automatic' => 'screenFullRedraw');
1134 elsif ($was_screen_mode and !$SCREEN_MODE) {
1137 $resetNeeded = ($SCREEN_MODE ?
1138 "\\\n" . Irssi::settings_get_int(set 'block').
1139 Irssi::settings_get_int(set 'height_adjust')
1140 : "!\n" . Irssi::settings_get_str(set 'placement').
1141 Irssi::settings_get_int(set 'position')).
1142 Irssi::settings_get_str(set 'automode');
1156 # pretend we didn't do anything ASAP
1157 Irssi::timeout_add_once(10, sub { $Unload = undef; }, undef);
1159 # last try to catch a sigsegv
1160 Irssi::signal_add_first('gui exit' => sub { $Unload = undef; });
1162 # this might well crash Irssi... try /eval /script unload someotherscript ;
1163 # /quit (= SEGFAULT !)
1165 $actString = ['']; # syncLines(); # XXX Irssi crashes when trying to disable
1166 killOldStatus(); # XXX all statusbars ?
1168 screenOff('unload mode');
1178 sub addPrintTextHook { # update on print text
1179 return if $_[0]->{'level'} == 262144 and $_[0]->{'target'} eq ''
1180 and !defined($_[0]->{'server'});
1181 if (Irssi::settings_get_str(set 'sort') =~ /^[-!]?last_line$/) {
1182 Irssi::timeout_add_once(100, 'eventChanged', undef);
1186 #sub _x { my ($x, $y) = @_; ($x, sub { Irssi::print('-->signal '.$x); eval "$y();"; }) }
1188 Irssi::signal_add_first(
1189 'command script unload' => 'unload'
1191 Irssi::signal_add_last({
1192 'setup changed' => 'eventChanged',
1193 'print text' => 'addPrintTextHook',
1194 'terminal resized' => 'resizeTerm',
1195 'setup reread' => 'wlreset',
1196 'window hilight' => 'eventChanged',
1199 'window created' => 'eventChanged',
1200 'window destroyed' => 'eventChanged',
1201 'window name changed' => 'eventChanged',
1202 'window refnum changed' => 'eventChanged',
1203 'window changed' => 'eventChanged',
1204 'window changed automatic' => 'eventChanged',
1207 #Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # relicts
1217 my ($data, $server, $item) = @_;
1218 Irssi::command_runsub($cmd, $data, $server, $item);
1221 Irssi::command_bind( setc() => runsub(setc()) );
1222 Irssi::command_bind( setc() . ' paste' => runsub(setc() . ' paste') );
1223 Irssi::command_bind(
1224 setc() . ' paste on' => sub {
1225 return unless $SCREEN_MODE;
1226 my $was_disabled = $DISABLE_SCREEN_TEMP;
1227 $DISABLE_SCREEN_TEMP = 1;
1228 Irssi::print('Paste mode is now ON, '.uc(setc()).' is temporarily '.
1230 if (!$was_disabled) {
1231 $screenResizing = 1;
1236 Irssi::command_bind(
1237 setc() . ' paste off' => sub {
1238 return unless $SCREEN_MODE;
1239 my $was_disabled = $DISABLE_SCREEN_TEMP;
1240 $DISABLE_SCREEN_TEMP = undef;
1241 Irssi::print('Paste mode is now OFF, '.uc(setc()).' is enabled.');
1242 if ($was_disabled) {
1243 $SCREEN_MODE = undef;
1244 $screenResizing = 0;
1249 Irssi::command_bind(
1250 setc() . ' paste toggle' => sub {
1251 if ($DISABLE_SCREEN_TEMP) {
1252 Irssi::command(setc() . ' paste off');
1255 Irssi::command(setc() . ' paste on');
1259 Irssi::command_bind(
1260 setc() . ' redraw' => sub {
1261 return unless $SCREEN_MODE;
1269 # {{{ Algorithm::LCSS module
1271 package Algorithm::Diff;
1272 # Skip to first "=head" line for documentation.
1275 use integer; # see below in _replaceNextLargerWith() for mod to make
1276 # if you don't use this
1278 # McIlroy-Hunt diff algorithm
1279 # Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com>
1280 # by Ned Konz, perl@bike-nomad.com
1281 # Updates by Tye McQueen, http://perlmonks.org/?node=tye
1283 # Create a hash that maps each element of $aCollection to the set of
1284 # positions it occupies in $aCollection, restricted to the elements
1285 # within the range of indexes specified by $start and $end.
1286 # The fourth parameter is a subroutine reference that will be called to
1287 # generate a string to use as a key.
1288 # Additional parameters, if any, will be passed to this subroutine.
1290 # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen );
1292 sub _withPositionsOfInInterval
1294 my $aCollection = shift; # array ref
1300 for ( $index = $start ; $index <= $end ; $index++ )
1302 my $element = $aCollection->[$index];
1303 my $key = &$keyGen( $element, @_ );
1304 if ( exists( $d{$key} ) )
1306 unshift ( @{ $d{$key} }, $index );
1310 $d{$key} = [$index];
1313 return wantarray ? %d : \%d;
1316 # Find the place at which aValue would normally be inserted into the
1317 # array. If that place is already occupied by aValue, do nothing, and
1318 # return undef. If the place does not exist (i.e., it is off the end of
1319 # the array), add it to the end, otherwise replace the element at that
1320 # point with aValue. It is assumed that the array's values are numeric.
1321 # This is where the bulk (75%) of the time is spent in this module, so
1322 # try to make it fast!
1324 sub _replaceNextLargerWith
1326 my ( $array, $aValue, $high ) = @_;
1330 if ( $high == -1 || $aValue > $array->[-1] )
1332 push ( @$array, $aValue );
1336 # binary search for insertion point...
1340 while ( $low <= $high )
1342 $index = ( $high + $low ) / 2;
1344 # $index = int(( $high + $low ) / 2); # without 'use integer'
1345 $found = $array->[$index];
1347 if ( $aValue == $found )
1351 elsif ( $aValue > $found )
1361 # now insertion point is in $low.
1362 $array->[$low] = $aValue; # overwrite next larger
1366 # This method computes the longest common subsequence in $a and $b.
1368 # Result is array or ref, whose contents is such that
1369 # $a->[ $i ] == $b->[ $result[ $i ] ]
1370 # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined.
1372 # An additional argument may be passed; this is a hash or key generating
1373 # function that should return a string that uniquely identifies the given
1374 # element. It should be the case that if the key is the same, the elements
1375 # will compare the same. If this parameter is undef or missing, the key
1376 # will be the element as a string.
1378 # By default, comparisons will use "eq" and elements will be turned into keys
1379 # using the default stringizing operator '""'.
1381 # Additional parameters, if any, will be passed to the key generation
1384 sub _longestCommonSubsequence
1386 my $a = shift; # array ref or hash ref
1387 my $b = shift; # array ref or hash ref
1388 my $counting = shift; # scalar
1389 my $keyGen = shift; # code ref
1390 my $compare; # code ref
1392 if ( ref($a) eq 'HASH' )
1393 { # prepared hash must be in $b
1399 # Check for bogus (non-ref) argument values
1400 if ( !ref($a) || !ref($b) )
1402 my @callerInfo = caller(1);
1403 die 'error: must pass array or hash references to ' . $callerInfo[3];
1407 # Note that these are optimized.
1408 if ( !defined($keyGen) ) # optimize for strings
1410 $keyGen = sub { $_[0] };
1411 $compare = sub { my ( $a, $b ) = @_; $a eq $b };
1418 &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ );
1422 my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] );
1423 my ( $prunedCount, $bMatches ) = ( 0, {} );
1425 if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us?
1431 my ( $bStart, $bFinish ) = ( 0, $#$b );
1433 # First we prune off any common elements at the beginning
1434 while ( $aStart <= $aFinish
1435 and $bStart <= $bFinish
1436 and &$compare( $a->[$aStart], $b->[$bStart], @_ ) )
1438 $matchVector->[ $aStart++ ] = $bStart++;
1443 while ( $aStart <= $aFinish
1444 and $bStart <= $bFinish
1445 and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) )
1447 $matchVector->[ $aFinish-- ] = $bFinish--;
1451 # Now compute the equivalence classes of positions of elements
1453 _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ );
1458 my ( $i, $ai, $j, $k );
1459 for ( $i = $aStart ; $i <= $aFinish ; $i++ )
1461 $ai = &$keyGen( $a->[$i], @_ );
1462 if ( exists( $bMatches->{$ai} ) )
1465 for $j ( @{ $bMatches->{$ai} } )
1468 # optimization: most of the time this will be true
1469 if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j )
1475 $k = _replaceNextLargerWith( $thresh, $j, $k );
1478 # oddly, it's faster to always test this (CPU cache?).
1482 [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ];
1490 return $prunedCount + @$thresh if $counting;
1491 for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] )
1493 $matchVector->[ $link->[1] ] = $link->[2];
1498 return $prunedCount;
1501 return wantarray ? @$matchVector : $matchVector;
1504 sub traverse_sequences
1506 my $a = shift; # array ref
1507 my $b = shift; # array ref
1508 my $callbacks = shift || {};
1510 my $matchCallback = $callbacks->{'MATCH'} || sub { };
1511 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
1512 my $finishedACallback = $callbacks->{'A_FINISHED'};
1513 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
1514 my $finishedBCallback = $callbacks->{'B_FINISHED'};
1515 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
1517 # Process all the lines in @$matchVector
1523 for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ )
1525 my $bLine = $matchVector->[$ai];
1526 if ( defined($bLine) ) # matched
1528 &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine;
1529 &$matchCallback( $ai, $bi++, @_ );
1533 &$discardACallback( $ai, $bi, @_ );
1537 # The last entry (if any) processed was a match.
1538 # $ai and $bi point just past the last matching lines in their sequences.
1540 while ( $ai <= $lastA or $bi <= $lastB )
1544 if ( $ai == $lastA + 1 and $bi <= $lastB )
1546 if ( defined($finishedACallback) )
1548 &$finishedACallback( $lastA, @_ );
1549 $finishedACallback = undef;
1553 &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB;
1558 if ( $bi == $lastB + 1 and $ai <= $lastA )
1560 if ( defined($finishedBCallback) )
1562 &$finishedBCallback( $lastB, @_ );
1563 $finishedBCallback = undef;
1567 &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA;
1571 &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA;
1572 &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB;
1578 sub traverse_balanced
1580 my $a = shift; # array ref
1581 my $b = shift; # array ref
1582 my $callbacks = shift || {};
1584 my $matchCallback = $callbacks->{'MATCH'} || sub { };
1585 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
1586 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
1587 my $changeCallback = $callbacks->{'CHANGE'};
1588 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
1590 # Process all the lines in match vector
1601 # Find next match indices $ma and $mb
1605 $ma <= $#$matchVector
1606 && !defined $matchVector->[$ma]
1609 last if $ma > $#$matchVector; # end of matchVector?
1610 $mb = $matchVector->[$ma];
1612 # Proceed with discard a/b or change events until
1614 while ( $ai < $ma || $bi < $mb )
1617 if ( $ai < $ma && $bi < $mb )
1621 if ( defined $changeCallback )
1623 &$changeCallback( $ai++, $bi++, @_ );
1627 &$discardACallback( $ai++, $bi, @_ );
1628 &$discardBCallback( $ai, $bi++, @_ );
1633 &$discardACallback( $ai++, $bi, @_ );
1639 &$discardBCallback( $ai, $bi++, @_ );
1644 &$matchCallback( $ai++, $bi++, @_ );
1647 while ( $ai <= $lastA || $bi <= $lastB )
1649 if ( $ai <= $lastA && $bi <= $lastB )
1653 if ( defined $changeCallback )
1655 &$changeCallback( $ai++, $bi++, @_ );
1659 &$discardACallback( $ai++, $bi, @_ );
1660 &$discardBCallback( $ai, $bi++, @_ );
1663 elsif ( $ai <= $lastA )
1665 &$discardACallback( $ai++, $bi, @_ );
1671 &$discardBCallback( $ai, $bi++, @_ );
1680 my $a = shift; # array ref
1681 my $keyGen = shift; # code ref
1684 $keyGen = sub { $_[0] } unless defined($keyGen);
1686 return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ );
1691 my $a = shift; # array ref
1692 my $b = shift; # array ref or hash ref
1693 my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ );
1696 for ( $i = 0 ; $i <= $#$matchVector ; $i++ )
1698 if ( defined( $matchVector->[$i] ) )
1700 push ( @retval, $a->[$i] );
1703 return wantarray ? @retval : \@retval;
1708 my $a = shift; # array ref
1709 my $b = shift; # array ref or hash ref
1710 return _longestCommonSubsequence( $a, $b, 1, @_ );
1717 my $match= _longestCommonSubsequence( $a, $b, 0, @_ );
1718 my @am= grep defined $match->[$_], 0..$#$match;
1719 my @bm= @{$match}[@am];
1727 my( $am, $bm )= LCSidx( $a, $b, @_ );
1729 my( $ai, $bi )= ( 0, 0 );
1730 push @cdiff, $ai, $bi;
1732 while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) {
1737 push @cdiff, $ai, $bi;
1741 push @cdiff, $ai, $bi;
1743 push @cdiff, 0+@$a, 0+@$b
1744 if $ai < @$a || $bi < @$b;
1745 return wantarray ? @cdiff : \@cdiff;
1750 my $a = shift; # array ref
1751 my $b = shift; # array ref
1755 push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ];
1758 push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ];
1761 push @$retval, $hunk
1765 traverse_sequences( $a, $b,
1766 { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ );
1768 return wantarray ? @$retval : $retval;
1773 my $a = shift; # array ref
1774 my $b = shift; # array ref
1776 my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) };
1777 my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) };
1779 push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] );
1782 push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] );
1789 DISCARD_A => $discard,
1795 return wantarray ? @$retval : $retval;
1798 ########################################
1799 my $Root= __PACKAGE__;
1800 package Algorithm::Diff::_impl;
1803 sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices
1804 # 1 # $me->[1]: Ref to first sequence
1805 # 2 # $me->[2]: Ref to second sequence
1806 sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos
1807 sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items
1808 sub _Base() { 5 } # $me->[_Base]: Added to range's min and max
1809 sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected
1810 sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position
1811 sub _Min() { -2 } # Added to _Off to get min instead of max+1
1816 Carp::confess( @_ );
1822 return if $me->[_Pos];
1823 my $meth= ( caller(1) )[3];
1824 Die( "Called $meth on 'reset' object" );
1829 my( $me, $seq )= @_;
1830 return $seq + $me->[_Off]
1831 if 1 == $seq || 2 == $seq;
1832 my $meth= ( caller(1) )[3];
1833 Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" );
1839 return ref $us if ref $us;
1840 return $us . "::_obj";
1845 my( $us, $seq1, $seq2, $opts ) = @_;
1847 for( $opts->{keyGen} ) {
1848 push @args, $_ if $_;
1850 for( $opts->{keyGenArgs} ) {
1851 push @args, @$_ if $_;
1853 my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args );
1855 if( 0 == $cdif->[2] && 0 == $cdif->[3] ) {
1857 splice @$cdif, 0, 2;
1859 my @obj= ( $cdif, $seq1, $seq2 );
1860 $obj[_End] = (1+@$cdif)/2;
1861 $obj[_Same] = $same;
1863 my $me = bless \@obj, $us->getObjPkg();
1870 my( $me, $pos )= @_;
1871 $pos= int( $pos || 0 );
1875 if $pos < 0 || $me->[_End] <= $pos;
1876 $me->[_Pos]= $pos || !1;
1877 $me->[_Off]= 2*$pos - 1;
1883 my( $me, $base )= @_;
1884 my $oldBase= $me->[_Base];
1885 $me->[_Base]= 0+$base if defined $base;
1891 my( $me, $pos, $base )= @_;
1893 my $you= bless \@obj, ref($me);
1894 $you->Reset( $pos ) if defined $pos;
1895 $you->Base( $base );
1900 my( $me, $steps )= @_;
1901 $steps= 1 if ! defined $steps;
1903 my $pos= $me->[_Pos];
1904 my $new= $pos + $steps;
1905 $new= 0 if $pos && $new < 0;
1912 my( $me, $steps )= @_;
1913 $steps= 1 if ! defined $steps;
1914 my $pos= $me->Next(-$steps);
1915 $pos -= $me->[_End] if $pos;
1922 return 0 if $me->[_Same] == ( 1 & $me->[_Pos] );
1924 my $off= $me->[_Off];
1925 for my $seq ( 1, 2 ) {
1927 if $me->[_Idx][ $off + $seq + _Min ]
1928 < $me->[_Idx][ $off + $seq ];
1934 my( $me, $seq, $base )= @_;
1936 my $off= $me->_ChkSeq($seq);
1937 $base= $me->[_Base] if !defined $base;
1938 return $base + $me->[_Idx][ $off + _Min ];
1942 my( $me, $seq, $base )= @_;
1944 my $off= $me->_ChkSeq($seq);
1945 $base= $me->[_Base] if !defined $base;
1946 return $base + $me->[_Idx][ $off ] -1;
1950 my( $me, $seq, $base )= @_;
1952 my $off = $me->_ChkSeq($seq);
1954 return $me->[_Idx][ $off ]
1955 - $me->[_Idx][ $off + _Min ];
1957 $base= $me->[_Base] if !defined $base;
1958 return ( $base + $me->[_Idx][ $off + _Min ] )
1959 .. ( $base + $me->[_Idx][ $off ] - 1 );
1963 my( $me, $seq )= @_;
1965 my $off = $me->_ChkSeq($seq);
1967 return $me->[_Idx][ $off ]
1968 - $me->[_Idx][ $off + _Min ];
1972 $me->[_Idx][ $off + _Min ]
1973 .. ( $me->[_Idx][ $off ] - 1 )
1980 return wantarray ? () : 0
1981 if $me->[_Same] != ( 1 & $me->[_Pos] );
1982 return $me->Items(1);
1993 items=> \&Items, # same thing
2001 for my $arg ( @_ ) {
2002 for my $word ( split ' ', $arg ) {
2004 if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/
2005 || not $meth= $getName{ lc $2 }
2007 Die( $Root, ", Get: Invalid request ($word)" );
2009 my( $base, $name, $seq )= ( $1, $2, $3 );
2010 push @value, scalar(
2013 : $meth->( $me, $seq, $base )
2019 } elsif( 1 == @value ) {
2022 Die( 0+@value, " values requested from ",
2023 $Root, "'s Get in scalar context" );
2027 my $Obj= getObjPkg($Root);
2030 for my $meth ( qw( new getObjPkg ) ) {
2031 *{$Root."::".$meth} = \
&{$meth};
2032 *{$Obj ."::".$meth} = \
&{$meth};
2035 Next Prev Reset Copy Base Diff
2036 Same Items Range Min Max Get
2039 *{$Obj."::".$meth} = \
&{$meth};
2044 package Algorithm
::LCSS
;
2049 *traverse_sequences
= \
&Algorithm
::Diff
::traverse_sequences
;
2052 sub _tokenize
{ [split //, $_[0]] }
2055 my $is_array = ref $_[0] eq 'ARRAY' ?
1 : 0;
2056 my ( $seq1, $seq2, @match, $from_match );
2061 traverse_sequences
( $seq1, $seq2, {
2062 MATCH
=> sub { push @
{$match[$i]}, $seq1->[$_[0]]; $from_match = 1 },
2063 DISCARD_A
=> sub { do{$i++; $from_match = 0} if $from_match },
2064 DISCARD_B
=> sub { do{$i++; $from_match = 0} if $from_match },
2068 $seq1 = _tokenize
($_[0]);
2069 $seq2 = _tokenize
($_[1]);
2070 traverse_sequences
( $seq1, $seq2, {
2071 MATCH
=> sub { $match[$i] .= $seq1->[$_[0]]; $from_match = 1 },
2072 DISCARD_A
=> sub { do{$i++; $from_match = 0} if $from_match },
2073 DISCARD_B
=> sub { do{$i++; $from_match = 0} if $from_match },
2080 my $match = CSS
(@_);
2081 if ( ref $_[0] eq 'ARRAY' ) {
2082 @
$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,scalar(@
$_)]}@
$match
2085 @
$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,length($_)]}@
$match
2091 my $is_array = ref $_[0] eq 'ARRAY' ?
1 : 0;
2096 for( my $i = 0; $i < @
$css; $i++ ) {
2097 next unless @
{$css->[$i]}>$length;
2099 $length = @
{$css->[$i]};
2103 for( my $i = 0; $i < @
$css; $i++ ) {
2104 next unless length($css->[$i])>$length;
2106 $length = length($css->[$i]);
2109 return $css->[$index];
2114 #{{{ Class::Classless module
2116 package Class
::Classless
;
2123 ###########################################################################
2125 @Class::Classless::X::ISA = ();
2127 ###########################################################################
2128 ###########################################################################
2130 sub Class::Classless::X::AUTOLOAD {
2131 # This's the big dispatcher.
2134 my $m = ($Class::Classless::X::AUTOLOAD =~ m/([^:]+)$/s )
2135 ? $1 : $Class::Classless::X::AUTOLOAD;
2137 croak "Can't call Class::Classless methods (like $m) without an object"
2138 unless ref $it; # sanity, basically.
2141 $prevstate = ${shift @_}
2142 if scalar(@_) && defined($_[0]) &&
2143 ref($_[0]) eq 'Class::Classless::CALLSTATE::SHIMMY'
2144 ; # A shim! we were called via $callstate->NEXT
2146 my $no_fail = $prevstate ? $prevstate->[3] : undef;
2147 my $i = $prevstate ? ($prevstate->[1] + 1) : 0;
2148 # where to start scanning
2151 # Get the linearization of the ISA tree
2153 $lineage = $prevstate->[2];
2154 } elsif(defined $it->{'ISA_CACHE'} and ref $it->{'ISA_CACHE'} ){
2155 $lineage = $it->{'ISA_CACHE'};
2157 $lineage = [ &Class::Classless::X::ISA_TREE($it) ];
2162 # $prevstate ? @{$prevstate->[2]}
2163 # : &Class::Classless::X::ISA_TREE($it);
2164 # # Get the linearization of the ISA tree
2165 # # ISA-memoization happens in the ISA_TREE function.
2167 for(; $i < @$lineage; ++$i) {
2169 if( !defined($no_fail) and exists($lineage->[$i]{'NO_FAIL'}) ) {
2170 $no_fail = ($lineage->[$i]{'NO_FAIL'} || 0);
2171 # so the first NO_FAIL sets it
2174 if( ref($lineage->[$i]{'METHODS'} || 0) # sanity
2175 && exists($lineage->[$i]{'METHODS'}{$m})
2177 # We found what we were after. Now see what to do with it.
2178 my $v = $lineage->[$i]{'METHODS'}{$m};
2179 return $v unless defined $v and ref $v;
2181 if(ref($v) eq 'CODE') { # normal case, I expect!
2182 # Used to have copying of the arglist here.
2183 # But it was apparently useless, so I deleted it
2185 $it, # $_[0] -- target object
2187 bless([$m, $i, $lineage, $no_fail, $prevstate ? 1 : 0],
2188 'Class::Classless::CALLSTATE'
2189 ), # $_[1] -- the callstate
2191 goto &{ $v }; # yes, magic goto! bimskalabim!
2193 return @$v if ref($v) eq '_deref_array';
2194 return $$v if ref($v) eq '_deref_scalar';
2195 return $v; # fallthru
2199 if($m eq 'DESTROY') { # mitigate DESTROY-lookup failure at global destruction
2200 # should be impossible
2205 croak "Can't find ", $prevstate ? 'NEXT method' : 'method',
2206 " $m in ", $it->{'NAME'} || $it,
2207 " or any ancestors\n";
2211 ###########################################################################
2212 ###########################################################################
2214 sub Class::Classless::X::DESTROY {
2218 ###########################################################################
2219 sub Class::Classless::X::ISA_TREE {
2221 # Returns the search path for $_[0], starting with $_[0]
2222 # Possibly memoized.
2224 # I stopped being able to understand this algorithm about five
2225 # minutes after I wrote it.
2228 my $set_cache = 0; # flag to set the cache on the way out
2230 if(exists($_[0]{'ISA_CACHE'})) {
2231 return @{$_[0]{'ISA_CACHE'}}
2232 if defined $_[0]{'ISA_CACHE'}
2233 and ref $_[0]{'ISA_CACHE'};
2235 # Otherwise, if exists but is not a ref, it's a signal that it should
2236 # be replaced at the earliest, with a listref
2240 my $has_mi = 0; # set to 0 on the first node we see with 2 parents!
2241 # First, just figure out what's in the tree.
2242 my %last_child = ($_[0] => 1); # as if already seen
2244 # if $last_child{$x} == $y, that means:
2245 # 1) incidentally, we've passed the node $x before.
2246 # 2) $x is the last child of $y,
2247 # so that means that $y can be pushed to the stack only after
2248 # we've pushed $x to the stack.
2253 my @in_stack = ($_[0]);
2256 defined($current = shift @in_stack)
2257 && ref($current) # sanity
2258 && ref($current->{'PARENTS'} || 0) # sanity
2261 push @tree_nodes, $current;
2263 $has_mi = 1 if @{$current->{'PARENTS'}} > 1;
2267 if(exists $last_child{$_}) { # seen before!
2268 $last_child{$_} = $current;
2269 (); # seen -- don't re-explore
2270 } else { # first time seen
2271 $last_child{$_} = $current;
2272 $_; # first time seen -- explore now
2275 @{$current->{'PARENTS'}}
2279 # If there was no MI, then that first scan was sufficient.
2281 $_[0]{'ISA_CACHE'} = \@tree_nodes if $set_cache;
2285 # Otherwise, toss this list and rescan, consulting %last_child
2288 # $last_child{$parent} holds the last (or only) child of $parent
2289 # in this tree. When walking the tree this time, only that
2290 # child is authorized to put its parent on the @in_stack.
2291 # And that's the only way a node can get added to @in_stack,
2292 # except for $_[0] (the start node) being there at the beginning.
2294 # Now, walk again, but this time exploring parents the LAST
2295 # time seen in the tree, not the first.
2300 my @in_stack = ($_[0]);
2302 next unless defined($current = shift @in_stack) && ref($current);
2303 push @out, $current; # finally.
2308 defined($_) # sanity
2310 && $last_child{$_} eq $current,
2312 # I'm lastborn (or onlyborn) of this parent
2313 # so it's OK to explore now
2314 @{$current->{'PARENTS'}}
2316 if ref($current->{'PARENTS'} || 0) # sanity
2320 unless(scalar(@out) == scalar(keys(%last_child))) {
2321 # the counts should be equal
2323 @good_ones{@out} = ();
2326 ($_[0]{'NAME'} || $_[0]) .
2327 " is apparently cyclic, probably involving the nodes " .
2328 nodelist( grep { ref($_) && !exists $good_ones{$_} }
2329 values(%last_child) )
2333 #print "Contents of out: ", nodelist(@out), "\n";
2335 $_[0]{'ISA_CACHE'} = \@out if $set_cache;
2339 ###########################################################################
2341 sub Class::Classless::X::can { # NOT like UNIVERSAL::can ...
2342 # return 1 if $it is capable of the method given -- otherwise 0
2343 my($it, $m) = @_[0,1];
2344 return undef unless ref $it;
2346 croak "undef is not a valid method name" unless defined($m);
2347 croak "null-string is not a valid method name" unless length($m);
2349 foreach my $o (&Class::Classless::X::ISA_TREE($it)) {
2351 if ref($o->{'METHODS'} || 0) # sanity
2352 && exists $o->{'METHODS'}{$m};
2359 ###########################################################################
2361 sub Class::Classless::X::isa { # Like UNIVERSAL::isa
2362 # Returns true for $X->isa($Y) iff $Y is $X or is an ancestor of $X.
2364 return unless ref($_[0]) && ref($_[1]);
2365 return scalar(grep {$_ eq $_[1]} &Class::Classless::X::ISA_TREE($_[0]));
2368 ###########################################################################
2370 sub nodelist { join ', ', map { "" . ($_->{'NAME'} || $_) . ""} @_ }
2372 ###########################################################################
2373 ###########################################################################
2374 ###########################################################################
2375 # Methods for the CALLSTATE class.
2376 # Basically, CALLSTATE objects represent the state of the dispatcher,
2377 # frozen at the moment when the method call was dispatched to the
2379 # In the grand scheme of things, this needn't be a class -- I could
2380 # have just made the callstate data-object be a hash with documented
2381 # keys, or a closure that responded to only certain parameters,
2382 # etc. But I like it this way. And I like being able to say simply
2384 # Yes, these are a bit cryptically written, but it's behoovy for
2385 # them to be very very efficient.
2387 @Class::Classless::ISA = ();
2388 sub Class::Classless::CALLSTATE::found_name { $_[0][0] }
2389 # the method name called and found
2390 sub Class::Classless::CALLSTATE::found_depth { $_[0][1] }
2391 # my depth in the lineage
2392 sub Class::Classless::CALLSTATE::lineage { @{$_[0][2]} }
2394 sub Class::Classless::CALLSTATE::target { $_[0][2][ 0 ] }
2395 # the object that's the target -- same as $_[0] for the method called
2396 sub Class::Classless::CALLSTATE::home { $_[0][2][ $_[0][1] ] }
2397 # the object I was found in
2398 sub Class::Classless::CALLSTATE::sub_found {
2399 $_[0][2][ $_[0][1] ]{'METHODS'}{ $_[0][0] }
2400 } # the routine called
2402 sub Class::Classless::CALLSTATE::no_fail { $_[0][3] }
2403 sub Class::Classless::CALLSTATE::set_no_fail_true { $_[0][3] = 1 }
2404 sub Class::Classless::CALLSTATE::set_fail_false { $_[0][3] = 0 }
2405 sub Class::Classless::CALLSTATE::set_fail_undef { $_[0][3] = undef }
2407 sub Class::Classless::CALLSTATE::via_next { $_[0][4] }
2409 sub Class::Classless::CALLSTATE::NEXT {
2410 #croak "NEXT needs at least one argument: \$cs->NEXT('method'...)"
2414 my $m = shift @_; # which may be (or come out) undef...
2415 $m = $cs->[0] unless defined $m; # the method name called and found
2418 bless( \$cs, 'Class::Classless::CALLSTATE::SHIMMY' ),
2423 ###########################################################################
2430 # {{{ *** C h a n g e l o g ***
2433 # - add screen support (from nicklist.pl)
2434 # - rename to adv_windowlist.pl (advanced window list) since it isn't just a
2435 # window list status bar (wlstat) anymore
2436 # - names can now have a max length and window names can be used
2437 # - fixed a bug with block display in screen mode and statusbar mode
2438 # - added space handling to ir_fe and removed it again
2439 # - now handling formats on my own
2440 # - added warning about missing sb_act_none abstract leading to
2441 # - display*active settings
2442 # - added warning about the bug in awl_display_(no)key_active settings
2445 # - add setting to also hide the last statusbar if empty (awl_all_disable)
2446 # - reverted to old utf8 code to also calculate broken utf8 length correctly
2447 # - simplified dealing with statusbars in wlreset
2448 # - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9
2449 # - fixed bug in handling channel #$$
2450 # - typo on line 200 spotted by f0rked
2451 # - reset background colour at the beginning of an entry
2454 # - fixed order of disabling statusbars
2455 # - several attempts at special chars, without any real success
2456 # and much more weird new bugs caused by this
2457 # - setting to specify sort order
2458 # - reduced timeout values
2459 # - added awl_hide_data for Geert Hauwaerts ( geert@irssi.org ) :)
2460 # - make it so the dynamic sub is actually deleted
2461 # - fix a bug with removing of the last separator
2462 # - take into consideration parse_special
2465 # - automatically kill old statusbars
2466 # - reset on /reload
2467 # - position/placement settings
2470 # - automated retrieval of key bindings (thanks grep.pl authors)
2471 # - improved removing of statusbars
2472 # - got rid of status chop
2475 # - rewritten to suit my needs
2476 # - based on chanact 0.5.5
2478 # vim: se fdm=marker tw=80 :