aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--fixery/adv_windowlist.pl2564
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 :