diff options
Diffstat (limited to 'fixery/adv_windowlist.pl')
-rw-r--r-- | fixery/adv_windowlist.pl | 2564 |
1 files changed, 0 insertions, 2564 deletions
diff --git a/fixery/adv_windowlist.pl b/fixery/adv_windowlist.pl deleted file mode 100644 index 4ca18fe..0000000 --- a/fixery/adv_windowlist.pl +++ /dev/null @@ -1,2564 +0,0 @@ -# {{{ debug - -#BEGIN { -# open STDERR, '>', '/home/ailin/wlstatwarnings'; -#}; - -# FIXME COULD SOMEONE PLEASE TELL ME HOW TO SHUT UP -# -# ... -# Variable "*" will not stay shared at (eval *) line *. -# Variable "*" will not stay shared at (eval *) line *. -# ... -# Can't locate package Irssi::Nick for @Irssi::Irc::Nick::ISA at (eval *) line *. -# ... -# -# THANKS - -# }}} - -# if you don't know how to operate folds, type zn - -# {{{ header - -use strict; -use warnings; - -use Irssi (); # which is the minimum required version of Irssi ? -use Irssi::TextUI; - -our $VERSION = '0.7'; -our %IRSSI = ( - original_authors => q(BC-bd, Veli, Timo Sirainen, ). - q(Wouter Coekaerts, Jean-Yves Lefort), # (decadix) - original_contact => q(bd@bc-bd.org, veli@piipiip.net, tss@iki.fi, ). - q(wouter@coekaerts.be, jylefort@brutele.be), - authors => q(Nei), - contact => q(Nei@QuakeNet), - url => "http://ai.onetrix.net/", - name => q(awl), - description => q(Adds a permanent advanced window list on the right or ). - q(in a statusbar.), - description2 => q(Based on chanact.pl which was apparently based on ). - q(lightbar.c and nicklist.pl with various other ideas ). - q(from random scripts.), - license => q(GNU GPLv2 or later), -); - -# }}} - -# {{{ *** D O C U M E N T A T I O N *** - -# adapted by Nei - -############### -# {{{ original comment -# ########### -# # Adds new powerful and customizable [Act: ...] item (chanelnames,modes,alias). -# # Lets you give alias characters to windows so that you can select those with -# # meta-<char>. -# # -# # for irssi 0.8.2 by bd@bc-bd.org -# # -# # inspired by chanlist.pl by 'cumol@hammerhart.de' -# # -# ######### -# # {{{ Contributors -# ######### -# # -# # veli@piipiip.net /window_alias code -# # qrczak@knm.org.pl chanact_abbreviate_names -# # qerub@home.se Extra chanact_show_mode and chanact_chop_status -# # }}} -# }}} -# -# {{{ FURTHER THANKS TO -# ############ -# # buu, fxn, Somni, Khisanth, integral, tybalt89 for much support in any aspect perl -# # and the channel in general ( #perl @ freenode ) and especially the ir_* functions -# # -# # Valentin 'senneth' Batz ( vb@g-23.org ) for the pointer to grep.pl, continuous support -# # and help in digging up ir_strip_codes -# # -# # OnetrixNET technology networks for the debian environment -# # -# # Monkey-Pirate.com / Spaceman Spiff for the webspace -# # -# }}} - -###### -# {{{ M A I N P R O B L E M -##### -# -# It is impossible to place the awl on a statusbar together with other items, -# because I do not know how to calculate the size that it is going to get -# granted, and therefore I cannot do the linebreaks properly. -# This is what is missing to make a nice script out of awl. -# If you have any ideas, please contact me ASAP :). -# }}} -###### - -###### -# {{{ UTF-8 PROBLEM -##### -# -# Please help me find a solution to this: -# this be your statusbar, it is using up the maximum term size -# [[1=1]#abc [2=2]#defghi] -# -# now consider this example:i -# "ascii" characters are marked with ., utf-8 characters with * -# [[1=1]#... [2=2]#...***] -# -# you should think that this is how it would be displayed? WRONG! -# [[1=1]#... [2=2]#...*** ] -# -# this is what Irssi does.. I believe my length calculating code to be correct, -# however, I'd love to be proven wrong (or receive any other fix, too, of -# course!) -# }}} -###### - -######### -# {{{ USAGE -### -# -# copy the script to ~/.irssi/scripts/ -# -# In irssi: -# -# /script load awl -# -# -# Hint: to get rid of the old [Act:] display -# /statusbar window remove act -# -# to get it back: -# /statusbar window add -after lag -priority 10 act -# }}} -########## -# {{{ OPTIONS -######## -# -# {{{ /set awl_display_nokey <string> -# /set awl_display_key <string> -# /set awl_display_nokey_active <string> -# /set awl_display_key_active <string> -# * string : Format String for one window. The following $'s are expanded: -# $C : Name -# $N : Number of the Window -# $Q : meta-Keymap -# $H : Start highlighting -# $S : Stop highlighting -# /+++++++++++++++++++++++++++++++++, -# | **** I M P O R T A N T : **** | -# | | -# | don't forget to use $S if you | -# | used $H before! | -# | | -# '+++++++++++++++++++++++++++++++++/ -# XXX NOTE ON *_active: there is a BUG somewhere in the length -# XXX calculation. currently it's best to NOT remove $H/$S from those -# XXX settings if you use it in the non-active settings. -# }}} -# {{{ /set awl_separator <string> -# * string : Charater to use between the channel entries -# you'll need to escape " " space and "$" like this: -# "/set awl_separator \ " -# "/set awl_separator \$" -# and {}% like this: -# "/set awl_separator %{" -# "/set awl_separator %}" -# "/set awl_separator %%" -# (reason being, that the separator is used inside a {format }) -# }}} -# {{{ /set awl_prefer_name <ON|OFF> -# * this setting decides whether awl will use the active_name (OFF) or the -# window name as the name/caption in awl_display_*. -# That way you can rename windows using /window name myownname. -# }}} -# {{{ /set awl_hide_data <num> -# * num : hide the window if its data_level is below num -# set it to 0 to basically disable this feature, -# 1 if you don't want windows without activity to be shown -# 2 to show only those windows with channel text or hilight -# 3 to show only windows with hilight -# }}} -# {{{ /set awl_maxlines <num> -# * num : number of lines to use for the window list (0 to disable, negative -# lock) -# }}} -# {{{ /set awl_columns <num> -# * num : number of columns to use in screen mode (0 for unlimited) -# }}} -# {{{ /set awl_block <num> -# * num : width of a column in screen mode (negative values = block display) -# /+++++++++++++++++++++++++++++++++, -# | ****** W A R N I N G ! ****** | -# | | -# | If your block display looks | -# | DISTORTED, you need to add the | -# | following line to your .theme | -# | file under | -# | abstracts = { : | -# | | -# | sb_act_none = "%n$*"; | -# | | -# '+++++++++++++++++++++++++++++++++/ -#.02:08:26. < shi> Irssi::current_theme()->get_format <.. can this be used? -# }}} -# {{{ /set awl_sbar_maxlength <ON|OFF> -# * if you enable the maxlength setting, the block width will be used as a -# maximum length for the non-block statusbar mode too. -# }}} -# {{{ /set awl_height_adjust <num> -# * num : how many lines to leave empty in screen mode -# }}} -# {{{ /set awl_sort <-data_level|-last_line|refnum> -# * you can change the window sort order with this variable -# -data_level : sort windows with hilight first -# -last_line : sort windows in order of activity -# refnum : sort windows by window number -# }}} -# {{{ /set awl_placement <top|bottom> -# /set awl_position <num> -# * these settings correspond to /statusbar because awl will create -# statusbars for you -# (see /help statusbar to learn more) -# }}} -# {{{ /set awl_all_disable <ON|OFF> -# * if you set awl_all_disable to ON, awl will also remove the -# last statusbar it created if it is empty. -# As you might guess, this only makes sense with awl_hide_data > 0 ;) -# }}} -# {{{ /set awl_automode <sbar|screen|emulate_lightbar> -# * this setting defines whether the window list is shown in statusbars or -# whether the screen hack is used (from nicklist.pl) -# }}} -# }}} -########## -# {{{ COMMANDS -######## -# {{{ /awl paste <ON|OFF|TOGGLE> -# * enables or disables the screen hack windowlist. This is useful when you -# want to mark & copy text that you want to paste somewhere (hence the -# name). (ON means AWL disabled!) -# This is nicely bound to a function key for example. -# }}} -# {{{ /awl redraw -# * redraws the screen hack windowlist. There are many occasions where the -# screen hack windowlist can get destroyed so you can use this command to -# fix it. -# }}} -# }}} -### -# {{{ WISHES -#### -# -# if you fiddle with my mess, provide me with your fixes so I can benefit as well -# -# Nei =^.^= ( QuakeNet accountname: ailin ) -# }}} - -# }}} - -# {{{ modules - -#use Class::Classless; -#use Term::Info; - -# }}} - -# {{{ global variables - -my $replaces = '[=]'; # AARGH!!! (chars that are always surrounded by weird - # colour codes by Irssi) - -my $actString = []; # statusbar texts -my $currentLines = 0; -my $resetNeeded; # layout/screen has changed, redo everything -my $needRemake; # "normal" changes -#my $callcount = 0; -sub GLOB_QUEUE_TIMER () { 100 } -my $globTime = undef; # timer to limit remake() calls - -my $EXTRA_HILIGHTS_STR = ''; -my $EXTRA_HILIGHTS = {}; - -my $SCREEN_MODE; -my $DISABLE_SCREEN_TEMP; -my $currentColumns = 0; -my $screenResizing; -my ($screenHeight, $screenWidth); -my $screenansi = bless { - NAME => 'Screen::ANSI', - PARENTS => [], - METHODS => { - dcs => sub { "\033P" }, - st => sub { "\033\\"}, - } -}, 'Class::Classless::X'; -#my $terminfo = new Term::Info 'xterm'; # xterm here, make this modular -# {{{{{{{{{{{{{{{ -my $terminfo = bless { # xterm here, make this modular - NAME => 'Term::Info::xterm', - PARENTS => [], - METHODS => { - # civis=\E[?25l, - civis => sub { "\033[?25l" }, - # sc=\E7, - sc => sub { "\0337" }, - # cup=\E[%i%p1%d;%p2%dH, - cup => sub { shift;shift; "\033[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' }, - # el=\E[K, - el => sub { "\033[K" }, - # rc=\E8, - rc => sub { "\0338" }, - # cnorm=\E[?25h, - cnorm => sub { "\033[?25h" }, - # setab=\E[4%p1%dm, - setab => sub { shift;shift; "\033[4" . $_[0] . 'm' }, - # setaf=\E[3%p1%dm, - setaf => sub { shift;shift; "\033[3" . $_[0] . 'm' }, - # bold=\E[1m, - bold => sub { "\033[1m" }, - # blink=\E[5m, - blink => sub { "\033[5m" }, - # rev=\E[7m, - rev => sub { "\033[7m" }, - # op=\E[39;49m, - op => sub { "\033[39;49m" }, - } -}, 'Class::Classless::X'; -# }}}}}}}}}}}}}}} - - -sub setc () { - $IRSSI{name} -} - -sub set ($) { - setc . '_' . shift -} - -# }}} - - -# {{{ sbar mode - -my %statusbars; # currently active statusbars - -# maybe I should just tie the array ? -sub add_statusbar { - for (@_) { - # add subs - for my $l ($_) { { - no strict 'refs'; # :P - *{set$l} = sub { awl($l, @_) }; - }; } - Irssi::command('statusbar ' . (set$_) . ' reset'); - Irssi::command('statusbar ' . (set$_) . ' enable'); - if (lc Irssi::settings_get_str(set 'placement') eq 'top') { - Irssi::command('statusbar ' . (set$_) . ' placement top'); - } - if ((my $x = int Irssi::settings_get_int(set 'position')) != 0) { - Irssi::command('statusbar ' . (set$_) . ' position ' . $x); - } - Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment left barstart'); - Irssi::command('statusbar ' . (set$_) . ' add ' . (set$_)); - Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment right barend'); - Irssi::command('statusbar ' . (set$_) . ' disable'); - Irssi::statusbar_item_register(set$_, '$0', set$_); - $statusbars{$_} = {}; - } -} - -sub remove_statusbar { - for (@_) { - Irssi::command('statusbar ' . (set$_) . ' reset'); - Irssi::statusbar_item_unregister(set$_); # XXX does this actually work ? - # DO NOT REMOVE the sub before you have unregistered it :)) - for my $l ($_) { { - no strict 'refs'; - undef &{set$l}; - }; } - delete $statusbars{$_}; - } -} - -sub syncLines { - my $temp = $currentLines; - $currentLines = @$actString; - #Irssi::print("current lines: $temp new lines: $currentLines"); - my $currMaxLines = Irssi::settings_get_int(set 'maxlines'); - if ($currMaxLines > 0 and @$actString > $currMaxLines) { - $currentLines = $currMaxLines; - } - elsif ($currMaxLines < 0) { - $currentLines = abs($currMaxLines); - } - return if ($temp == $currentLines); - if ($currentLines > $temp) { - for ($temp .. ($currentLines - 1)) { - add_statusbar($_); - Irssi::command('statusbar ' . (set$_) . ' enable'); - } - } - else { - for ($_ = ($temp - 1); $_ >= $currentLines; $_--) { - Irssi::command('statusbar ' . (set$_) . ' disable'); - remove_statusbar($_); - } - } -} - -# FIXME implement $get_size_only check, and user $item->{min|max-size} ?? -sub awl { - my ($line, $item, $get_size_only) = @_; - - if ($needRemake) { - $needRemake = undef; - remake(); - } - - my $text = $actString->[$line]; # DO NOT set the actual $actString->[$line] to '' here or - $text = '' unless defined $text; # you'll screw up the statusbar counter ($currentLines) - $item->default_handler($get_size_only, $text, '', 1); -} - -# remove old statusbars -my %killBar; -sub get_old_status { - my ($textDest, $cont, $cont_stripped) = @_; - if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq '' - and !defined($textDest->{'server'}) - ) { - my $name = quotemeta(set ''); - if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = {}; } - Irssi::signal_stop(); - } -} -sub killOldStatus { - %killBar = (); - Irssi::signal_add_first('print text' => 'get_old_status'); - Irssi::command('statusbar'); - Irssi::signal_remove('print text' => 'get_old_status'); - remove_statusbar(keys %killBar); -} -#killOldStatus(); - -# end sbar mode }}} - - -# {{{ keymaps - -my %keymap; - -sub get_keymap { - my ($textDest, undef, $stripped_text) = @_; - #if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq '') { - if ($textDest->{level} == Irssi::MSGLEVEL_CLIENTCRAP and - $textDest->{target} eq '') { - if (not defined($textDest->{server})) { - - my $bind = $stripped_text; - if ($bind =~ m/((?:meta-)+)(.)\s+change_window (\d+)/) { - - my ($level, $key, $window) = ($1, $2, $3); - my $numlevel = ($level =~ y/-//) - 1; - - $keymap{$window} = ('-' x $numlevel) . "$key"; - - } elsif ($bind =~ m/((?:meta-)+)(.)\s+window goto (\d+|(?:#\w+))/) { - # TODO: make this work moar better - my ($level, $key, $window) = ($1, $2, $3); - my $numlevel = ($level =~ y/-//) - 1; - - $keymap{$window} = ('-' x $numlevel) . "$key"; - } - - Irssi::signal_stop(); - } - - } -} - -sub update_keymap { - %keymap = (); - Irssi::signal_remove('command bind' => 'watch_keymap'); - Irssi::signal_add_first('print text' => 'get_keymap'); - Irssi::command('bind'); # stolen from grep - Irssi::signal_remove('print text' => 'get_keymap'); - Irssi::signal_add('command bind' => 'watch_keymap'); - Irssi::timeout_add_once(100, 'eventChanged', undef); -} - -# watch keymap changes -sub watch_keymap { - Irssi::timeout_add_once(1000, 'update_keymap', undef); -} - -update_keymap(); - -# end keymaps }}} - -# {{{ format handling - -# a bad way do do expansions but who cares -sub expand { - my ($string, %format) = @_; - my ($exp, $repl); - $string =~ s/\$$exp/$repl/g while (($exp, $repl) = each(%format)); - return $string; -} - -my %strip_table = ( - # fe-common::core::formats.c:format_expand_styles - # delete format_backs format_fores bold_fores other stuff - (map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8:|FnN>#[')), - # escape - (map { $_ => $_ } (split //, '{}%')), -); -sub ir_strip_codes { # strip %codes - my $o = shift; - $o =~ s/(%(.))/exists $strip_table{$2} ? $strip_table{$2} : $1/gex; - $o -} - -sub ir_parse_special { - my $o; my $i = shift; - #if ($_[0]) { # for the future?!? - # eval { - # $o = $_[0]->parse_special($i); - # }; - # unless ($@) { - # return $o; - # } - #} - my $win = shift || Irssi::active_win(); - my $server = Irssi::active_server(); - if (ref $win and ref $win->{'active'}) { - $o = $win->{'active'}->parse_special($i); - } - elsif (ref $win and ref $win->{'active_server'}) { - $o = $win->{'active_server'}->parse_special($i); - } - elsif (ref $server) { - $o = $server->parse_special($i); - } - else { - $o = Irssi::parse_special($i); - } - $o -} -sub ir_parse_special_protected { - my $o; my $i = shift; - $i =~ s/ - ( \\. ) | # skip over escapes (maybe) - ( \$[^% $\]+ ) # catch special variables - / - if ($1) { $1 } - elsif ($2) { my $i2 = $2; ir_fe(ir_parse_special($i2, @_)) } - else { $& } - /gex; - $i -} - - -sub sb_ctfe { # Irssi::current_theme->format_expand wrapper - Irssi::current_theme->format_expand( - shift, - ( - Irssi::EXPAND_FLAG_IGNORE_REPLACES - | - ($_[0]?0:Irssi::EXPAND_FLAG_IGNORE_EMPTY) - ) - ) -} -sub sb_expand { # expand {format }s (and apply parse_special for $vars) - ir_parse_special( - sb_ctfe(shift) - ) -} -sub sb_strip { - ir_strip_codes( - sb_expand(shift) - ); # does this get us the actual length of that s*ty bar :P ? -} -sub sb_length { - # unicode cludge, d*mn broken Irssi - # screw it, this will fail from broken joining anyway (and cause warnings) - my $term_type = 'term_type'; - if (Irssi::version > 20040819) { # this is probably wrong, but I don't know - # when the setting name got changed - $term_type = 'term_charset'; - } - #if (lc Irssi::settings_get_str($term_type) eq '8bit' - # or Irssi::settings_get_str($term_type) =~ /^iso/i - #) { - # length(sb_strip(shift)) - #} - #else { - my $temp = sb_strip(shift); - # try to get the displayed width - my $length; - eval { - require Text::CharWidth; - $length = Text::CharWidth::mbswidth($temp); - }; - unless ($@) { - return $length; - } - else { - if (lc Irssi::settings_get_str($term_type) eq 'utf-8') { - # try to switch on utf8 - eval { - no warnings; - require Encode; - #$temp = Encode::decode_utf8($temp); # thanks for the hint, but I have my - # # reasons for _utf8_on - Encode::_utf8_on($temp); - }; - } - # there is nothing more I can do - length($temp) - } - #} -} - -# !!! G*DD*MN Irssi is adding an additional layer of backslashitis per { } layer -# !!! AND I still don't know what I need to escape. -# !!! and NOONE else seems to know or care either. -# !!! f*ck open source. I mean it. -# XXX any Irssi::print debug statement leads to SEGFAULT - why ? - -# major parts of the idea by buu (#perl @ freenode) -# thanks to fxn and Somni for debugging -# while ($_[0] =~ /(.)/g) { -# my $c = $1; # XXX sooo... goto kills $1 -# if ($q eq '%') { goto ESC; } - -## <freenode:#perl:tybalt89> s/%(.)|(\{)|(\})|(\\|\$)/$1?$1:$2?($level++,$2):$3?($level>$min_level&&$level--,$3):'\\'x(2**$level-1).$4/ge; # untested... -sub ir_escape { - my $min_level = $_[1] || 0; my $level = $min_level; - my $o = shift; - $o =~ s/ - ( %. ) | # $1 - ( \{ ) | # $2 - ( \} ) | # $3 - ( \\ ) | # $4 - ( \$(?=[^\\]) ) | # $5 - ( \$ ) # $6 - / - if ($1) { $1 } # %. escape - elsif ($2) { $level++; $2 } # { nesting start - elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end - elsif ($4) { '\\'x(2**$level) } # \ needs \\escaping - elsif ($5) { '\\'x(2**$level-1) . '$' . '\\'x(2**$level-1) } # and $ needs even more because of "parse_special" - else { '\\'x(2**$level-1) . '$' } # $ needs \$ escaping - /gex; - $o -} -#sub ir_escape { -# my $min_level = $_[1] || 0; my $level = $min_level; -# my $o = shift; -# $o =~ s/ -# ( %. ) | # $1 -# ( \{ ) | # $2 -# ( \} ) | # $3 -# ( \\ | \$ ) # $4 -# / -# if ($1) { $1 } # %. escape -# elsif ($2) { $level++; $2 } # { nesting start -# elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end -# else { '\\'x(2**($level-1)-1) . $4 } # \ or $ needs \\escaping -# /gex; -# $o -#} - -sub ir_fe { # try to fix format stuff - my $x = shift; - # XXX why do I have to use two/four % here instead of one/two ?? - # answer: you screwed up in ir_escape - $x =~ s/([%{}])/%$1/g; - #$x =~ s/(\\|\$|[ ])/\\$1/g; # XXX HOW CAN I HANDLE THE SPACES CORRECTLY XXX - $x =~ s/(\\|\$)/\\$1/g; - #$x =~ s/(\$(?=.))|(\$)/$1?"\\\$\\":"\\\$"/ge; # I think this should be here - # # (logic), but it doesn't work - # # that way :P - #$x =~ s/\\/\\\\/g; # that's right, escape escapes - $x -} -sub ir_ve { # escapes special vars but leave colours alone - my $x = shift; - #$x =~ s/([%{}])/%$1/g; - $x =~ s/(\\|\$|[ ])/\\$1/g; - $x -} - -my %ansi_table; -{ - my ($i, $j, $k) = (0, 0, 0); - %ansi_table = ( - # fe-common::core::formats.c:format_expand_styles - # do format_backs - (map { $_ => $terminfo->setab($i++) } (split //, '01234567' )), - # do format_fores - (map { $_ => $terminfo->setaf($j++) } (split //, 'krgybmcw' )), - # do bold_fores - (map { $_ => $terminfo->bold() . - $terminfo->setaf($k++) } (split //, 'KRGYBMCW')), - # reset - #(map { $_ => $terminfo->op() } (split //, 'nN')), - (map { $_ => $terminfo->op() } (split //, 'n')), - (map { $_ => "\033[0m" } (split //, 'N')), # XXX quick and DIRTY - # flash/bright - F => $terminfo->blink(), - # reverse - 8 => $terminfo->rev(), - # bold - (map { $_ => $terminfo->bold() } (split //, '9_')), - # delete other stuff - (map { $_ => '' } (split //, ':|>#[')), - # escape - (map { $_ => $_ } (split //, '{}%')), - ) -} -sub formats_to_ansi_basic { - my $o = shift; - $o =~ s/(%(.))/exists $ansi_table{$2} ? $ansi_table{$2} : $1/gex; - $o -} - -sub lc1459 ($) { my $x = shift; $x =~ y/A-Z][\^/a-z}{|~/; $x } -Irssi::settings_add_str(setc, 'banned_channels', ''); -Irssi::settings_add_bool(setc, 'banned_channels_on', 0); -my %banned_channels = map { lc1459($_) => undef } -split ' ', Irssi::settings_get_str('banned_channels'); -Irssi::settings_add_str(setc, 'fancy_abbrev', 'fancy'); - -sub _error { - my $msg = join '', @_; - Irssi::print(setc . ':', Irssi::MSGLEVEL_CLIENTERROR); -} - -# }}} - -# {{{ extra channel hilights - -sub rereadChanHilights { - my $hilight_str = Irssi::settings_get_str(set 'extra_hilights'); - - # only update if changed. - return if $hilight_str eq $EXTRA_HILIGHTS_STR; - - $EXTRA_HILIGHTS_STR = $hilight_str - - return unless length $hilight_str; - - # kill all existing entries - $EXTRA_HILIGHTS = {}; - - my @entries = split /\s+/, $hilight_str; - - my $pattern = qr|^ (?:([^/]*)/) # $1: optional '$tag/' capturing $tag - ([^:]+): # $2: required '$target:', capturing $target - (%.+) # $3: required '%$colour' - $|xo; - - foreach my $entry (@entries) { - - if ($entry =~ $pattern) { - my ($tag, $target, $colour) = ($1, $2, $3); - my $components = ; - - my $target_match_str = $target; - if (length $tag) { - $target_match_str = $tag . '/' . $target_match_str; - } - - $EXTRA_HILIGHTS->{$target_match_str} - = { tag => $tag, target => $target, colour => $colour }; - - } else { - _error("malformed hilight entry: '$entry'"); - } - } -} - -sub matchExtraHilight { - my ($target, $tag) = @_; - my $match = $target; - if (length $tag) { - $match = $tag . '/' . $tag; - } - if (exists $EXTRA_HILIGHTS->{$match}) { - return $EXTRA_HILIGHTS->{$match}->{colour}; - } else { - return ''; - } -} - -# }}} - -# {{{ -# {{{ main - -sub remake () { - #$callcount++; - #my $xx = $callcount; Irssi::print("starting remake [ $xx ]"); - my ($hilight, $number, $display); - my $separator = '{sb_act_sep ' . Irssi::settings_get_str(set 'separator') . - '}'; - my $custSort = Irssi::settings_get_str(set 'sort'); - my $custSortDir = 1; - if ($custSort =~ /^[-!](.*)/) { - $custSortDir = -1; - $custSort = $1; - } - - my @wins = - sort { - ( - ( (int($a->{$custSort}) <=> int($b->{$custSort})) * $custSortDir ) - || - ($a->{'refnum'} <=> $b->{'refnum'}) - ) - } Irssi::windows; - my $block = Irssi::settings_get_int(set 'block'); - my $columns = $currentColumns; - my $oldActString = $actString if $SCREEN_MODE; - $actString = $SCREEN_MODE ? [' A W L'] : []; - my $line = $SCREEN_MODE ? 1 : 0; - my $width = $SCREEN_MODE - ? - $screenWidth - abs($block)*$columns + 1 - : - ([Irssi::windows]->[0]{'width'} - sb_length('{sb x}')); - my $height = $screenHeight - abs(Irssi::settings_get_int(set - 'height_adjust')); - my ($numPad, $keyPad) = (0, 0); - my %abbrevList; - if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength') - or ($block < 0) - ) { - %abbrevList = (); - if (Irssi::settings_get_str('fancy_abbrev') !~ /^(no|off|head)/i) { - my @nameList = map { ref $_ ? $_->get_active_name : '' } @wins; - for (my $i = 0; $i < @nameList - 1; ++$i) { - my ($x, $y) = ($nameList[$i], $nameList[$i + 1]); - for ($x, $y) { s/^[+#!=]// } - my $res = Algorithm::LCSS::LCSS($x, $y); - if (defined $res) { - #Irssi::print("common pattern $x $y : $res"); - #Irssi::print("found at $nameList[$i] ".index($nameList[$i], - # $res)); - $abbrevList{$nameList[$i]} = int (index($nameList[$i], $res) + - (length($res) / 2)); - #Irssi::print("found at ".$nameList[$i+1]." ".index($nameList[$i+1], - # $res)); - $abbrevList{$nameList[$i+1]} = int (index($nameList[$i+1], $res) + - (length($res) / 2)); - } - } - } - if ($SCREEN_MODE or ($block < 0)) { - $numPad = length((sort { length($b) <=> length($a) } keys %keymap)[0]); - $keyPad = length((sort { length($b) <=> length($a) } values %keymap)[0]); - } - } - if ($SCREEN_MODE) { - print STDERR $screenansi->dcs(). - $terminfo->civis(). - $terminfo->sc(). - $screenansi->st(); - if (@$oldActString < 1) { - print STDERR $screenansi->dcs(). - $terminfo->cup(0, $width). - $actString->[0]. - $terminfo->el(). - $screenansi->st(); - } - } - foreach my $win (@wins) { - unless ($SCREEN_MODE) { - $actString->[$line] = '' unless defined $actString->[$line] - or Irssi::settings_get_bool(set 'all_disable'); - } - - # all stolen from chanact, what does this code do and why do we need it ? - !ref($win) && next; - - my $name = $win->get_active_name; - $name = '*' if (Irssi::settings_get_bool('banned_channels_on') and exists - $banned_channels{lc1459($name)}); - $name = $win->{'name'} if $name ne '*' and $win->{'name'} ne '' - and Irssi::settings_get_bool(set 'prefer_name'); - my $active = $win->{'active'}; - my $colour = $win->{'hilight_color'}; - if (!defined $colour) { $colour = ''; } - - if ($win->{'data_level'} < Irssi::settings_get_int(set 'hide_data')) { - next; } # for Geert - if ($win->{'data_level'} == 0) { $hilight = '{sb_act_none '; } - elsif ($win->{'data_level'} == 1) { $hilight = '{sb_act_text '; } - elsif ($win->{'data_level'} == 2) { $hilight = '{sb_act_msg '; } - elsif ($colour ne '') { $hilight = "{sb_act_hilight_color $colour "; } - elsif ($win->{'data_level'} == 3) { $hilight = '{sb_act_hilight '; } - else { $hilight = '{sb_act_special '; } - - $number = $win->{'refnum'}; - my @display = ('display_nokey'); - if (defined $keymap{$number} and $keymap{$number} ne '') { - unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display; - } - if (Irssi::active_win->{'refnum'} == $number) { - unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy } @display; - } - #Irssi::print("win $number [@display]: " . join '.', split //, join '<<', map { - # Irssi::settings_get_str(set $_) } @display); - $display = (grep { $_ } - map { Irssi::settings_get_str(set $_) } - @display)[0]; - #Irssi::print("win $number : " . join '.', split //, $display); - - if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength') - or ($block < 0) - ) { - my $baseLength = sb_length(ir_escape(ir_ve(ir_parse_special_protected(sb_ctfe( - '{sb_background}' . expand($display, - C => ir_fe('x'), - N => $number . (' 'x($numPad - length($number))), - Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}), - H => $hilight, - S => '}{sb_background}' - ), 1), $win)))) - 1; - my $diff = abs($block) - (length($name) + $baseLength); - if ($diff < 0) { # too long - if (abs($diff) >= length($name)) { $name = '' } # forget it - elsif (abs($diff) + 1 >= length($name)) { $name = substr($name, - 0, 1); } - else { - my $middle = exists $abbrevList{$name} ? - (($abbrevList{$name} + (2*(length($name) / 2)))/3) : - ((Irssi::settings_get_str('fancy_abbrev') =~ /^head/i) ? - length($name) : - (length($name) / 2)); - my $cut = int($middle - (abs($diff) / 2) + .55); - $cut = 1 if $cut < 1; - $cut = length($name) - abs($diff) - 1 if $cut > (length($name) - - abs($diff) - 1); - $name = substr($name, 0, $cut) . '~' . substr($name, $cut + - abs($diff) + 1); - } - } - elsif ($SCREEN_MODE or ($block < 0)) { - $name .= (' ' x $diff); - } - } - - my $add = ir_ve(ir_parse_special_protected(sb_ctfe('{sb_background}' . expand($display, - C => ir_fe($name), - N => $number . (' 'x($numPad - length($number))), - Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}), - H => $hilight, - S => '}{sb_background}' - ), 1), $win)); - if ($SCREEN_MODE) { - $actString->[$line] = $add; - if ((!defined $oldActString->[$line] - or $oldActString->[$line] ne $actString->[$line]) - and - $line <= ($columns * $height) - ) { - print STDERR $screenansi->dcs(). - $terminfo->cup(($line-1) % $height+1, $width + ( - abs($block) * int(($line-1) / $height))). - formats_to_ansi_basic(sb_expand(ir_escape($actString->[$line]))). - #$terminfo->el(). - $screenansi->st(); - } - $line++; - } - else { - #$temp =~ s/\{\S+?(?:\s(.*?))?\}/$1/g; - #$temp =~ s/\\\\\\\\/\\/g; # XXX I'm actually guessing here, someone point me - # # XXX to docs please - $actString->[$line] = '' unless defined $actString->[$line]; - - # XXX how can I check whether the content still fits in the bar? this would - # XXX allow awlstatus to reside on a statusbar together with other items... - if (sb_length(ir_escape($actString->[$line] . $add)) >= $width) { - # XXX doesn't correctly handle utf-8 multibyte ... help !!? - $actString->[$line] .= ' ' x ($width - sb_length(ir_escape( - $actString->[$line]))); - $line++; - } - $actString->[$line] .= $add . $separator; - # XXX if I use these prints, output layout gets screwed up... why ? - #Irssi::print("line $line: ".$actString->[$line]); - #Irssi::print("temp $line: ".$temp); - } - } - - if ($SCREEN_MODE) { - while ($line <= ($columns * $height)) { - print STDERR $screenansi->dcs(). - $terminfo->cup(($line-1) % $height+1, $width + ( - abs($block) * int(($line-1) / $height))). - $terminfo->el(). - $screenansi->st(); - $line++; - } - print STDERR $screenansi->dcs(). - $terminfo->rc(). - $terminfo->cnorm(). - $screenansi->st(); - } - else { - # XXX the Irssi::print statements lead to the MOST WEIRD results - # e.g.: the loop gets executed TWICE for p > 0 ?!? - for (my $p = 0; $p < @$actString; $p++) { # wrap each line in {sb }, escape it - my $x = $actString->[$p]; # properly, etc. - $x =~ s/\Q$separator\E([ ]*)$/$1/; - #Irssi::print("[$p]".'current:'.join'.',split//,sb_strip(ir_escape($x,0))); - #Irssi::print("assumed length before:".sb_length(ir_escape($x,0))); - $x = "{sb $x}"; - #Irssi::print("[$p]".'new:'.join'.',split//,sb_expand(ir_escape($x,0))); - #Irssi::print("[$p]".'new:'.join'.',split//,ir_escape($x,0)); - #Irssi::print("assumed length after:".sb_length(ir_escape($x,0))); - $x = ir_escape($x); - #Irssi::print("[$p]".'REALnew:'.join'.',split//,sb_strip($x)); - $actString->[$p] = $x; - # XXX any Irssi::print debug statement leads to SEGFAULT (sometimes) - why ? - } - } - #Irssi::print("remake [ $xx ] finished"); -} - -sub awlHasChanged () { - $globTime = undef; - - rereadChanHilights(); - - my $temp = ($SCREEN_MODE ? - "\\\n" . Irssi::settings_get_int(set 'block'). - Irssi::settings_get_int(set 'height_adjust') - : "!\n" . Irssi::settings_get_str(set 'placement'). - Irssi::settings_get_int(set 'position')). - Irssi::settings_get_str(set 'automode'); - if ($temp ne $resetNeeded) { wlreset(); return; } - #Irssi::print("awl has changed, calls to remake so far: $callcount"); - $needRemake = 1; - - #remake(); - if ( - ($SCREEN_MODE and !$DISABLE_SCREEN_TEMP) - or - ($needRemake and Irssi::settings_get_bool(set 'all_disable')) - or - (!Irssi::settings_get_bool(set 'all_disable') and $currentLines < 1) - ) { - $needRemake = undef; - remake(); - } - - unless ($SCREEN_MODE) { - # XXX Irssi crashes if I try to do this without timer, why ? What's the minimum - # XXX delay I need to use in the timer ? - Irssi::timeout_add_once(100, 'syncLines', undef); - - for (keys %statusbars) { - Irssi::statusbar_items_redraw(set$_); - } - } - else { - Irssi::timeout_add_once(100, 'syncColumns', undef); - } -} - -sub eventChanged () { # Implement a change queue/blocker -.-) - if (defined $globTime) { - Irssi::timeout_remove($globTime); - } # delay the update further - $globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awlHasChanged', undef); -} - -# }}} - - -# {{{ screen mode - -sub screenFullRedraw { - my ($window) = @_; - if (!ref $window or $window->{'refnum'} == Irssi::active_win->{'refnum'}) { - $actString = []; - eventChanged(); - } -} - -sub screenSize { # from nicklist.pl - $screenResizing = 1; - # fit screen - system 'screen -x '.$ENV{'STY'}.' -X fit'; - # get size - my ($row, $col) = split ' ', `stty size`; - # set screen width - $screenWidth = $col-1; - $screenHeight = $row-1; - - # on some recent systems, "screen -X fit; screen -X width -w 50" doesn't work, needs a sleep in between the 2 commands - # so we wait a second before setting the width - Irssi::timeout_add_once(100, sub { - my ($new_irssi_width) = @_; - $new_irssi_width -= abs(Irssi::settings_get_int(set - 'block'))*$currentColumns - 1; - system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $new_irssi_width; - # and then we wait another second for the resizing, and then redraw. - Irssi::timeout_add_once(10,sub {$screenResizing = 0; screenFullRedraw()}, []); - }, $screenWidth); -} - -sub screenOff { - my ($unloadMode) = @_; - Irssi::signal_remove('gui print text finished' => 'screenFullRedraw'); - Irssi::signal_remove('gui page scrolled' => 'screenFullRedraw'); - Irssi::signal_remove('window changed' => 'screenFullRedraw'); - Irssi::signal_remove('window changed automatic' => 'screenFullRedraw'); - if ($unloadMode) { - Irssi::signal_remove('terminal resized' => 'resizeTerm'); - } - system 'screen -x '.$ENV{'STY'}.' -X fit'; -} - -sub syncColumns { - return if (@$actString == 0); - my $temp = $currentColumns; - #Irssi::print("current columns $temp"); - my $height = $screenHeight - abs(Irssi::settings_get_int(set - 'height_adjust')); - $currentColumns = int(($#$actString-1) / $height) + 1; - #Irssi::print("objects in actstring:".scalar(@$actString).", screen height:". - # $height); - my $currMaxColumns = Irssi::settings_get_int(set 'columns'); - if ($currMaxColumns > 0 and $currentColumns > $currMaxColumns) { - $currentColumns = $currMaxColumns; - } - elsif ($currMaxColumns < 0) { - $currentColumns = abs($currMaxColumns); - } - return if ($temp == $currentColumns); - screenSize(); -} - -#$needRemake = 1; -sub resizeTerm () { - if ($SCREEN_MODE and !$screenResizing) { - $screenResizing = 1; - Irssi::timeout_add_once(10, 'screenSize', undef); - } - Irssi::timeout_add_once(100, 'eventChanged', undef); -} - -# }}} - - -# {{{ settings add - -Irssi::settings_add_str(setc, set 'display_nokey', '[$N]$H$C$S'); -Irssi::settings_add_str(setc, set 'display_key', '[$Q=$N]$H$C$S'); -Irssi::settings_add_str(setc, set 'display_nokey_active', ''); -Irssi::settings_add_str(setc, set 'display_key_active', ''); -Irssi::settings_add_str(setc, set 'separator', "\\ "); -Irssi::settings_add_bool(setc, set 'prefer_name', 0); -Irssi::settings_add_int(setc, set 'hide_data', 0); -Irssi::settings_add_int(setc, set 'maxlines', 9); -Irssi::settings_add_int(setc, set 'columns', 1); -Irssi::settings_add_int(setc, set 'block', 20); -Irssi::settings_add_bool(setc, set 'sbar_maxlength', 0); -Irssi::settings_add_int(setc, set 'height_adjust', 2); -Irssi::settings_add_str(setc, set 'sort', 'refnum'); -Irssi::settings_add_str(setc, set 'placement', 'bottom'); -Irssi::settings_add_int(setc, set 'position', 0); -Irssi::settings_add_bool(setc, set 'all_disable', 0); -Irssi::settings_add_str(setc, set 'automode', 'sbar'); - -# format is '[$tag/]#channel:%colour1 [$tag/]#channel:%colour2 ...' list. -Irssi::settings_add_str(setc, set 'extra_hilights', ''); - -# }}} - - -# {{{ init - -sub wlreset { - $actString = []; - $currentLines = 0; # 1; # mhmmmm .. we actually enable one line down there so - # let's try this. - #update_keymap(); - killOldStatus(); - # Register statusbar - #add_statusbar(0); - #Irssi::command('statusbar wl0 enable'); - my $was_screen_mode = $SCREEN_MODE; - if ($SCREEN_MODE = (Irssi::settings_get_str(set 'automode') =~ /screen/i) - and - !$was_screen_mode - ) { - if (!defined $ENV{'STY'}) { - Irssi::print('Screen mode can only be used in GNU screen but no '. - 'screen was found.', MSGLEVEL_CLIENTERROR); - $SCREEN_MODE = undef; - } - else { - Irssi::signal_add_last('gui print text finished' => 'screenFullRedraw'); - Irssi::signal_add_last('gui page scrolled' => 'screenFullRedraw'); - Irssi::signal_add('window changed' => 'screenFullRedraw'); - Irssi::signal_add('window changed automatic' => 'screenFullRedraw'); - } - } - elsif ($was_screen_mode and !$SCREEN_MODE) { - screenOff(); - } - $resetNeeded = ($SCREEN_MODE ? - "\\\n" . Irssi::settings_get_int(set 'block'). - Irssi::settings_get_int(set 'height_adjust') - : "!\n" . Irssi::settings_get_str(set 'placement'). - Irssi::settings_get_int(set 'position')). - Irssi::settings_get_str(set 'automode'); - resizeTerm(); -} - -wlreset(); - -# }}} - - -# {{{ unload/deinit - -my $Unload; -sub unload ($$$) { - $Unload = 1; - # pretend we didn't do anything ASAP - Irssi::timeout_add_once(10, sub { $Unload = undef; }, undef); -} -# last try to catch a sigsegv -Irssi::signal_add_first('gui exit' => sub { $Unload = undef; }); -sub UNLOAD { - # this might well crash Irssi... try /eval /script unload someotherscript ; - # /quit (= SEGFAULT !) - if ($Unload) { - $actString = ['']; # syncLines(); # XXX Irssi crashes when trying to disable - killOldStatus(); # XXX all statusbars ? - if ($SCREEN_MODE) { - screenOff('unload mode'); - } - } -} - -# }}} - - -# {{{ signals - -sub addPrintTextHook { # update on print text - return if $_[0]->{'level'} == 262144 and $_[0]->{'target'} eq '' - and !defined($_[0]->{'server'}); - if (Irssi::settings_get_str(set 'sort') =~ /^[-!]?last_line$/) { - Irssi::timeout_add_once(100, 'eventChanged', undef); - } -} - -#sub _x { my ($x, $y) = @_; ($x, sub { Irssi::print('-->signal '.$x); eval "$y();"; }) } -#sub _x { @_ } -Irssi::signal_add_first( - 'command script unload' => 'unload' -); -Irssi::signal_add_last({ - 'setup changed' => 'eventChanged', - 'print text' => 'addPrintTextHook', - 'terminal resized' => 'resizeTerm', - 'setup reread' => 'wlreset', - 'window hilight' => 'eventChanged', -}); -Irssi::signal_add({ - 'window created' => 'eventChanged', - 'window destroyed' => 'eventChanged', - 'window name changed' => 'eventChanged', - 'window refnum changed' => 'eventChanged', - 'window changed' => 'eventChanged', - 'window changed automatic' => 'eventChanged', -}); - -#Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # relicts - -# }}} - -# {{{ commands - - -sub runsub { - my ($cmd) = @_; - sub { - my ($data, $server, $item) = @_; - Irssi::command_runsub($cmd, $data, $server, $item); - }; -} -Irssi::command_bind( setc() => runsub(setc()) ); -Irssi::command_bind( setc() . ' paste' => runsub(setc() . ' paste') ); -Irssi::command_bind( - setc() . ' paste on' => sub { - return unless $SCREEN_MODE; - my $was_disabled = $DISABLE_SCREEN_TEMP; - $DISABLE_SCREEN_TEMP = 1; - Irssi::print('Paste mode is now ON, '.uc(setc()).' is temporarily '. - 'disabled.'); - if (!$was_disabled) { - $screenResizing = 1; - screenOff(); - } - } -); -Irssi::command_bind( - setc() . ' paste off' => sub { - return unless $SCREEN_MODE; - my $was_disabled = $DISABLE_SCREEN_TEMP; - $DISABLE_SCREEN_TEMP = undef; - Irssi::print('Paste mode is now OFF, '.uc(setc()).' is enabled.'); - if ($was_disabled) { - $SCREEN_MODE = undef; - $screenResizing = 0; - wlreset(); - } - } -); -Irssi::command_bind( - setc() . ' paste toggle' => sub { - if ($DISABLE_SCREEN_TEMP) { - Irssi::command(setc() . ' paste off'); - } - else { - Irssi::command(setc() . ' paste on'); - } - } -); -Irssi::command_bind( - setc() . ' redraw' => sub { - return unless $SCREEN_MODE; - screenFullRedraw(); - } -); - - -# }}} - -# {{{ Algorithm::LCSS module -{ - package Algorithm::Diff; - # Skip to first "=head" line for documentation. - use strict; - - use integer; # see below in _replaceNextLargerWith() for mod to make - # if you don't use this - - # McIlroy-Hunt diff algorithm - # Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com> - # by Ned Konz, perl@bike-nomad.com - # Updates by Tye McQueen, http://perlmonks.org/?node=tye - - # Create a hash that maps each element of $aCollection to the set of - # positions it occupies in $aCollection, restricted to the elements - # within the range of indexes specified by $start and $end. - # The fourth parameter is a subroutine reference that will be called to - # generate a string to use as a key. - # Additional parameters, if any, will be passed to this subroutine. - # - # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); - - sub _withPositionsOfInInterval - { - my $aCollection = shift; # array ref - my $start = shift; - my $end = shift; - my $keyGen = shift; - my %d; - my $index; - for ( $index = $start ; $index <= $end ; $index++ ) - { - my $element = $aCollection->[$index]; - my $key = &$keyGen( $element, @_ ); - if ( exists( $d{$key} ) ) - { - unshift ( @{ $d{$key} }, $index ); - } - else - { - $d{$key} = [$index]; - } - } - return wantarray ? %d : \%d; - } - - # Find the place at which aValue would normally be inserted into the - # array. If that place is already occupied by aValue, do nothing, and - # return undef. If the place does not exist (i.e., it is off the end of - # the array), add it to the end, otherwise replace the element at that - # point with aValue. It is assumed that the array's values are numeric. - # This is where the bulk (75%) of the time is spent in this module, so - # try to make it fast! - - sub _replaceNextLargerWith - { - my ( $array, $aValue, $high ) = @_; - $high ||= $#$array; - - # off the end? - if ( $high == -1 || $aValue > $array->[-1] ) - { - push ( @$array, $aValue ); - return $high + 1; - } - - # binary search for insertion point... - my $low = 0; - my $index; - my $found; - while ( $low <= $high ) - { - $index = ( $high + $low ) / 2; - - # $index = int(( $high + $low ) / 2); # without 'use integer' - $found = $array->[$index]; - - if ( $aValue == $found ) - { - return undef; - } - elsif ( $aValue > $found ) - { - $low = $index + 1; - } - else - { - $high = $index - 1; - } - } - - # now insertion point is in $low. - $array->[$low] = $aValue; # overwrite next larger - return $low; - } - - # This method computes the longest common subsequence in $a and $b. - - # Result is array or ref, whose contents is such that - # $a->[ $i ] == $b->[ $result[ $i ] ] - # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. - - # An additional argument may be passed; this is a hash or key generating - # function that should return a string that uniquely identifies the given - # element. It should be the case that if the key is the same, the elements - # will compare the same. If this parameter is undef or missing, the key - # will be the element as a string. - - # By default, comparisons will use "eq" and elements will be turned into keys - # using the default stringizing operator '""'. - - # Additional parameters, if any, will be passed to the key generation - # routine. - - sub _longestCommonSubsequence - { - my $a = shift; # array ref or hash ref - my $b = shift; # array ref or hash ref - my $counting = shift; # scalar - my $keyGen = shift; # code ref - my $compare; # code ref - - if ( ref($a) eq 'HASH' ) - { # prepared hash must be in $b - my $tmp = $b; - $b = $a; - $a = $tmp; - } - - # Check for bogus (non-ref) argument values - if ( !ref($a) || !ref($b) ) - { - my @callerInfo = caller(1); - die 'error: must pass array or hash references to ' . $callerInfo[3]; - } - - # set up code refs - # Note that these are optimized. - if ( !defined($keyGen) ) # optimize for strings - { - $keyGen = sub { $_[0] }; - $compare = sub { my ( $a, $b ) = @_; $a eq $b }; - } - else - { - $compare = sub { - my $a = shift; - my $b = shift; - &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); - }; - } - - my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] ); - my ( $prunedCount, $bMatches ) = ( 0, {} ); - - if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us? - { - $bMatches = $b; - } - else - { - my ( $bStart, $bFinish ) = ( 0, $#$b ); - - # First we prune off any common elements at the beginning - while ( $aStart <= $aFinish - and $bStart <= $bFinish - and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) - { - $matchVector->[ $aStart++ ] = $bStart++; - $prunedCount++; - } - - # now the end - while ( $aStart <= $aFinish - and $bStart <= $bFinish - and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) - { - $matchVector->[ $aFinish-- ] = $bFinish--; - $prunedCount++; - } - - # Now compute the equivalence classes of positions of elements - $bMatches = - _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); - } - my $thresh = []; - my $links = []; - - my ( $i, $ai, $j, $k ); - for ( $i = $aStart ; $i <= $aFinish ; $i++ ) - { - $ai = &$keyGen( $a->[$i], @_ ); - if ( exists( $bMatches->{$ai} ) ) - { - $k = 0; - for $j ( @{ $bMatches->{$ai} } ) - { - - # optimization: most of the time this will be true - if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) - { - $thresh->[$k] = $j; - } - else - { - $k = _replaceNextLargerWith( $thresh, $j, $k ); - } - - # oddly, it's faster to always test this (CPU cache?). - if ( defined($k) ) - { - $links->[$k] = - [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; - } - } - } - } - - if (@$thresh) - { - return $prunedCount + @$thresh if $counting; - for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) - { - $matchVector->[ $link->[1] ] = $link->[2]; - } - } - elsif ($counting) - { - return $prunedCount; - } - - return wantarray ? @$matchVector : $matchVector; - } - - sub traverse_sequences - { - my $a = shift; # array ref - my $b = shift; # array ref - my $callbacks = shift || {}; - my $keyGen = shift; - my $matchCallback = $callbacks->{'MATCH'} || sub { }; - my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; - my $finishedACallback = $callbacks->{'A_FINISHED'}; - my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; - my $finishedBCallback = $callbacks->{'B_FINISHED'}; - my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); - - # Process all the lines in @$matchVector - my $lastA = $#$a; - my $lastB = $#$b; - my $bi = 0; - my $ai; - - for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) - { - my $bLine = $matchVector->[$ai]; - if ( defined($bLine) ) # matched - { - &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; - &$matchCallback( $ai, $bi++, @_ ); - } - else - { - &$discardACallback( $ai, $bi, @_ ); - } - } - - # The last entry (if any) processed was a match. - # $ai and $bi point just past the last matching lines in their sequences. - - while ( $ai <= $lastA or $bi <= $lastB ) - { - - # last A? - if ( $ai == $lastA + 1 and $bi <= $lastB ) - { - if ( defined($finishedACallback) ) - { - &$finishedACallback( $lastA, @_ ); - $finishedACallback = undef; - } - else - { - &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; - } - } - - # last B? - if ( $bi == $lastB + 1 and $ai <= $lastA ) - { - if ( defined($finishedBCallback) ) - { - &$finishedBCallback( $lastB, @_ ); - $finishedBCallback = undef; - } - else - { - &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; - } - } - - &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; - &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; - } - - return 1; - } - - sub traverse_balanced - { - my $a = shift; # array ref - my $b = shift; # array ref - my $callbacks = shift || {}; - my $keyGen = shift; - my $matchCallback = $callbacks->{'MATCH'} || sub { }; - my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; - my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; - my $changeCallback = $callbacks->{'CHANGE'}; - my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); - - # Process all the lines in match vector - my $lastA = $#$a; - my $lastB = $#$b; - my $bi = 0; - my $ai = 0; - my $ma = -1; - my $mb; - - while (1) - { - - # Find next match indices $ma and $mb - do { - $ma++; - } while( - $ma <= $#$matchVector - && !defined $matchVector->[$ma] - ); - - last if $ma > $#$matchVector; # end of matchVector? - $mb = $matchVector->[$ma]; - - # Proceed with discard a/b or change events until - # next match - while ( $ai < $ma || $bi < $mb ) - { - - if ( $ai < $ma && $bi < $mb ) - { - - # Change - if ( defined $changeCallback ) - { - &$changeCallback( $ai++, $bi++, @_ ); - } - else - { - &$discardACallback( $ai++, $bi, @_ ); - &$discardBCallback( $ai, $bi++, @_ ); - } - } - elsif ( $ai < $ma ) - { - &$discardACallback( $ai++, $bi, @_ ); - } - else - { - - # $bi < $mb - &$discardBCallback( $ai, $bi++, @_ ); - } - } - - # Match - &$matchCallback( $ai++, $bi++, @_ ); - } - - while ( $ai <= $lastA || $bi <= $lastB ) - { - if ( $ai <= $lastA && $bi <= $lastB ) - { - - # Change - if ( defined $changeCallback ) - { - &$changeCallback( $ai++, $bi++, @_ ); - } - else - { - &$discardACallback( $ai++, $bi, @_ ); - &$discardBCallback( $ai, $bi++, @_ ); - } - } - elsif ( $ai <= $lastA ) - { - &$discardACallback( $ai++, $bi, @_ ); - } - else - { - - # $bi <= $lastB - &$discardBCallback( $ai, $bi++, @_ ); - } - } - - return 1; - } - - sub prepare - { - my $a = shift; # array ref - my $keyGen = shift; # code ref - - # set up code ref - $keyGen = sub { $_[0] } unless defined($keyGen); - - return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ ); - } - - sub LCS - { - my $a = shift; # array ref - my $b = shift; # array ref or hash ref - my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ ); - my @retval; - my $i; - for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) - { - if ( defined( $matchVector->[$i] ) ) - { - push ( @retval, $a->[$i] ); - } - } - return wantarray ? @retval : \@retval; - } - - sub LCS_length - { - my $a = shift; # array ref - my $b = shift; # array ref or hash ref - return _longestCommonSubsequence( $a, $b, 1, @_ ); - } - - sub LCSidx - { - my $a= shift @_; - my $b= shift @_; - my $match= _longestCommonSubsequence( $a, $b, 0, @_ ); - my @am= grep defined $match->[$_], 0..$#$match; - my @bm= @{$match}[@am]; - return \@am, \@bm; - } - - sub compact_diff - { - my $a= shift @_; - my $b= shift @_; - my( $am, $bm )= LCSidx( $a, $b, @_ ); - my @cdiff; - my( $ai, $bi )= ( 0, 0 ); - push @cdiff, $ai, $bi; - while( 1 ) { - while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) { - shift @$am; - shift @$bm; - ++$ai, ++$bi; - } - push @cdiff, $ai, $bi; - last if ! @$am; - $ai = $am->[0]; - $bi = $bm->[0]; - push @cdiff, $ai, $bi; - } - push @cdiff, 0+@$a, 0+@$b - if $ai < @$a || $bi < @$b; - return wantarray ? @cdiff : \@cdiff; - } - - sub diff - { - my $a = shift; # array ref - my $b = shift; # array ref - my $retval = []; - my $hunk = []; - my $discard = sub { - push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ]; - }; - my $add = sub { - push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ]; - }; - my $match = sub { - push @$retval, $hunk - if 0 < @$hunk; - $hunk = [] - }; - traverse_sequences( $a, $b, - { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); - &$match(); - return wantarray ? @$retval : $retval; - } - - sub sdiff - { - my $a = shift; # array ref - my $b = shift; # array ref - my $retval = []; - my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; - my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; - my $change = sub { - push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); - }; - my $match = sub { - push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); - }; - traverse_balanced( - $a, - $b, - { - MATCH => $match, - DISCARD_A => $discard, - DISCARD_B => $add, - CHANGE => $change, - }, - @_ - ); - return wantarray ? @$retval : $retval; - } - - ######################################## - my $Root= __PACKAGE__; - package Algorithm::Diff::_impl; - use strict; - - sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices - # 1 # $me->[1]: Ref to first sequence - # 2 # $me->[2]: Ref to second sequence - sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos - sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items - sub _Base() { 5 } # $me->[_Base]: Added to range's min and max - sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected - sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position - sub _Min() { -2 } # Added to _Off to get min instead of max+1 - - sub Die - { - require Carp; - Carp::confess( @_ ); - } - - sub _ChkPos - { - my( $me )= @_; - return if $me->[_Pos]; - my $meth= ( caller(1) )[3]; - Die( "Called $meth on 'reset' object" ); - } - - sub _ChkSeq - { - my( $me, $seq )= @_; - return $seq + $me->[_Off] - if 1 == $seq || 2 == $seq; - my $meth= ( caller(1) )[3]; - Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" ); - } - - sub getObjPkg - { - my( $us )= @_; - return ref $us if ref $us; - return $us . "::_obj"; - } - - sub new - { - my( $us, $seq1, $seq2, $opts ) = @_; - my @args; - for( $opts->{keyGen} ) { - push @args, $_ if $_; - } - for( $opts->{keyGenArgs} ) { - push @args, @$_ if $_; - } - my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args ); - my $same= 1; - if( 0 == $cdif->[2] && 0 == $cdif->[3] ) { - $same= 0; - splice @$cdif, 0, 2; - } - my @obj= ( $cdif, $seq1, $seq2 ); - $obj[_End] = (1+@$cdif)/2; - $obj[_Same] = $same; - $obj[_Base] = 0; - my $me = bless \@obj, $us->getObjPkg(); - $me->Reset( 0 ); - return $me; - } - - sub Reset - { - my( $me, $pos )= @_; - $pos= int( $pos || 0 ); - $pos += $me->[_End] - if $pos < 0; - $pos= 0 - if $pos < 0 || $me->[_End] <= $pos; - $me->[_Pos]= $pos || !1; - $me->[_Off]= 2*$pos - 1; - return $me; - } - - sub Base - { - my( $me, $base )= @_; - my $oldBase= $me->[_Base]; - $me->[_Base]= 0+$base if defined $base; - return $oldBase; - } - - sub Copy - { - my( $me, $pos, $base )= @_; - my @obj= @$me; - my $you= bless \@obj, ref($me); - $you->Reset( $pos ) if defined $pos; - $you->Base( $base ); - return $you; - } - - sub Next { - my( $me, $steps )= @_; - $steps= 1 if ! defined $steps; - if( $steps ) { - my $pos= $me->[_Pos]; - my $new= $pos + $steps; - $new= 0 if $pos && $new < 0; - $me->Reset( $new ) - } - return $me->[_Pos]; - } - - sub Prev { - my( $me, $steps )= @_; - $steps= 1 if ! defined $steps; - my $pos= $me->Next(-$steps); - $pos -= $me->[_End] if $pos; - return $pos; - } - - sub Diff { - my( $me )= @_; - $me->_ChkPos(); - return 0 if $me->[_Same] == ( 1 & $me->[_Pos] ); - my $ret= 0; - my $off= $me->[_Off]; - for my $seq ( 1, 2 ) { - $ret |= $seq - if $me->[_Idx][ $off + $seq + _Min ] - < $me->[_Idx][ $off + $seq ]; - } - return $ret; - } - - sub Min { - my( $me, $seq, $base )= @_; - $me->_ChkPos(); - my $off= $me->_ChkSeq($seq); - $base= $me->[_Base] if !defined $base; - return $base + $me->[_Idx][ $off + _Min ]; - } - - sub Max { - my( $me, $seq, $base )= @_; - $me->_ChkPos(); - my $off= $me->_ChkSeq($seq); - $base= $me->[_Base] if !defined $base; - return $base + $me->[_Idx][ $off ] -1; - } - - sub Range { - my( $me, $seq, $base )= @_; - $me->_ChkPos(); - my $off = $me->_ChkSeq($seq); - if( !wantarray ) { - return $me->[_Idx][ $off ] - - $me->[_Idx][ $off + _Min ]; - } - $base= $me->[_Base] if !defined $base; - return ( $base + $me->[_Idx][ $off + _Min ] ) - .. ( $base + $me->[_Idx][ $off ] - 1 ); - } - - sub Items { - my( $me, $seq )= @_; - $me->_ChkPos(); - my $off = $me->_ChkSeq($seq); - if( !wantarray ) { - return $me->[_Idx][ $off ] - - $me->[_Idx][ $off + _Min ]; - } - return - @{$me->[$seq]}[ - $me->[_Idx][ $off + _Min ] - .. ( $me->[_Idx][ $off ] - 1 ) - ]; - } - - sub Same { - my( $me )= @_; - $me->_ChkPos(); - return wantarray ? () : 0 - if $me->[_Same] != ( 1 & $me->[_Pos] ); - return $me->Items(1); - } - - my %getName; - %getName= ( - same => \&Same, - diff => \&Diff, - base => \&Base, - min => \&Min, - max => \&Max, - range=> \&Range, - items=> \&Items, # same thing - ); - - sub Get - { - my $me= shift @_; - $me->_ChkPos(); - my @value; - for my $arg ( @_ ) { - for my $word ( split ' ', $arg ) { - my $meth; - if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/ - || not $meth= $getName{ lc $2 } - ) { - Die( $Root, ", Get: Invalid request ($word)" ); - } - my( $base, $name, $seq )= ( $1, $2, $3 ); - push @value, scalar( - 4 == length($name) - ? $meth->( $me ) - : $meth->( $me, $seq, $base ) - ); - } - } - if( wantarray ) { - return @value; - } elsif( 1 == @value ) { - return $value[0]; - } - Die( 0+@value, " values requested from ", - $Root, "'s Get in scalar context" ); - } - - - my $Obj= getObjPkg($Root); - no strict 'refs'; - - for my $meth ( qw( new getObjPkg ) ) { - *{$Root."::".$meth} = \&{$meth}; - *{$Obj ."::".$meth} = \&{$meth}; - } - for my $meth ( qw( - Next Prev Reset Copy Base Diff - Same Items Range Min Max Get - _ChkPos _ChkSeq - ) ) { - *{$Obj."::".$meth} = \&{$meth}; - } - -}; -{ - package Algorithm::LCSS; - - use strict; - { - no strict 'refs'; - *traverse_sequences = \&Algorithm::Diff::traverse_sequences; - } - - sub _tokenize { [split //, $_[0]] } - - sub CSS { - my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0; - my ( $seq1, $seq2, @match, $from_match ); - my $i = 0; - if ( $is_array ) { - $seq1 = $_[0]; - $seq2 = $_[1]; - traverse_sequences( $seq1, $seq2, { - MATCH => sub { push @{$match[$i]}, $seq1->[$_[0]]; $from_match = 1 }, - DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match }, - DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match }, - }); - } - else { - $seq1 = _tokenize($_[0]); - $seq2 = _tokenize($_[1]); - traverse_sequences( $seq1, $seq2, { - MATCH => sub { $match[$i] .= $seq1->[$_[0]]; $from_match = 1 }, - DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match }, - DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match }, - }); - } - return \@match; - } - - sub CSS_Sorted { - my $match = CSS(@_); - if ( ref $_[0] eq 'ARRAY' ) { - @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,scalar(@$_)]}@$match - } - else { - @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,length($_)]}@$match - } - return $match; - } - - sub LCSS { - my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0; - my $css = CSS(@_); - my $index; - my $length = 0; - if ( $is_array ) { - for( my $i = 0; $i < @$css; $i++ ) { - next unless @{$css->[$i]}>$length; - $index = $i; - $length = @{$css->[$i]}; - } - } - else { - for( my $i = 0; $i < @$css; $i++ ) { - next unless length($css->[$i])>$length; - $index = $i; - $length = length($css->[$i]); - } - } - return $css->[$index]; - } - -}; -# }}} -#{{{ Class::Classless module -{ - package Class::Classless; - use strict; - use vars qw(@ISA); - use Carp; - - @ISA = (); - - ########################################################################### - - @Class::Classless::X::ISA = (); - - ########################################################################### - ########################################################################### - - sub Class::Classless::X::AUTOLOAD { - # This's the big dispatcher. - - my $it = shift @_; - my $m = ($Class::Classless::X::AUTOLOAD =~ m/([^:]+)$/s ) - ? $1 : $Class::Classless::X::AUTOLOAD; - - croak "Can't call Class::Classless methods (like $m) without an object" - unless ref $it; # sanity, basically. - - my $prevstate; - $prevstate = ${shift @_} - if scalar(@_) && defined($_[0]) && - ref($_[0]) eq 'Class::Classless::CALLSTATE::SHIMMY' - ; # A shim! we were called via $callstate->NEXT - - my $no_fail = $prevstate ? $prevstate->[3] : undef; - my $i = $prevstate ? ($prevstate->[1] + 1) : 0; - # where to start scanning - my $lineage; - - # Get the linearization of the ISA tree - if($prevstate) { - $lineage = $prevstate->[2]; - } elsif(defined $it->{'ISA_CACHE'} and ref $it->{'ISA_CACHE'} ){ - $lineage = $it->{'ISA_CACHE'}; - } else { - $lineage = [ &Class::Classless::X::ISA_TREE($it) ]; - } - - # Was: - #my @lineage = - # $prevstate ? @{$prevstate->[2]} - # : &Class::Classless::X::ISA_TREE($it); - # # Get the linearization of the ISA tree - # # ISA-memoization happens in the ISA_TREE function. - - for(; $i < @$lineage; ++$i) { - - if( !defined($no_fail) and exists($lineage->[$i]{'NO_FAIL'}) ) { - $no_fail = ($lineage->[$i]{'NO_FAIL'} || 0); - # so the first NO_FAIL sets it - } - - if( ref($lineage->[$i]{'METHODS'} || 0) # sanity - && exists($lineage->[$i]{'METHODS'}{$m}) - ){ - # We found what we were after. Now see what to do with it. - my $v = $lineage->[$i]{'METHODS'}{$m}; - return $v unless defined $v and ref $v; - - if(ref($v) eq 'CODE') { # normal case, I expect! - # Used to have copying of the arglist here. - # But it was apparently useless, so I deleted it - unshift @_, - $it, # $_[0] -- target object - # a NEW callstate - bless([$m, $i, $lineage, $no_fail, $prevstate ? 1 : 0], - 'Class::Classless::CALLSTATE' - ), # $_[1] -- the callstate - ; - goto &{ $v }; # yes, magic goto! bimskalabim! - } - return @$v if ref($v) eq '_deref_array'; - return $$v if ref($v) eq '_deref_scalar'; - return $v; # fallthru - } - } - - if($m eq 'DESTROY') { # mitigate DESTROY-lookup failure at global destruction - # should be impossible - } else { - if($no_fail || 0) { - return; - } - croak "Can't find ", $prevstate ? 'NEXT method' : 'method', - " $m in ", $it->{'NAME'} || $it, - " or any ancestors\n"; - } - } - - ########################################################################### - ########################################################################### - - sub Class::Classless::X::DESTROY { - # noop - } - - ########################################################################### - sub Class::Classless::X::ISA_TREE { - # The linearizer! - # Returns the search path for $_[0], starting with $_[0] - # Possibly memoized. - - # I stopped being able to understand this algorithm about five - # minutes after I wrote it. - use strict; - - my $set_cache = 0; # flag to set the cache on the way out - - if(exists($_[0]{'ISA_CACHE'})) { - return @{$_[0]{'ISA_CACHE'}} - if defined $_[0]{'ISA_CACHE'} - and ref $_[0]{'ISA_CACHE'}; - - # Otherwise, if exists but is not a ref, it's a signal that it should - # be replaced at the earliest, with a listref - $set_cache = 1; - } - - my $has_mi = 0; # set to 0 on the first node we see with 2 parents! - # First, just figure out what's in the tree. - my %last_child = ($_[0] => 1); # as if already seen - - # if $last_child{$x} == $y, that means: - # 1) incidentally, we've passed the node $x before. - # 2) $x is the last child of $y, - # so that means that $y can be pushed to the stack only after - # we've pushed $x to the stack. - - my @tree_nodes; - { - my $current; - my @in_stack = ($_[0]); - while(@in_stack) { - next unless - defined($current = shift @in_stack) - && ref($current) # sanity - && ref($current->{'PARENTS'} || 0) # sanity - ; - - push @tree_nodes, $current; - - $has_mi = 1 if @{$current->{'PARENTS'}} > 1; - unshift - @in_stack, - map { - if(exists $last_child{$_}) { # seen before! - $last_child{$_} = $current; - (); # seen -- don't re-explore - } else { # first time seen - $last_child{$_} = $current; - $_; # first time seen -- explore now - } - } - @{$current->{'PARENTS'}} - ; - } - - # If there was no MI, then that first scan was sufficient. - unless($has_mi) { - $_[0]{'ISA_CACHE'} = \@tree_nodes if $set_cache; - return @tree_nodes; - } - - # Otherwise, toss this list and rescan, consulting %last_child - } - - # $last_child{$parent} holds the last (or only) child of $parent - # in this tree. When walking the tree this time, only that - # child is authorized to put its parent on the @in_stack. - # And that's the only way a node can get added to @in_stack, - # except for $_[0] (the start node) being there at the beginning. - - # Now, walk again, but this time exploring parents the LAST - # time seen in the tree, not the first. - - my @out; - { - my $current; - my @in_stack = ($_[0]); - while(@in_stack) { - next unless defined($current = shift @in_stack) && ref($current); - push @out, $current; # finally. - unshift - @in_stack, - grep( - ( - defined($_) # sanity - && ref($_) # sanity - && $last_child{$_} eq $current, - ), - # I'm lastborn (or onlyborn) of this parent - # so it's OK to explore now - @{$current->{'PARENTS'}} - ) - if ref($current->{'PARENTS'} || 0) # sanity - ; - } - - unless(scalar(@out) == scalar(keys(%last_child))) { - # the counts should be equal - my %good_ones; - @good_ones{@out} = (); - croak - "ISA tree for " . - ($_[0]{'NAME'} || $_[0]) . - " is apparently cyclic, probably involving the nodes " . - nodelist( grep { ref($_) && !exists $good_ones{$_} } - values(%last_child) ) - . "\n"; - } - } - #print "Contents of out: ", nodelist(@out), "\n"; - - $_[0]{'ISA_CACHE'} = \@out if $set_cache; - return @out; - } - - ########################################################################### - - sub Class::Classless::X::can { # NOT like UNIVERSAL::can ... - # return 1 if $it is capable of the method given -- otherwise 0 - my($it, $m) = @_[0,1]; - return undef unless ref $it; - - croak "undef is not a valid method name" unless defined($m); - croak "null-string is not a valid method name" unless length($m); - - foreach my $o (&Class::Classless::X::ISA_TREE($it)) { - return 1 - if ref($o->{'METHODS'} || 0) # sanity - && exists $o->{'METHODS'}{$m}; - } - - return 0; - } - - - ########################################################################### - - sub Class::Classless::X::isa { # Like UNIVERSAL::isa - # Returns true for $X->isa($Y) iff $Y is $X or is an ancestor of $X. - - return unless ref($_[0]) && ref($_[1]); - return scalar(grep {$_ eq $_[1]} &Class::Classless::X::ISA_TREE($_[0])); - } - - ########################################################################### - - sub nodelist { join ', ', map { "" . ($_->{'NAME'} || $_) . ""} @_ } - - ########################################################################### - ########################################################################### - ########################################################################### - # Methods for the CALLSTATE class. - # Basically, CALLSTATE objects represent the state of the dispatcher, - # frozen at the moment when the method call was dispatched to the - # appropriate sub. - # In the grand scheme of things, this needn't be a class -- I could - # have just made the callstate data-object be a hash with documented - # keys, or a closure that responded to only certain parameters, - # etc. But I like it this way. And I like being able to say simply - # $cs->NEXT - # Yes, these are a bit cryptically written, but it's behoovy for - # them to be very very efficient. - - @Class::Classless::ISA = (); - sub Class::Classless::CALLSTATE::found_name { $_[0][0] } - # the method name called and found - sub Class::Classless::CALLSTATE::found_depth { $_[0][1] } - # my depth in the lineage - sub Class::Classless::CALLSTATE::lineage { @{$_[0][2]} } - # my lineage - sub Class::Classless::CALLSTATE::target { $_[0][2][ 0 ] } - # the object that's the target -- same as $_[0] for the method called - sub Class::Classless::CALLSTATE::home { $_[0][2][ $_[0][1] ] } - # the object I was found in - sub Class::Classless::CALLSTATE::sub_found { - $_[0][2][ $_[0][1] ]{'METHODS'}{ $_[0][0] } - } # the routine called - - sub Class::Classless::CALLSTATE::no_fail { $_[0][3] } - sub Class::Classless::CALLSTATE::set_no_fail_true { $_[0][3] = 1 } - sub Class::Classless::CALLSTATE::set_fail_false { $_[0][3] = 0 } - sub Class::Classless::CALLSTATE::set_fail_undef { $_[0][3] = undef } - - sub Class::Classless::CALLSTATE::via_next { $_[0][4] } - - sub Class::Classless::CALLSTATE::NEXT { - #croak "NEXT needs at least one argument: \$cs->NEXT('method'...)" - # unless @_ > 1; - # no longer true. - my $cs = shift @_; - my $m = shift @_; # which may be (or come out) undef... - $m = $cs->[0] unless defined $m; # the method name called and found - - ($cs->[2][0])->$m( - bless( \$cs, 'Class::Classless::CALLSTATE::SHIMMY' ), - @_ - ); - } - - ########################################################################### -}; -#}}} - -############### -### -# -# {{{ *** C h a n g e l o g *** -# -# 0.6ca -# - add screen support (from nicklist.pl) -# - rename to adv_windowlist.pl (advanced window list) since it isn't just a -# window list status bar (wlstat) anymore -# - names can now have a max length and window names can be used -# - fixed a bug with block display in screen mode and statusbar mode -# - added space handling to ir_fe and removed it again -# - now handling formats on my own -# - added warning about missing sb_act_none abstract leading to -# - display*active settings -# - added warning about the bug in awl_display_(no)key_active settings -# -# 0.5d -# - add setting to also hide the last statusbar if empty (awl_all_disable) -# - reverted to old utf8 code to also calculate broken utf8 length correctly -# - simplified dealing with statusbars in wlreset -# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9 -# - fixed bug in handling channel #$$ -# - typo on line 200 spotted by f0rked -# - reset background colour at the beginning of an entry -# -# 0.4d -# - fixed order of disabling statusbars -# - several attempts at special chars, without any real success -# and much more weird new bugs caused by this -# - setting to specify sort order -# - reduced timeout values -# - added awl_hide_data for Geert Hauwaerts ( geert@irssi.org ) :) -# - make it so the dynamic sub is actually deleted -# - fix a bug with removing of the last separator -# - take into consideration parse_special -# -# 0.3b -# - automatically kill old statusbars -# - reset on /reload -# - position/placement settings -# -# 0.2 -# - automated retrieval of key bindings (thanks grep.pl authors) -# - improved removing of statusbars -# - got rid of status chop -# -# 0.1 -# - rewritten to suit my needs -# - based on chanact 0.5.5 -# }}} -# vim: se fdm=marker tw=80 : |