diff options
author | Matias Linares <matiaslina@openmailbox.org> | 2015-10-23 03:14:36 -0300 |
---|---|---|
committer | Matias Linares <matiaslina@openmailbox.org> | 2015-10-23 03:14:36 -0300 |
commit | 5b132bf1244a008ad3ce67688030a7678831f77d (patch) | |
tree | 501aa27e89f81d5f4bdfaab31c72378991a9c749 /irssi/scripts/autorun/adv_windowlist.pl | |
download | dotfiles-5b132bf1244a008ad3ce67688030a7678831f77d.tar.gz |
Initial commit
Diffstat (limited to 'irssi/scripts/autorun/adv_windowlist.pl')
-rw-r--r-- | irssi/scripts/autorun/adv_windowlist.pl | 2478 |
1 files changed, 2478 insertions, 0 deletions
diff --git a/irssi/scripts/autorun/adv_windowlist.pl b/irssi/scripts/autorun/adv_windowlist.pl new file mode 100644 index 0000000..780ab35 --- /dev/null +++ b/irssi/scripts/autorun/adv_windowlist.pl @@ -0,0 +1,2478 @@ +use strict; # use warnings; + +# {{{ 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 Irssi (); # which is the minimum required version of Irssi ? +use Irssi::TextUI; + +use vars qw($VERSION %IRSSI); + +$VERSION = '0.6ca'; +%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 @ anti@conference.jabber.teamidiot.de), + url => "http://anti.teamidiot.de/", + 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 =^.^= ( anti@conference.jabber.teamidiot.de ) +# }}} + +# }}} + +# {{{ 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 $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, $cont_stripped) = @_; + if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq '' + and !defined($textDest->{'server'}) + ) { + if ($cont_stripped =~ m/((?:meta-)+)(.)\s+change_window (\d+)/) { + 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'); + +# }}} + +# {{{ 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; + 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'); + +# }}} + + +# {{{ 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 : |