summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authordx2015-11-23 14:18:41 -0300
committerdx2015-11-23 14:18:41 -0300
commit8d9d995b69ba2b436d4bd63061d8189a6844a9a8 (patch)
tree0a8e939a47dbd15d9a8c04b61e21c1e33d6da738 /scripts
parent2605878778636421276fe85cd9a2045d125546bd (diff)
parent6dec1974e3ac2e71c26e842887a4b8fe2ec9082c (diff)
downloadscripts.irssi.org-8d9d995b69ba2b436d4bd63061d8189a6844a9a8.tar.bz2
Merge pull request #173 from irssi/nei-scripts
FORCEFULLY ADD ALL THE SCRIPTS FROM NEI'S WEBSITE
Diffstat (limited to 'scripts')
-rw-r--r--scripts/adv_windowlist.pl2456
-rw-r--r--scripts/aspell.pl725
-rw-r--r--scripts/clearable.pl71
-rw-r--r--scripts/cmpchans.pl64
-rw-r--r--scripts/colorize_nicks.pl136
-rw-r--r--scripts/complete_at.pl40
-rw-r--r--scripts/dim_nicks.pl392
-rw-r--r--scripts/hideshow.pl286
-rw-r--r--scripts/hlscroll.pl83
-rw-r--r--scripts/ido_switcher.pl1166
-rw-r--r--scripts/ircuwhois.pl84
-rw-r--r--scripts/linebuffer.pl278
-rw-r--r--scripts/messages_bottom.pl29
-rw-r--r--scripts/mouse-awl.pl144
-rw-r--r--scripts/mouse_soliton.pl146
-rw-r--r--scripts/nickcolor_expando.pl1048
-rw-r--r--scripts/nickcolor_gay.pl83
-rw-r--r--scripts/nm2.pl560
-rw-r--r--scripts/recentdepart.pl332
-rw-r--r--scripts/sb_position.pl112
-rw-r--r--scripts/sbclearmatch.pl93
-rw-r--r--scripts/tmux-nicklist-portable.pl390
-rw-r--r--scripts/trackbar22.pl500
-rw-r--r--scripts/typofix.pl165
-rw-r--r--scripts/uberprompt.pl787
25 files changed, 10170 insertions, 0 deletions
diff --git a/scripts/adv_windowlist.pl b/scripts/adv_windowlist.pl
new file mode 100644
index 0000000..19bbe70
--- /dev/null
+++ b/scripts/adv_windowlist.pl
@@ -0,0 +1,2456 @@
+use strict;
+use warnings;
+
+our $VERSION = '1.0a2'; # 185124f561a65ff
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'adv_windowlist',
+ description => 'Adds a permanent advanced window list on the right or in a status bar.',
+ license => 'GNU GPLv2 or later',
+ );
+
+# UPGRADE NOTE
+# ============
+# for users of 0.7 or earlier series, please note that appearance
+# settings have moved to /format, i.e. inside your theme!
+# the fifo (screen) has been replaced by an external viewer script
+
+# Usage
+# =====
+# copy the script to ~/.irssi/scripts/
+#
+# In irssi:
+#
+# /run adv_windowlist
+#
+# In your shell (for example a tmux split):
+#
+# perl ~/.irssi/scripts/adv_windowlist.pl
+#
+# To use sbar mode instead:
+#
+# /toggle awl_viewer
+#
+# 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
+# =======
+# formats can be cleared with /format -delete
+#
+# /format awl_display_(no)key(_active|_visible) <string>
+# * string : Format String for one window. The following $'s are expanded:
+# $C : Name
+# $N : Number of the Window
+# $Q : meta-Keymap
+# $H : Start hilighting
+# $S : Stop hilighting
+# /+++++++++++++++++++++++++++++++++,
+# | **** I M P O R T A N T : **** |
+# | |
+# | don't forget to use $S if you |
+# | used $H before! |
+# | |
+# '+++++++++++++++++++++++++++++++++/
+# key : a key binding that goes to this window could be detected in /bind
+# nokey : no such key binding was detected
+# active : window would receive the input you are currently typing
+# visible : window is also visible on screen but not active (a split window)
+#
+# /format awl_name_display <string>
+# * string : Format String for window names
+# $0 : name as formatted by the settings
+#
+# /format awl_display_header <string>
+# * string : Format String for this header line. The following $'s are expanded:
+# $C : network tag
+#
+# /format awl_separator(2) <string>
+# * string : Character to use between the channel entries
+# variant 2 can be used for alternating separators (only in status bar
+# without block display)
+#
+# /format awl_viewer_item_bg <string>
+# * string : Format String specifying the viewer's item background colour
+#
+# /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_empty <num>
+# * if visible windows without items should be hidden from the window list
+# set it to 0 to show all windows
+# 1 to hide visible windows without items (negative exempt
+# active window)
+#
+# /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 (negative exempt active window)
+#
+# /set awl_hide_name_data <num>
+# * num : hide the name of the window if its data_level is below num
+# (only works in status bar without block display)
+# you will want to change your formats to add $H...$S around $Q or $N
+# if you plan to use this
+#
+# /set awl_maxlines <num>
+# * num : number of lines to use for the window list (0 to disable, negative
+# lock)
+#
+# /set awl_maxcolumns <num>
+# * num : number of columns to use for the window list when using the
+# tmux integration (0 to disable)
+#
+# /set awl_block <num>
+# * num : width of a column in viewer mode (negative values = block
+# display in status bar mode)
+# /+++++++++++++++++++++++++++++++++,
+# | ****** 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 = "%K$*"; |
+# | |
+# '+++++++++++++++++++++++++++++++++/
+#
+# /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 status bar mode too.
+#
+# /set awl_height_adjust <num>
+# * num : how many lines to leave empty in viewer 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
+# active/server/tag : sort by server name
+# "-" reverses the sort order
+# typechecks are supported via ::, e.g. active::Query or active::Irc::Query
+# undefinedness can be checked with ~, e.g. ~active
+# string comparison can be done with =, e.g. name=(status)
+# to make sort case insensitive, use #i, e.g. name#i
+# any key in the window hash can be tested, e.g. active/chat_type=XMPP
+# multiple criteria can be separated with , or +, e.g. -data_level+-last_line
+#
+# /set awl_placement <top|bottom>
+# /set awl_position <num>
+# * these settings correspond to /statusbar because awl will create
+# status bars 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 status bar it created if it is empty.
+# As you might guess, this only makes sense with awl_hide_data > 0 ;)
+#
+# /set awl_viewer <ON|OFF>
+# * enable the external viewer script
+#
+# /set awl_viewer_launch <ON|OFF>
+# * try to auto-launch the viewer under tmux or with a shell command
+# /awl restart is required all auto-launch related settings to take
+# effect
+#
+# /set awl_viewer_tmux_position <left|top|right|bottom|custom>
+# * try to split in this direction when using tmux for the viewer
+# custom : use custom_command setting
+#
+# /set awl_viewer_xwin_command <shell command>
+# * custom command to run in order to start the viewer when irssi is
+# running under X
+# %A - gets replaced by the command to run the viewer
+# %qA - additionally quote the command
+#
+# /set awl_viewer_custom_command <shell command>
+# * custom command to run in order to start the viewer
+#
+# /set awl_viewer_launch_env <string>
+# * specific environment settings for use on viewer auto-launch,
+# without the AWL_ prefix
+#
+# /set awl_shared_sbar <left<right|OFF>
+# * share a status bar for the first awl item, you will need to manually
+# /statusbar window add -after lag -priority 10 awl_shared
+# left : space in cells occupied on the left of status bar
+# right : space occupied on the right
+# Note: you need to replace "left" AND "right" with the appropriate numbers!
+#
+# /set awl_path <path>
+# * path to the file which the viewer script reads
+#
+# /set fancy_abbrev <no|head|strict|fancy>
+# * how to shorten too long names
+# no : shorten in the middle
+# head : always cut off the ends
+# strict : shorten repeating substrings
+# fancy : combination of no+strict
+#
+# /set awl_custom_xform <perl code>
+# * specify a custom routine to transform window names
+# example: s/^#// remove the #-mark of IRC channels
+# the special flags $CHANNEL / $TAG / $QUERY / $NAME can be
+# tested in conditionals
+#
+# /set awl_last_line_shade <timeout>
+# * set timeout to shade activity base colours, to enable
+# you also need to add +-last_line to awl_sort
+# (requires 256 colour support)
+#
+# /set awl_no_mode_hint <ON|OFF>
+# * whether to show the hint of running the viewer script in the
+# status bar
+#
+# /set awl_mouse <ON|OFF>
+# * enable the terminal mouse in irssi
+# (use the awl-patched mouse.pl for gestures and commands if you need
+# them and disable mouse_escape)
+#
+# /set awl_mouse_offset <num>
+# * specifies where on the screen is the awl status bar
+# (0 = on top/bottom, 1 = one additional line in between,
+# e.g. prompt)
+# you MUST set this correctly otherwise the mouse coordinates will
+# be off
+#
+# /set mouse_scroll <num>
+# * how many lines the mouse wheel scrolls
+#
+# /set mouse_escape <num>
+# * seconds to disable the mouse, when not clicked on the windowlist
+#
+
+# Commands
+# ========
+# /awl redraw
+# * redraws the windowlist. There may be occasions where the
+# windowlist can get destroyed so you can use this command to
+# force a redraw.
+#
+# /awl restart
+# * restart the connection to the viewer script.
+
+# Viewer script
+# =============
+# When run from the command line, adv_windowlist acts as the viewer
+# script to be used together with the irssi script to display the
+# window list in a sidebar/terminal of its own.
+#
+# One optional parameter is accepted, the awl_path
+#
+# The viewer can be configured by three environment variables:
+#
+# AWL_HI9=1
+# * interpret %9 as high-intensity toggle instead of bold. This had
+# been the default prior to version 0.9b8
+#
+# AWL_AUTOFOCUS=0
+# * disable auto-focus behaviour when activating a window
+#
+# AWL_NOTITLE=1
+# * disable the title bar
+
+# Nei =^.^= ( anti@conference.jabber.teamidiot.de )
+
+no warnings 'redefine';
+use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK};
+use constant SCRIPT_FILE => __FILE__;
+no if !IN_IRSSI, strict => (qw(subs refs));
+use if IN_IRSSI, Irssi => ();
+use if IN_IRSSI, 'Irssi::TextUI' => ();
+use v5.10;
+use Encode;
+use Storable ();
+use IO::Socket::UNIX;
+use List::Util qw(min max reduce);
+use Hash::Util qw(lock_keys);
+use Text::ParseWords qw(shellwords);
+
+unless (IN_IRSSI) {
+ local *_ = \@ARGV;
+ &AwlViewer::main;
+ exit;
+}
+
+
+use constant GLOB_QUEUE_TIMER => 100;
+
+our $BLOCK_ALL; # localized blocker
+my @actString; # status bar texts
+my @win_items;
+my $currentLines = 0;
+my %awins;
+my $globTime; # timer to limit remake calls
+
+my %CHANGED;
+my $VIEWER_MODE;
+my $MOUSE_ON;
+my %mouse_coords;
+my %statusbars;
+my %S; # settings
+my $settings_str = '';
+my $window_sort_func;
+my $custom_xform;
+my ($sb_base_width, $sb_base_width_pre, $sb_base_width_post);
+my $print_text_activity;
+my $shade_line_timer;
+my ($screenHeight, $screenWidth);
+my %viewer;
+
+my (%keymap, %nummap, %wnmap, %specialmap, %wnmap_exp, %custom_key_map);
+my %banned_channels;
+my %abbrev_cache;
+
+use constant setc => 'awl';
+
+sub set ($) {
+ setc . '_' . $_[0]
+}
+
+sub add_statusbar {
+ for (@_) {
+ # add subs
+ my $l = set $_;
+ {
+ my $close = $_;
+ no strict 'refs';
+ *{$l} = sub { awl($close, @_) };
+ }
+ Irssi::command("statusbar $l reset");
+ Irssi::command("statusbar $l enable");
+ if (lc $S{placement} eq 'top') {
+ Irssi::command("statusbar $l placement top");
+ }
+ if (my $x = $S{position}) {
+ Irssi::command("statusbar $l position $x");
+ }
+ Irssi::command("statusbar $l add -priority 100 -alignment left barstart");
+ Irssi::command("statusbar $l add $l");
+ Irssi::command("statusbar $l add -priority 100 -alignment right barend");
+ Irssi::command("statusbar $l disable");
+ Irssi::statusbar_item_register($l, '$0', $l);
+ $statusbars{$_} = 1;
+ Irssi::command("statusbar $l enable");
+ }
+}
+
+sub remove_statusbar {
+ for (@_) {
+ my $l = set $_;
+ Irssi::command("statusbar $l disable");
+ Irssi::command("statusbar $l reset");
+ Irssi::statusbar_item_unregister($l);
+ {
+ no strict 'refs';
+ undef &{$l};
+ }
+ delete $statusbars{$_};
+ }
+}
+
+my $awl_shared_empty = sub {
+ return if $BLOCK_ALL;
+ my ($item, $get_size_only) = @_;
+ $item->default_handler($get_size_only, '', '', 0);
+};
+
+sub syncLines {
+ my $maxLines = $S{maxlines};
+ my $newLines = ($maxLines > 0 and @actString > $maxLines) ?
+ $maxLines :
+ ($maxLines < 0) ?
+ -$maxLines :
+ @actString;
+ $currentLines = 1 if !$currentLines && $S{shared_sbar};
+ if ($S{shared_sbar} && !$statusbars{shared}) {
+ my $l = set 'shared';
+ {
+ no strict 'refs';
+ *{$l} = sub {
+ return if $BLOCK_ALL;
+ my ($item, $get_size_only) = @_;
+
+ my $text = $actString[0];
+ my $pat = defined $text ? '{sb '.ucfirst(setc()).': $*}' : '{sb }';
+ $text //= '';
+ $item->default_handler($get_size_only, $pat, $text, 0);
+ };
+ }
+ $statusbars{shared} = 1;
+ remove_statusbar (0) if $statusbars{0};
+ }
+ elsif ($statusbars{shared} && !$S{shared_sbar}) {
+ add_statusbar (0) if $currentLines && $newLines;
+ delete $statusbars{shared};
+ my $l = set 'shared';
+ {
+ no strict 'refs';
+ *{$l} = $awl_shared_empty;
+ }
+ }
+ if ($currentLines == $newLines) { return; }
+ elsif ($newLines > $currentLines) {
+ add_statusbar ($currentLines .. ($newLines - 1));
+ }
+ else {
+ remove_statusbar (reverse ($newLines .. ($currentLines - 1)));
+ }
+ $currentLines = $newLines;
+}
+
+sub awl {
+ return if $BLOCK_ALL;
+ my ($line, $item, $get_size_only) = @_;
+
+ my $text = $actString[$line];
+ my $pat = defined $text ? '{sb $*}' : '{sb }';
+ $text //= '';
+ $item->default_handler($get_size_only, $pat, $text, 0);
+}
+
+# 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} = 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);
+ }
+}
+
+sub get_keymap {
+ my ($textDest, undef, $cont_stripped) = @_;
+ if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) {
+ my $one_meta_or_ctrl_key = qr/((?:meta-)*?)(?:(meta-|\^)(\S)|(\w+))/;
+ $cont_stripped = as_uni($cont_stripped);
+ if ($cont_stripped =~ m/((?:$one_meta_or_ctrl_key-)*$one_meta_or_ctrl_key)\s+(.*)$/) {
+ my ($combo, $command) = ($1, $10);
+ my $map = '';
+ while ($combo =~ s/(?:-|^)$one_meta_or_ctrl_key$//) {
+ my ($level, $ctl, $key, $nkey) = ($1, $2, $3, $4);
+ my $numlevel = ($level =~ y/-//);
+ $ctl = '' if !$ctl || $ctl ne '^';
+ $map = ('-' x ($numlevel%2)) . ('+' x ($numlevel/2)) .
+ $ctl . (defined $key ? $key : "\01$nkey\01") . $map;
+ }
+ for ($command) {
+ last unless length $map;
+ if (/^change_window (\d+)/i) {
+ $nummap{$1} = $map;
+ }
+ elsif (/^command window goto (\S+)/i) {
+ my $window = $1;
+ if ($window !~ /\D/) {
+ $nummap{$window} = $map;
+ }
+ elsif (lc $window eq 'active') {
+ $specialmap{_active} = $map;
+ }
+ else {
+ $wnmap{$window} = $map;
+ }
+ }
+ elsif (/^(?:active_window|command (ack))/i) {
+ $specialmap{_active} = $map;
+ $viewer{use_ack} = !!$1;
+ }
+ elsif (/^command window last/i) {
+ $specialmap{_last} = $map;
+ }
+ elsif (/^(?:upper_window|command window up)/i) {
+ $specialmap{_up} = $map;
+ }
+ elsif (/^(?:lower_window|command window down)/i) {
+ $specialmap{_down} = $map;
+ }
+ elsif (/^key\s+(\w+)/i) {
+ $custom_key_map{$1} = $map;
+ }
+ }
+ }
+ Irssi::signal_stop;
+ }
+}
+
+sub update_keymap {
+ %nummap = %wnmap = %specialmap = %custom_key_map = ();
+ Irssi::signal_remove('command bind' => 'watch_keymap');
+ Irssi::signal_add_first('print text' => 'get_keymap');
+ Irssi::command('bind');
+ Irssi::signal_remove('print text' => 'get_keymap');
+ for (keys %custom_key_map) {
+ if (exists $custom_key_map{$_} &&
+ $custom_key_map{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) {
+ if ($custom_key_map{$_} =~ /\02/) {
+ delete $custom_key_map{$_};
+ }
+ else {
+ redo;
+ }
+ }
+ }
+ for my $keymap (\(%specialmap, %wnmap, %nummap)) {
+ for (keys %$keymap) {
+ if ($keymap->{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) {
+ if ($keymap->{$_} =~ /\02/) {
+ delete $keymap->{$_};
+ }
+ }
+ }
+ }
+ Irssi::signal_add('command bind' => 'watch_keymap');
+ delete $viewer{client_keymap};
+ &wl_changed;
+}
+
+# watch keymap changes
+sub watch_keymap {
+ Irssi::timeout_add_once(1000, 'update_keymap', undef);
+}
+
+{ 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_8I:|FnN>#[' . 'pP')),
+ # escape
+ (map { $_ => $_ } (split //, '{}%')),
+ );
+ sub ir_strip_codes { # strip %codes
+ my $o = shift;
+ $o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} :
+ $2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex;
+ $o
+ }
+}
+## ir_parse_special -- wrapper around parse_special
+## $i - input format
+## $args - array ref of arguments to format
+## $win - different target window (default current window)
+## $flags - different kind of escape flags (default 4|8)
+## returns formatted str
+sub ir_parse_special {
+ my $o;
+ my $i = shift;
+ my $args = shift // [];
+ y/ /\177/ for @$args; # hack to escape spaces
+ my $win = shift || Irssi::active_win;
+ my $flags = shift // 0x4|0x8;
+ my @cmd_args = ($i, (join ' ', @$args), $flags);
+ my $server = Irssi::active_server();
+ if (ref $win and ref $win->{active}) {
+ $o = $win->{active}->parse_special(@cmd_args);
+ }
+ elsif (ref $win and ref $win->{active_server}) {
+ $o = $win->{active_server}->parse_special(@cmd_args);
+ }
+ elsif (ref $server) {
+ $o = $server->parse_special(@cmd_args);
+ }
+ else {
+ $o = &Irssi::parse_special(@cmd_args);
+ }
+ $o =~ y/\177/ /;
+ $o
+}
+
+sub sb_format_expand { # Irssi::current_theme->format_expand wrapper
+ Irssi::current_theme->format_expand(
+ $_[0],
+ (
+ Irssi::EXPAND_FLAG_IGNORE_REPLACES
+ |
+ ($_[1] ? 0 : Irssi::EXPAND_FLAG_IGNORE_EMPTY)
+ )
+ )
+}
+
+{ my $term_type = Irssi::version > 20040819 ? 'term_charset' : 'term_type';
+ local $@;
+ eval { require Text::CharWidth; };
+ unless ($@) {
+ *screen_length = sub { Text::CharWidth::mbswidth($_[0]) };
+ }
+ else {
+ my $err = $@; chomp $err; $err =~ s/\sat .* line \d+\.$//;
+ #Irssi::print("%_$IRSSI{name}: warning:%_ Text::CharWidth module failed to load. Length calculation may be off! Error was:");
+ print "%_$IRSSI{name}:%_ $err";
+ *screen_length = sub {
+ my $temp = shift;
+ if (lc Irssi::settings_get_str($term_type) eq 'utf-8') {
+ Encode::_utf8_on($temp);
+ }
+ length($temp)
+ };
+ }
+ sub as_uni {
+ no warnings 'utf8';
+ Encode::decode(Irssi::settings_get_str($term_type), $_[0], 0)
+ }
+ sub as_tc {
+ Encode::encode(Irssi::settings_get_str($term_type), $_[0], 0)
+ }
+}
+
+sub sb_length {
+ screen_length(ir_strip_codes($_[0]))
+}
+
+sub run_custom_xform {
+ local $@;
+ eval {
+ $custom_xform->()
+ };
+ if ($@) {
+ $@ =~ /^(.*)/;
+ print '%_'.(set 'custom_xform').'%_ died (disabling): '.$1;
+ $custom_xform = undef;
+ }
+}
+
+sub remove_uniform {
+ my $o = shift;
+ $o =~ s/^xmpp:(.*?[%@]).+\.[^.]+$/$1/ or
+ $o =~ s#^psyc://.+\.[^.]+/([@~].*)$#$1#;
+ if ($custom_xform) {
+ run_custom_xform() for $o;
+ }
+ $o
+}
+
+sub remove_uniform_vars {
+ my $win = shift;
+ my $name = __PACKAGE__ . '::custom_xform::' . $win->{active}{type}
+ if $win->{active} && $win->{active}{type};
+ no strict 'refs';
+ local ${$name} = 1 if $name;
+ remove_uniform(+shift);
+}
+
+sub lc1459 {
+ my $x = shift;
+ $x =~ y/][\\^/}{|~/;
+ lc $x
+}
+
+sub window_list {
+ sort $window_sort_func Irssi::windows;
+}
+
+sub _calculate_abbrev {
+ my ($wins, $abbrevList) = @_;
+ if ($S{fancy_abbrev} !~ /^(no|off|head)/i) {
+ my @nameList = map { ref $_ ? remove_uniform_vars($_, as_uni($_->get_active_name) // '') : '' } @$wins;
+ for (my $i = 0; $i < @nameList - 1; ++$i) {
+ my ($x, $y) = ($nameList[$i], $nameList[$i + 1]);
+ s/^[+#!=]// for $x, $y;
+ my $res = exists $abbrev_cache{$x}{$y} ? $abbrev_cache{$x}{$y}
+ : $abbrev_cache{$x}{$y} = string_LCSS($x, $y);
+ if (defined $res) {
+ for ($nameList[$i], $nameList[$i + 1]) {
+ $abbrevList->{$_} //= int((index $_, $res) + (length $res) / 2);
+ }
+ }
+ }
+ }
+}
+
+my %act_last_line_shades = (
+ r => [qw[ 50 40 30 20 ]],
+ g => [qw[ 1O 1I 1C 16 ]],
+ y => [qw[ 5O 4I 3C 26 ]],
+ b => [qw[ 15 14 13 12 ]],
+ m => [qw[ 54 43 32 21 ]],
+ c => [qw[ 1S 1L 1E 17 ]],
+ w => [qw[ 7W 7T 7Q 3E ]],
+ K => [qw[ 7M 7K 27 7H ]],
+ R => [qw[ 60 50 40 30 ]],
+ G => [qw[ 1U 1O 1I 1C ]],
+ Y => [qw[ 6U 5O 4I 3C ]],
+ B => [qw[ 2B 2A 29 28 ]],
+ M => [qw[ 65 54 43 32 ]],
+ C => [qw[ 1Z 1S 1L 1E ]],
+ W => [qw[ 6Z 5S 7R 7O ]],
+ );
+
+sub _format_display {
+ my (undef, $format, $cformat, $hilight, $name, $number, $key, $win) = @_;
+ if ($print_text_activity && $S{line_shade}) {
+ my @hilight_code = split /\177/, sb_format_expand("{$hilight \177}"), 2;
+ my $max_time = max(1, log($S{line_shade}) - log(1000));
+ my $time_delta = min(3, min($max_time, log(max(1, time - $win->{last_line}))) / $max_time * 3);
+ if ($hilight_code[0] =~ /%(.)/ && exists $act_last_line_shades{$1}) {
+ $hilight = 'sb_act_hilight_color %X'.$act_last_line_shades{$1}[$time_delta];
+ }
+ }
+ $cformat = '$0' unless defined $cformat && length $cformat;
+ my %map = ('$C' => $cformat, '$N' => '$1', '$Q' => '$2');
+ $format =~ s<(\$.)><$map{$1}//$1>ge;
+ $format =~ s<\$H((?:\$.|[^\$])*?)\$S><{$hilight $1%n}>g;
+ my @ret = ir_parse_special(sb_format_expand($format), [$name, $number, $key], $win);
+ @ret
+}
+
+sub _calculate_items {
+ my ($wins, $abbrevList) = @_;
+
+ my $display_header = Irssi::current_theme->get_format(__PACKAGE__, set 'display_header');
+ my $name_format = Irssi::current_theme->get_format(__PACKAGE__, set 'name_display');
+ my %displays;
+
+ my $active = Irssi::active_win;
+ @win_items = ();
+ %keymap = (%nummap, %wnmap_exp);
+
+ my ($numPad, $keyPad) = (0, 0);
+ if ($VIEWER_MODE or $S{block} < 0) {
+ $numPad = length((sort { length $b <=> length $a } keys %keymap)[0]) // 0;
+ $keyPad = length((sort { length $b <=> length $a } values %keymap)[0]) // 0;
+ }
+ my $last_net;
+ for my $win (@$wins) {
+ my $global_hack_alert_tag_header;
+
+ next unless ref $win;
+
+ my $backup_win = Storable::dclone($win);
+ delete $backup_win->{active} unless ref $backup_win->{active};
+
+ $global_hack_alert_tag_header =
+ $display_header && ($last_net // '') ne ($backup_win->{active}{server}{tag} // '');
+
+ if ($win->{data_level} < abs $S{hide_data}
+ && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_data})) {
+ next; }
+ elsif (exists $awins{$win->{refnum}} && $S{hide_empty} && !$win->items
+ && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_empty})) {
+ next; }
+
+ my $colour = $win->{hilight_color} // '';
+ my $hilight = do {
+ if ($win->{data_level} == 0) { 'sb_act_none'; }
+ elsif ($win->{data_level} == 1) { 'sb_act_text'; }
+ elsif ($win->{data_level} == 2) { 'sb_act_msg'; }
+ elsif ($colour ne '') { "sb_act_hilight_color $colour"; }
+ elsif ($win->{data_level} == 3) { 'sb_act_hilight'; }
+ else { 'sb_act_special'; }
+ };
+ my $number = $win->{refnum};
+
+ my ($name, $display, $cdisplay);
+ if ($global_hack_alert_tag_header) {
+ $display = $display_header;
+ $name = as_uni($backup_win->{active}{server}{tag}) // '';
+ if ($custom_xform) {
+ no strict 'refs';
+ local ${ __PACKAGE__ . '::custom_xform::TAG' } = 1;
+ run_custom_xform() for $name;
+ }
+ }
+ else {
+ my @display = ('display_nokey');
+ if (defined $keymap{$number} and $keymap{$number} ne '') {
+ unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display;
+ }
+ if (exists $awins{$number}) {
+ unshift @display, map { my $cpy = $_; $cpy .= '_visible'; $cpy } @display;
+ }
+ if ($active->{refnum} == $number) {
+ unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy }
+ grep { !/_visible$/ } @display;
+ }
+ $display = (grep { length $_ }
+ map { $displays{$_} //= Irssi::current_theme->get_format(__PACKAGE__, set $_) }
+ @display)[0];
+ $cdisplay = $name_format;
+ $name = as_uni($win->get_active_name) // '';
+ $name = '*' if $S{banned_on} and exists $banned_channels{lc1459($name)};
+ $name = remove_uniform_vars($win, $name) if $name ne '*';
+ if ($name ne '*' and $win->{name} ne '' and $S{prefer_name}) {
+ $name = as_uni($win->{name});
+ if ($custom_xform) {
+ no strict 'refs';
+ local ${ __PACKAGE__ . '::custom_xform::NAME' } = 1;
+ run_custom_xform() for $name;
+ }
+ }
+
+ if (!$VIEWER_MODE && $S{block} >= 0 && $S{hide_name}
+ && $win->{data_level} < abs $S{hide_name}
+ && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_name})) {
+ $name = '';
+ $cdisplay = '';
+ }
+ }
+
+ $display = "$display%n";
+ my $num_ent = (' 'x max(0,$numPad - length $number)) . $number;
+ my $key_ent = exists $keymap{$number} ? ((' 'x max(0,$keyPad - length $keymap{$number})) . $keymap{$number}) : ' 'x$keyPad;
+ if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) {
+ my $baseLength = sb_length(_format_display(
+ '', $display, $cdisplay, $hilight,
+ 'x', # placeholder
+ $num_ent,
+ $key_ent,
+ $win)) - 1;
+ my $diff = (abs $S{block}) - (screen_length(as_tc($name)) + $baseLength);
+ if ($diff < 0) { # too long
+ my $screen_length = screen_length(as_tc($name));
+ if ((abs $diff) >= $screen_length) { $name = '' } # forget it
+ elsif ((abs $diff) + screen_length(as_tc(substr($name, 0, 1))) >= $screen_length) { $name = substr($name, 0, 1); }
+ else {
+ my $ulen = length $name;
+ my $middle2 = exists $abbrevList->{$name} ?
+ ($S{fancy_strict}) ?
+ 2* $abbrevList->{$name} :
+ (2*($abbrevList->{$name} + $ulen) / 3) :
+ ($S{fancy_head}) ?
+ 2*$ulen :
+ $ulen;
+ my $first = 1;
+ while (length $name > 1) {
+ my $cp = $middle2 > -1 ? $middle2/2 : -1; # check position for double width
+ my $rm = 2;
+ if (screen_length(as_tc(substr $name, $cp, 1)) > 1) {
+ if ($first || $cp < 0) {
+ $rm = 1;
+ $first = undef;
+ }
+ }
+ elsif ($cp < 0) {
+ --$cp;
+ }
+ (substr $name, $cp, $rm) = '~';
+ if ($cp > -1 && $rm > 1) {
+ --$middle2;
+ }
+ my $sl = screen_length(as_tc($name));
+ if ($sl + $baseLength < abs $S{block}) {
+ (substr $name, ($middle2+1)/2, 1) = "\x{301c}";
+ last;
+ }
+ elsif ($sl + $baseLength == abs $S{block}) {
+ last;
+ }
+ }
+ }
+ }
+ elsif ($VIEWER_MODE or $S{block} < 0) {
+ $name .= (' ' x $diff);
+ }
+ }
+
+ push @win_items, _format_display(
+ '', $display, $cdisplay, $hilight,
+ as_tc($name),
+ $num_ent,
+ as_tc($key_ent),
+ $win);
+
+ if ($global_hack_alert_tag_header) {
+ $last_net = $backup_win->{active}{server}{tag};
+ redo;
+ }
+
+ $mouse_coords{refnum}{$#win_items} = $number;
+ }
+}
+
+sub _spread_items {
+ my $width = [Irssi::windows]->[0]{width} - $sb_base_width - 1;
+ my @separator = Irssi::current_theme->get_format(__PACKAGE__, set 'separator');
+ if ($S{block} >= 0) {
+ my $sep2 = Irssi::current_theme->get_format(__PACKAGE__, set 'separator2');
+ push @separator, $sep2 if length $sep2 && $sep2 ne $separator[0];
+ }
+ $separator[0] .= '%n';
+ my @sepLen = map { sb_length($_) } @separator;
+
+ @actString = ();
+ my $curLine;
+ my $curLen = 0;
+ if ($S{shared_sbar}) {
+ $curLen += $S{shared_sbar}[0] + 2 + length setc();
+ $width -= $S{shared_sbar}[2];
+ }
+ my $mouse_header_check = 0;
+ for my $it (@win_items) {
+ my $itemLen = sb_length($it);
+ if ($curLen) {
+ if ($curLen + $itemLen + $sepLen[$mouse_header_check % @sepLen] > $width) {
+ $width += $S{shared_sbar}[2]
+ if !@actString && $S{shared_sbar};
+ push @actString, $curLine;
+ $curLine = undef;
+ $curLen = 0;
+ }
+ elsif (defined $curLine) {
+ $curLine .= $separator[$mouse_header_check % @separator];
+ $curLen += $sepLen[$mouse_header_check % @sepLen];
+ }
+ }
+ $curLine .= $it;
+ if (exists $mouse_coords{refnum}{$mouse_header_check}) {
+ $mouse_coords{scalar @actString}{ $_ } = $mouse_coords{refnum}{$mouse_header_check}
+ for $curLen .. $curLen + $itemLen - 1;
+ }
+ $curLen += $itemLen;
+ }
+ continue {
+ ++$mouse_header_check;
+ }
+ $curLen -= $S{shared_sbar}[0]
+ if !@actString && $S{shared_sbar};
+ push @actString, $curLine if $curLen;
+}
+
+sub remake {
+ my %abbrevList;
+ my @wins = window_list();
+ if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) {
+ _calculate_abbrev(\@wins, \%abbrevList);
+ }
+
+ %mouse_coords = ( refnum => +{} );
+ _calculate_items(\@wins, \%abbrevList);
+
+ unless ($VIEWER_MODE) {
+ _spread_items();
+
+ push @actString, undef unless @actString || $S{all_disable};
+ }
+}
+
+sub update_wl {
+ return if $BLOCK_ALL;
+ remake();
+
+ Irssi::statusbar_items_redraw(set $_) for keys %statusbars;
+
+ unless ($VIEWER_MODE) {
+ Irssi::timeout_add_once(100, 'syncLines', undef);
+ }
+ else {
+ syncViewer();
+ }
+}
+
+sub screenFullRedraw {
+ my ($window) = @_;
+ if (!ref $window or $window->{refnum} == Irssi::active_win->{refnum}) {
+ $viewer{fullRedraw} = 1 if $viewer{client};
+ $settings_str = '';
+ &setup_changed;
+ }
+}
+
+sub restartViewerServer {
+ if ($VIEWER_MODE) {
+ stop_viewer();
+ start_viewer();
+ }
+}
+
+sub _simple_quote {
+ my @r = map {
+ my $x = $_;
+ $x =~ s/'/'"'"'/g;
+ $x = "'$x'";
+ } @_;
+ wantarray ? @r : shift @r
+}
+
+sub _viewer_command_replace_format {
+ my ($ecmd, @args) = @_;
+ my $file = _simple_quote(SCRIPT_FILE());
+ my $path = _simple_quote($viewer{path});
+ my @env;
+ for my $env (shellwords($S{viewer_launch_env})) {
+ if ($env =~ /^(\w+)(?:=(.*))$/) {
+ push @env, "AWL_$1=$2"
+ }
+ }
+ my $cmd = join ' ',
+ (@env ? ('env', _simple_quote(@env)) : ()),
+ 'perl', $file, '-1', _simple_quote(@args), $path;
+ $ecmd =~ s{%(%|\w+)}{
+ my $sub = $1;
+ if ($sub eq '%') {
+ '%'
+ }
+ elsif ($sub =~ /^(q*)A(.*)/) {
+ my $ret = $cmd;
+ for (1..length $1) {
+ $ret = _simple_quote($ret);
+ }
+ "$ret$2"
+ }
+ else {
+ "%$sub"
+ }
+ }gex;
+ $ecmd
+}
+
+sub start_viewer {
+ unlink $viewer{path} if -S $viewer{path} || -p _;
+
+ $viewer{server} = IO::Socket::UNIX->new(
+ Type => SOCK_STREAM,
+ Local => $viewer{path},
+ Listen => 1
+ );
+ unless ($viewer{server}) {
+ $viewer{msg} = "Viewer: $!";
+ $viewer{retry} = Irssi::timeout_add_once(5000, 'retry_viewer', 1);
+ return;
+ }
+ $viewer{server}->blocking(0);
+ set_viewer_mode_hint();
+ $viewer{server_tag} = Irssi::input_add($viewer{server}->fileno, INPUT_READ, 'vi_connected', undef);
+
+ if ($S{viewer_launch}) {
+ if ((defined $ENV{TMUX_PANE} && length $ENV{TMUX_PANE}) && (defined $ENV{TMUX} && length $ENV{TMUX}) && lc $S{viewer_tmux_position} ne 'custom') {
+ my $cmd = _viewer_command_replace_format('%qA', '-p', lc $S{viewer_tmux_position});
+ Irssi::command("exec - tmux neww -d $cmd 2>&1 &");
+ }
+ elsif ((defined $ENV{WINDOWID} && length $ENV{WINDOWID}) && (defined $ENV{DISPLAY} && length $ENV{DISPLAY}) && length $S{viewer_xwin_command} && $S{viewer_xwin_command} =~ /\S/) {
+ my $cmd = _viewer_command_replace_format($S{viewer_xwin_command});
+ Irssi::command("exec - $cmd 2>&1 &");
+ }
+ elsif (length $S{viewer_custom_command} && $S{viewer_custom_command} =~ /\S/) {
+ my $cmd = _viewer_command_replace_format($S{viewer_custom_command});
+ Irssi::command("exec - $cmd 2>&1 &");
+ }
+ }
+}
+
+sub set_viewer_mode_hint {
+ return unless $viewer{server};
+ if ($S{no_mode_hint}) {
+ $viewer{msg} = undef;
+ }
+ else {
+ my ($name) = __PACKAGE__ =~ /::([^:]+)$/;
+ $viewer{msg} = "Run $name from the shell or switch to sbar mode";
+ }
+}
+
+sub retry_viewer {
+ start_viewer();
+}
+
+sub vi_close_client {
+ Irssi::input_remove(delete $viewer{client_tag}) if exists $viewer{client_tag};
+ $viewer{client}->close if $viewer{client};
+ delete $viewer{client};
+ delete $viewer{client_keymap};
+ delete $viewer{client_settings};
+ delete $viewer{client_env};
+ delete $viewer{fullRedraw};
+}
+
+sub vi_connected {
+ vi_close_client();
+ $viewer{client} = $viewer{server}->accept or return;
+ $viewer{client}->blocking(0);
+ $viewer{client_tag} = Irssi::input_add($viewer{client}->fileno, INPUT_READ, 'vi_clientinput', undef);
+ syncViewer();
+}
+
+use constant VIEWER_BLOCK_SIZE => 1024;
+sub vi_clientinput {
+ if ($viewer{client}->read(my $buf, VIEWER_BLOCK_SIZE)) {
+ $viewer{rcvbuf} .= $buf;
+ if ($viewer{rcvbuf} =~ s/^(?:(active|\d+)|(last|up|down))\n//igm) {
+ if (defined $2) {
+ Irssi::command("window $2");
+ }
+ elsif (lc $1 eq 'active' && $viewer{use_ack}) {
+ Irssi::command("ack");
+ }
+ else {
+ Irssi::command("window goto $1");
+ }
+ }
+ }
+ else {
+ vi_close_client();
+ Irssi::timeout_add_once(100, 'syncViewer', undef);
+ }
+}
+
+sub stop_viewer {
+ Irssi::timeout_remove(delete $viewer{retry}) if exists $viewer{retry};
+ vi_close_client();
+ Irssi::input_remove(delete $viewer{server_tag}) if exists $viewer{server_tag};
+ return unless $viewer{server};
+ $viewer{server}->close;
+ delete $viewer{server};
+}
+sub _encode_var {
+ my $str;
+ while (@_) {
+ my ($name, $var) = splice @_, 0, 2;
+ my $type = ref $var ? $var =~ /HASH/ ? 'map' : $var =~ /ARRAY/ ? 'list' : '' : '';
+ $str .= "\n\U$name$type\_begin\n";
+ if ($type eq 'map') {
+ no warnings 'numeric';
+ $str .= " $_\n ${$var}{$_}\n" for sort { $a <=> $b || $a cmp $b } keys %$var;
+ }
+ elsif ($type eq 'list') {
+ $str .= " $_\n" for @$var;
+ }
+ else {
+ $str .= " $var\n";
+ }
+ $str .= "\U$name$type\_end\n";
+ }
+ $str
+}
+sub syncViewer {
+ if ($viewer{client}) {
+ @actString = ();
+ if ($currentLines) {
+ killOldStatus();
+ $currentLines = 0;
+ }
+ my $str;
+ unless ($viewer{client_keymap}) {
+ $str .= _encode_var('key', +{ %nummap, %specialmap });
+ $viewer{client_keymap} = 1;
+ }
+ unless ($viewer{client_settings}) {
+ $str .= _encode_var(
+ block => $S{block},
+ ha => $S{height_adjust},
+ mc => $S{maxcolumns},
+ ml => $S{maxlines},
+ );
+ $viewer{client_settings} = 1;
+ }
+ unless ($viewer{client_env}) {
+ $str .= _encode_var(irssienv => +{
+ (defined $ENV{TMUX_PANE} && length $ENV{TMUX_PANE}) && (defined $ENV{TMUX} && length $ENV{TMUX}) ?
+ (tmux_pane => $ENV{TMUX_PANE},
+ tmux_srv => $ENV{TMUX}) : (),
+ (defined $ENV{WINDOWID} && length $ENV{WINDOWID}) ?
+ (xwinid => $ENV{WINDOWID}) : (),
+ });
+ $viewer{client_env} = 1;
+ }
+ my $separator = Irssi::current_theme->get_format(__PACKAGE__, set 'separator');
+ my $sepLen = sb_length($separator);
+ my $item_bg = Irssi::current_theme->get_format(__PACKAGE__, set 'viewer_item_bg');
+ $str .= _encode_var(redraw => 1) if delete $viewer{fullRedraw};
+ $str .= _encode_var(separator => $separator,
+ seplen => $sepLen,
+ itembg => $item_bg,
+ mouse => $mouse_coords{refnum},
+ key2 => \%wnmap_exp,
+ win => \@win_items);
+
+ my $was = $viewer{client}->blocking(1);
+ $viewer{client}->print($str);
+ $viewer{client}->blocking($was);
+ }
+ elsif ($viewer{server}) {
+ if (defined $viewer{msg}) {
+ @actString = ((uc setc()).": $viewer{msg}");
+ }
+ else {
+ @actString = ();
+ }
+ }
+ elsif (defined $viewer{msg}) {
+ @actString = ((uc setc()).": $viewer{msg}");
+ }
+ if (@actString) {
+ Irssi::timeout_add_once(100, 'syncLines', undef);
+ }
+ elsif ($currentLines) {
+ killOldStatus();
+ $currentLines = 0;
+ }
+}
+
+sub reset_awl {
+ Irssi::timeout_remove($shade_line_timer) if $shade_line_timer; $shade_line_timer = undef;
+ my $was_sort = $S{sort} // '';
+ my $was_xform = $S{xform} // '';
+ my $was_shared = $S{shared_sbar};
+ my $was_no_hint = $S{no_mode_hint};
+ %S = (
+ sort => Irssi::settings_get_str( set 'sort'),
+ fancy_abbrev => Irssi::settings_get_str('fancy_abbrev'),
+ xform => Irssi::settings_get_str( set 'custom_xform'),
+ block => Irssi::settings_get_int( set 'block'),
+ banned_on => Irssi::settings_get_bool('banned_channels_on'),
+ prefer_name => Irssi::settings_get_bool(set 'prefer_name'),
+ hide_data => Irssi::settings_get_int( set 'hide_data'),
+ hide_name => Irssi::settings_get_int( set 'hide_name_data'),
+ hide_empty => Irssi::settings_get_int( set 'hide_empty'),
+ sbar_maxlen => Irssi::settings_get_bool(set 'sbar_maxlength'),
+ placement => Irssi::settings_get_str( set 'placement'),
+ position => Irssi::settings_get_int( set 'position'),
+ maxlines => Irssi::settings_get_int( set 'maxlines'),
+ maxcolumns => Irssi::settings_get_int( set 'maxcolumns'),
+ all_disable => Irssi::settings_get_bool(set 'all_disable'),
+ height_adjust => Irssi::settings_get_int( set 'height_adjust'),
+ mouse_offset => Irssi::settings_get_int( set 'mouse_offset'),
+ mouse_scroll => Irssi::settings_get_int( 'mouse_scroll'),
+ mouse_escape => Irssi::settings_get_int( 'mouse_escape'),
+ line_shade => Irssi::settings_get_time(set 'last_line_shade'),
+ no_mode_hint => Irssi::settings_get_bool(set 'no_mode_hint'),
+ viewer_launch => Irssi::settings_get_bool(set 'viewer_launch'),
+ viewer_launch_env => Irssi::settings_get_str(set 'viewer_launch_env'),
+ viewer_xwin_command => Irssi::settings_get_str(set 'viewer_xwin_command'),
+ viewer_custom_command => Irssi::settings_get_str(set 'viewer_custom_command'),
+ viewer_tmux_position => Irssi::settings_get_str(set 'viewer_tmux_position'),
+ );
+ $S{fancy_strict} = $S{fancy_abbrev} =~ /^strict/i;
+ $S{fancy_head} = $S{fancy_abbrev} =~ /^head/i;
+ my $shared = Irssi::settings_get_str(set 'shared_sbar');
+ if ($shared =~ /^(\d+)([<])(\d+)$/) {
+ $S{shared_sbar} = [$1, $2, $3];
+ }
+ else {
+ Irssi::settings_set_str(set 'shared_sbar', 'OFF');
+ $S{shared_sbar} = undef;
+ }
+ lock_keys(%S);
+ if ($was_sort ne $S{sort}) {
+ $print_text_activity = undef;
+ my @sort_order = grep { @$_ > 4 } map {
+ s/^\s*//;
+ my $reverse = s/^\W*\K[-!]//;
+ my $undef_check = s/^\W*\K~// ? 1 : undef;
+ my $equal_check = s/=(.*)\s?$// ? $1 : undef;
+ s/\s*$//;
+ my $ignore_case = s/#i$// ? 1 : undef;
+
+ $print_text_activity = 1 if $_ eq 'last_line';
+
+ my @path = split '/';
+ my $class_check = @path && $path[-1] =~ s/(::.*)$// ? $1 : undef;
+
+ [ $reverse ? -1 : 1, $undef_check, $equal_check, $class_check, $ignore_case, @path ]
+ } "$S{sort}," =~ /([^+,]*|[^+,]*=[^,]*?\s(?=\+)|[^+,]*=[^,]*)[+,]/g;
+ $window_sort_func = sub {
+ no warnings qw(numeric uninitialized);
+ for my $so (@sort_order) {
+ my @x = map {
+ my $ret = 0;
+ $_ = lc1459($_) if defined $_ && !ref $_ && $so->[4];
+ $ret = $_ eq ($so->[4] ? lc1459($so->[2]) : $so->[2]) ? 1 : -1 if defined $so->[2];
+ $ret = defined $_ ? ($ret || -3) : 3 if $so->[1];
+ $ret = ref $_ && $_->isa('Irssi'.$so->[3]) ? 2 : ($ret || -2) if $so->[3];
+ -$ret || $_
+ }
+ map {
+ reduce { return unless ref $a; $a->{$b} } $_, @{$so}[5..$#$so]
+ } $a, $b;
+ return ((($x[0] <=> $x[1] || $x[0] cmp $x[1]) * $so->[0]) || next);
+ }
+ return ($a->{refnum} <=> $b->{refnum});
+ };
+ }
+ if ($was_xform ne $S{xform}) {
+ if ($S{xform} !~ /\S/) {
+ $custom_xform = undef;
+ }
+ else {
+ my $script_pkg = __PACKAGE__ . '::custom_xform';
+ local $@;
+ $custom_xform = eval qq{
+package $script_pkg;
+use strict;
+no warnings;
+our (\$QUERY, \$CHANNEL, \$TAG, \$NAME);
+return sub {
+# line 1 @{[ set 'custom_xform' ]}\n$S{xform}\n}};
+ if ($@) {
+ $@ =~ /^(.*)/;
+ print '%_'.(set 'custom_xform').'%_ did not compile: '.$1;
+ }
+ }
+ }
+
+ my $new_settings = join "\n", $VIEWER_MODE
+ ? ("\\", $S{block}, $S{height_adjust}, $S{maxlines}, $S{maxcolumns})
+ : ("!", $S{placement}, $S{position});
+
+ if ($settings_str ne $new_settings) {
+ @actString = ();
+ %abbrev_cache = ();
+ $currentLines = 0;
+ killOldStatus();
+ delete $viewer{client_settings};
+ $settings_str = $new_settings;
+ }
+
+ my $was_mouse_mode = $MOUSE_ON;
+ if ($MOUSE_ON = Irssi::settings_get_bool(set 'mouse') and !$was_mouse_mode) {
+ install_mouse();
+ }
+ elsif ($was_mouse_mode and !$MOUSE_ON) {
+ uninstall_mouse();
+ }
+
+ my $path = Irssi::settings_get_str(set 'path');
+ my $was_viewer_mode = $VIEWER_MODE;
+ if ($was_viewer_mode &&
+ defined $viewer{path} && $viewer{path} ne $path) {
+ stop_viewer();
+ $was_viewer_mode = 0;
+ }
+ elsif ($was_viewer_mode && $S{no_mode_hint} != $was_no_hint + 0) {
+ set_viewer_mode_hint();
+ }
+ $viewer{path} = $path;
+ if ($VIEWER_MODE = Irssi::settings_get_bool(set 'viewer') and !$was_viewer_mode) {
+ start_viewer();
+ }
+ elsif ($was_viewer_mode and !$VIEWER_MODE) {
+ stop_viewer();
+ }
+
+ %banned_channels = map { lc1459(to_uni($_)) => undef }
+ split ' ', Irssi::settings_get_str('banned_channels');
+
+ my @sb_base = split /\177/, sb_format_expand("{sb \177}"), 2;
+ $sb_base_width_pre = sb_length($sb_base[0]);
+ $sb_base_width_post = sb_length($sb_base[1]);
+ $sb_base_width = $sb_base_width_pre + $sb_base_width_post;
+
+ if ($print_text_activity && $S{line_shade}) {
+ $shade_line_timer = Irssi::timeout_add(max(10 * GLOB_QUEUE_TIMER, 100*$S{line_shade}**(1/3)), 'wl_changed', undef);
+ }
+
+ $CHANGED{AWINS} = 1;
+}
+
+sub stop_mouse_tracking {
+ print STDERR "\e[?1005l\e[?1000l";
+}
+sub start_mouse_tracking {
+ print STDERR "\e[?1000h\e[?1005h";
+}
+sub install_mouse {
+ Irssi::command_bind('mouse_xterm' => 'mouse_xterm');
+ Irssi::command('^bind meta-[M command mouse_xterm');
+ Irssi::signal_add_first('gui key pressed' => 'mouse_key_hook');
+ start_mouse_tracking();
+}
+sub uninstall_mouse {
+ stop_mouse_tracking();
+ Irssi::signal_remove('gui key pressed' => 'mouse_key_hook');
+ Irssi::command('^bind -delete meta-[M');
+ Irssi::command_unbind('mouse_xterm' => 'mouse_xterm');
+}
+
+sub awl_mouse_event {
+ return if $VIEWER_MODE;
+ if ((($_[0] == 3 and $_[3] == 0)
+ || $_[0] == 64 || $_[0] == 65) and
+ $_[1] == $_[4] and $_[2] == $_[5]) {
+ my $top = lc $S{placement} eq 'top';
+ my ($pos, $line) = @_[1 .. 2];
+ unless ($top) {
+ $line -= $screenHeight;
+ $line += $currentLines;
+ $line += $S{mouse_offset};
+ }
+ else {
+ $line -= $S{mouse_offset};
+ }
+ $pos -= $sb_base_width_pre;
+ return if $line < 0 || $line >= $currentLines;
+ if ($_[0] == 64) {
+ Irssi::command('window up');
+ }
+ elsif ($_[0] == 65) {
+ Irssi::command('window down');
+ }
+ elsif (exists $mouse_coords{$line}{$pos}) {
+ my $win = $mouse_coords{$line}{$pos};
+ Irssi::command('window ' . $win);
+ }
+ Irssi::signal_stop;
+ }
+}
+
+sub mouse_scroll_event {
+ return unless $S{mouse_scroll};
+ if (($_[3] == 64 or $_[3] == 65) and
+ $_[0] == $_[3] and $_[1] == $_[4] and $_[2] == $_[5]) {
+ my $cmd = 'scrollback goto ' . ($_[3] == 64 ? '-' : '+') . $S{mouse_scroll};
+ Irssi::active_win->command($cmd);
+ Irssi::signal_stop;
+ }
+ elsif ($_[0] == 64 or $_[0] == 65) {
+ Irssi::signal_stop;
+ }
+}
+
+sub mouse_escape {
+ return unless $S{mouse_escape} > 0;
+ if ($_[0] == 3) {
+ my $tm = $S{mouse_escape};
+ $tm *= 1000 if $tm < 1000;
+ stop_mouse_tracking();
+ Irssi::timeout_add_once($tm, 'start_mouse_tracking', undef);
+ Irssi::signal_stop;
+ }
+}
+
+{ sub UNLOAD {
+ @actString = ();
+ killOldStatus();
+ stop_viewer() if $VIEWER_MODE;
+ uninstall_mouse() if $MOUSE_ON;
+ }
+}
+
+sub addPrintTextHook { # update on print text
+ return if $BLOCK_ALL;
+ return unless $print_text_activity;
+ return if $_[0]->{level} == 262144 and $_[0]->{target} eq ''
+ and !defined($_[0]->{server});
+ &wl_changed;
+}
+
+sub block_event_window_change {
+ Irssi::signal_stop;
+}
+
+sub update_awins {
+ {
+ my @wins = Irssi::windows;
+ local $BLOCK_ALL = 1;
+ Irssi::signal_add_first('window changed' => 'block_event_window_change');
+ my $bwin =
+ my $awin = Irssi::active_win;
+ my $lwin;
+ my $defer_irssi_broken_last;
+ unless ($wins[0]{refnum} == $awin->{refnum}) {
+ # special case: more than 1 last win, so /win last;
+ # /win last doesn't come back to the current window. eg. after
+ # connect & autojoin; we can't handle this situation, bail out
+ $defer_irssi_broken_last = 1;
+ }
+ else {
+ $awin->command('window last');
+ $lwin = Irssi::active_win;
+ $lwin->command('window last');
+ $defer_irssi_broken_last = $lwin->{refnum} == $bwin->{refnum};
+ }
+ my $awin_counter = 0;
+ Irssi::signal_remove('window changed' => 'block_event_window_change');
+ unless ($defer_irssi_broken_last) {
+ # we need to keep the fe-windows code running here
+ Irssi::signal_add_priority('window changed' => 'block_event_window_change', -99);
+ %awins = %wnmap_exp = ();
+ do {
+ Irssi::active_win->command('window up');
+ $awin = Irssi::active_win;
+ $awins{$awin->{refnum}} = undef;
+ ++$awin_counter;
+ } until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins);
+ Irssi::signal_remove('window changed' => 'block_event_window_change');
+
+ Irssi::signal_add_first('window changed' => 'block_event_window_change');
+ for my $key (keys %wnmap) {
+ next unless Irssi::window_find_name($key) || Irssi::window_find_item($key);
+ $awin->command("window goto $key");
+ my $cwin = Irssi::active_win;
+ $wnmap_exp{ $cwin->{refnum} } = $wnmap{$key};
+ $cwin->command('window last')
+ if $cwin->{refnum} != $awin->{refnum};
+ }
+ for my $win (reverse @wins) { # restore original window order
+ Irssi::active_win->command('window '.$win->{refnum});
+ }
+ $awin->command('window '.$lwin->{refnum}); # restore last win
+ Irssi::active_win->command('window last');
+ Irssi::signal_remove('window changed' => 'block_event_window_change');
+ }
+ }
+ $CHANGED{WL} = 1;
+}
+
+sub resizeTerm {
+ if (defined (my $r = `stty size 2>/dev/null`)) {
+ ($screenHeight, $screenWidth) = split ' ', $r;
+ $CHANGED{SETUP} = 1;
+ }
+ else {
+ $CHANGED{SIZE} = 1;
+ }
+}
+
+sub awl_refresh {
+ $globTime = undef;
+ resizeTerm() if delete $CHANGED{SIZE};
+ reset_awl() if delete $CHANGED{SETUP};
+ update_awins() if delete $CHANGED{AWINS};
+ update_wl() if delete $CHANGED{WL};
+}
+
+sub termsize_changed { $CHANGED{SIZE} = 1; &queue_refresh; }
+sub setup_changed { $CHANGED{SETUP} = 1; &queue_refresh; }
+sub awins_changed { $CHANGED{AWINS} = 1; &queue_refresh; }
+sub wl_changed { $CHANGED{WL} = 1; &queue_refresh; }
+
+sub window_changed {
+ &awins_changed if $_[1];
+}
+
+sub queue_refresh {
+ return if $BLOCK_ALL;
+ Irssi::timeout_remove($globTime)
+ if defined $globTime; # delay the update further
+ $globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awl_refresh', undef);
+}
+
+sub awl_init {
+ termsize_changed();
+ update_keymap();
+}
+
+sub runsub {
+ my $cmd = shift;
+ sub {
+ my ($data, $server, $item) = @_;
+ Irssi::command_runsub($cmd, $data, $server, $item);
+ };
+}
+
+Irssi::signal_register({
+ 'gui mouse' => [qw/int int int int int int/],
+ });
+{ my $broken_expandos = (Irssi::version >= 20081128 && Irssi::version < 20110210)
+ ? sub { my $x = shift; $x =~ s/\$\{cumode_space\}/ /; $x } : undef;
+ Irssi::theme_register([
+ map { $broken_expandos ? $broken_expandos->($_) : $_ }
+ set 'display_nokey' => '$N${cumode_space}$H$C$S',
+ set 'display_key' => '$Q${cumode_space}$H$C$S',
+ set 'display_nokey_visible' => '%2$N${cumode_space}$H$C$S',
+ set 'display_key_visible' => '%2$Q${cumode_space}$H$C$S',
+ set 'display_nokey_active' => '%1$N${cumode_space}$H$C$S',
+ set 'display_key_active' => '%1$Q${cumode_space}$H$C$S',
+ set 'display_header' => '%8$C|${N}',
+ set 'name_display' => '$0',
+ set 'separator' => ' ',
+ set 'separator2' => '',
+ set 'viewer_item_bg' => sb_format_expand('{sb_background}'),
+ ]);
+}
+Irssi::settings_add_bool(setc, set 'prefer_name', 0); #
+Irssi::settings_add_int( setc, set 'hide_empty', 0); #
+Irssi::settings_add_int( setc, set 'hide_data', 0); #
+Irssi::settings_add_int( setc, set 'hide_name_data', 0); #
+Irssi::settings_add_int( setc, set 'maxlines', 9); #
+Irssi::settings_add_int( setc, set 'maxcolumns', 4); #
+Irssi::settings_add_int( setc, set 'block', 15); #
+Irssi::settings_add_bool(setc, set 'sbar_maxlength', 1); #
+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', 1); #
+Irssi::settings_add_bool(setc, set 'viewer', 1); #
+Irssi::settings_add_str( setc, set 'shared_sbar', 'OFF'); #
+Irssi::settings_add_bool(setc, set 'mouse', 0); #
+Irssi::settings_add_str( setc, set 'path', Irssi::get_irssi_dir . '/_windowlist'); #
+Irssi::settings_add_str( setc, set 'custom_xform', ''); #
+Irssi::settings_add_time(setc, set 'last_line_shade', '0'); #
+Irssi::settings_add_int( setc, set 'mouse_offset', 1); #
+Irssi::settings_add_int( setc, 'mouse_scroll', 3); #
+Irssi::settings_add_int( setc, 'mouse_escape', 1); #
+Irssi::settings_add_str( setc, 'banned_channels', '');
+Irssi::settings_add_bool(setc, 'banned_channels_on', 1);
+Irssi::settings_add_str( setc, 'fancy_abbrev', 'fancy'); #
+Irssi::settings_add_bool(setc, set 'no_mode_hint', 0); #
+Irssi::settings_add_bool(setc, set 'viewer_launch', 1); #
+Irssi::settings_add_str( setc, set 'viewer_launch_env', ''); #
+Irssi::settings_add_str( setc, set 'viewer_tmux_position', 'left'); #
+Irssi::settings_add_str( setc, set 'viewer_xwin_command', 'xterm +sb -e %A'); #
+Irssi::settings_add_str( setc, set 'viewer_custom_command', ''); #
+
+Irssi::signal_add_last({
+ 'setup changed' => 'setup_changed',
+ 'print text' => 'addPrintTextHook',
+ 'terminal resized' => 'termsize_changed',
+ 'setup reread' => 'screenFullRedraw',
+ 'window hilight' => 'wl_changed',
+ 'command format' => 'wl_changed',
+});
+Irssi::signal_add({
+ 'window changed' => 'window_changed',
+ 'window item changed' => 'wl_changed',
+ 'window changed automatic' => 'window_changed',
+ 'window created' => 'awins_changed',
+ 'window destroyed' => 'awins_changed',
+ 'window name changed' => 'wl_changed',
+ 'window refnum changed' => 'wl_changed',
+});
+Irssi::signal_add_last('gui mouse' => 'mouse_escape');
+Irssi::signal_add_last('gui mouse' => 'mouse_scroll_event');
+Irssi::signal_add_last('gui mouse' => 'awl_mouse_event');
+Irssi::command_bind( setc() => runsub(setc()) );
+Irssi::command_bind( setc() . ' redraw' => 'screenFullRedraw' );
+Irssi::command_bind( setc() . ' restart' => 'restartViewerServer' );
+
+{
+ my $l = set 'shared';
+ {
+ no strict 'refs';
+ *{$l} = $awl_shared_empty;
+ }
+ Irssi::statusbar_item_register($l, '$0', $l);
+}
+
+awl_init();
+
+# Mouse script based on irssi mouse patch by mirage
+{ my $mouse_status = -1; # -1:off 0,1,2:filling mouse_combo
+ my @mouse_combo; # 0:button 1:x 2:y
+ my @mouse_previous; # previous contents of mouse_combo
+
+ sub mouse_xterm_off {
+ $mouse_status = -1;
+ }
+ sub mouse_xterm {
+ $mouse_status = 0;
+ Irssi::timeout_add_once(10, 'mouse_xterm_off', undef);
+ }
+
+ sub mouse_key_hook {
+ my ($key) = @_;
+ if ($mouse_status != -1) {
+ if ($mouse_status == 0) {
+ @mouse_previous = @mouse_combo;
+ #if @mouse_combo && $mouse_combo[0] < 64;
+ }
+ $mouse_combo[$mouse_status] = $key - 32;
+ $mouse_status++;
+ if ($mouse_status == 3) {
+ $mouse_status = -1;
+ # match screen coordinates
+ $mouse_combo[1]--;
+ $mouse_combo[2]--;
+ Irssi::signal_emit('gui mouse', @mouse_combo[0 .. 2], @mouse_previous[0 .. 2]);
+ }
+ Irssi::signal_stop;
+ }
+ }
+}
+
+sub string_LCSS {
+ my $str = join "\0", @_;
+ (sort { length $b <=> length $a } $str =~ /(?=(.+).*\0.*\1)/g)[0]
+}
+
+{ package Irssi::Nick }
+
+UNITCHECK
+{ package AwlViewer;
+ use strict;
+ use warnings;
+ no warnings 'redefine';
+ use Encode;
+ use IO::Socket::UNIX;
+ use IO::Select;
+ use List::Util qw(max);
+ use constant BLOCK_SIZE => 1024;
+ use constant RECONNECT_TIME => 5;
+
+ my $sockpath;
+
+ our $VERSION = '0.8';
+
+ our ($got_int, $resized, $timeout);
+
+ my %vars;
+ my (%c2w, @seqlist);
+ my %mouse_coords;
+ my (@mouse, @last_mouse);
+ my ($err, $sock, $loop);
+ my ($keybuf, $rcvbuf);
+ my @screen;
+ my ($screenHeight, $screenWidth);
+ my ($disp_update, $fs_open, $one_shot_integration, $one_shot_resize);
+ my $integration_position;
+ my $show_title_bar;
+
+ sub connect_it {
+ $sock = IO::Socket::UNIX->new(
+ Type => SOCK_STREAM,
+ Peer => $sockpath,
+ );
+ unless ($sock) {
+ $err = $!;
+ return;
+ }
+ $sock->blocking(0);
+ $loop->add($sock);
+ }
+
+ sub remove_conn {
+ my $fh = shift;
+ $loop->remove($fh);
+ $fh->close;
+ $sock = undef;
+ %vars = ();
+ @screen = ();
+ }
+
+ { package Terminfo; # xterm
+ sub civis { "\e[?25l" }
+ sub sc { "\e7" }
+ sub cup { "\e[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' }
+ sub el { "\e[K" }
+ sub rc { "\e8" }
+ sub cnorm { "\e[?25h" }
+ sub setab { "\e[4" . $_[0] . 'm' }
+ sub setaf { "\e[3" . $_[0] . 'm' }
+ sub setaf16 { "\e[9" . $_[0] . 'm' }
+ sub setab16 { "\e[10" . $_[0] . 'm' }
+ sub setaf256 { "\e[38;5;" . $_[0] . 'm' }
+ sub setab256 { "\e[48;5;" . $_[0] . 'm' }
+ sub sgr0 { "\e[0m" }
+ sub bold { "\e[1m" }
+ sub it { "\e[3m" }
+ sub ul { "\e[4m" }
+ sub blink { "\e[5m" }
+ sub rev { "\e[7m" }
+ sub op { "\e[39;49m" }
+ sub exit_bold { "\e[22m" }
+ sub exit_it { "\e[23m" }
+ sub exit_ul { "\e[24m" }
+ sub exit_blink { "\e[25m" }
+ sub exit_rev { "\e[27m" }
+ sub smcup { "\e[?1049h" }
+ sub rmcup { "\e[?1049l" }
+ sub smmouse { "\e[?1000h\e[?1005h" }
+ sub rmmouse { "\e[?1005l\e[?1000l" }
+ }
+
+ sub init {
+ $sockpath = shift // "$ENV{HOME}/.irssi/_windowlist";
+ STDOUT->autoflush(1);
+ printf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name};
+
+ `stty -icanon -echo`;
+
+ $loop = IO::Select->new;
+ STDIN->blocking(0);
+ $loop->add(\*STDIN);
+
+ $SIG{INT} = sub {
+ $got_int = 1
+ };
+ $SIG{WINCH} = sub {
+ $resized = 1
+ };
+
+ $resized = 3;
+
+ $disp_update = 2;
+
+ $show_title_bar = 1;
+ }
+
+ sub enter_fs {
+ return if $fs_open;
+ safe_print(Terminfo::rc, Terminfo::smcup, Terminfo::civis, Terminfo::smmouse);
+ $fs_open = 1;
+ }
+
+ sub leave_fs {
+ return unless $fs_open;
+ safe_print(Terminfo::rmmouse, Terminfo::cnorm, Terminfo::rmcup);
+ safe_print(sprintf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name}) if $_[0];
+
+ $fs_open = 0;
+ }
+
+ sub end_prog {
+ leave_fs();
+ STDIN->blocking(1);
+ `stty sane`;
+ printf "\r%s%sthanks for using %s\n", Terminfo::rc, Terminfo::el, $::IRSSI{name};
+ }
+
+ sub safe_print {
+ my $st = STDIN->blocking(1);
+ print @_;
+ STDIN->blocking($st);
+ }
+
+ sub safe_qx {
+ my $st = STDIN->blocking(1);
+ my $ret = `$_[0]`;
+ STDIN->blocking($st);
+ $ret
+ }
+
+ sub safe_print_sock {
+ return unless $sock;
+ my $was = $sock->blocking(1);
+ $sock->print(@_);
+ $sock->blocking($was);
+ }
+
+ sub process_recv {
+ my $need = 0;
+ while ($rcvbuf =~ s/\n(.+)_BEGIN\n((?: .*\n)*)\1_END\n//) {
+ my $var = lc $1;
+ my $data = $2;
+ my @data = split "\n ", "\n$data ", -1;
+ shift @data; pop @data;
+ my $itembg = $vars{itembg};
+ if ($var =~ s/list$//) {
+ $vars{$var} = \@data;
+ }
+ elsif ($var =~ s/map$//) {
+ $vars{$var} = +{ @data };
+ }
+ else {
+ $vars{$var} = join "\n", @data;
+ }
+ $need = 1 if $var eq 'win';
+ $need = 1 if $var eq 'redraw' && $vars{$var};
+ if (($itembg//'') ne ($vars{itembg}//'')) {
+ $need = $vars{redraw} = 1;
+ }
+ _build_keymap() if $var eq 'key2';
+ }
+ $need
+ }
+
+ { my %ansi_table;
+ my ($i, $j, $k) = (0, 0, 0);
+ my %term_state;
+ sub reset_term_state { my %old_term = %term_state; %term_state = (); %old_term }
+ sub set_term_state { my %old_term = %term_state; %term_state = @_; %old_term }
+ %ansi_table = (
+ # fe-common::core::formats.c:format_expand_styles
+ (map { my $t = $i++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setab16 : \&Terminfo::setab;
+ $n->($t) }) } (split //, '01234567' )),
+ (map { my $t = $j++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf16 : \&Terminfo::setaf;
+ $n->($t) }) } (split //, 'krgybmcw' )),
+ (map { my $t = $k++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf : \&Terminfo::setaf16;
+ $n->($t) }) } (split //, 'KRGYBMCW')),
+ # reset
+ n => sub { $term_state{hicolor} = 0; my $r = Terminfo::op;
+ for (qw(blink rev bold)) {
+ $r .= Terminfo->can("exit_$_")->() if delete $term_state{$_};
+ }
+ {
+ local $ansi_table{n} = $ansi_table{N};
+ $r .= formats_to_ansi_basic($vars{itembg});
+ }
+ $r
+ },
+ N => sub { reset_term_state(); Terminfo::sgr0 },
+ # flash/bright
+ F => sub { my $n = 'blink'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
+ # reverse
+ 8 => sub { my $n = 'rev'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
+ # bold
+ "_" => sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
+ # underline
+ U => sub { my $n = 'ul'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
+ # italic
+ I => sub { my $n = 'it'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
+ # bold, used as colour modifier if AWL_HI9 is set
+ 9 => $ENV{AWL_HI9} ? sub { $term_state{hicolor} ^= 1; '' }
+ : sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
+ # delete other stuff
+ (map { $_ => sub { '' } } (split //, ':|>#[')),
+ # escape
+ (map { my $close = $_; $_ => sub { $close } } (split //, '{}%')),
+ );
+ for my $base (0 .. 15) {
+ my $close = $base;
+ my $idx = ($close&8) | ($close&4)>>2 | ($close&2) | ($close&1)<<2;
+ $ansi_table{ (sprintf "x0%x", $close) } =
+ $ansi_table{ (sprintf "x0%X", $close) } =
+ sub { Terminfo::setab256($idx) };
+ $ansi_table{ (sprintf "X0%x", $close) } =
+ $ansi_table{ (sprintf "X0%X", $close) } =
+ sub { Terminfo::setaf256($idx) };
+ }
+ for my $plane (1 .. 6) {
+ for my $coord (0 .. 35) {
+ my $close = 16 + ($plane-1) * 36 + $coord;
+ my $ch = $coord < 10 ? $coord : chr( $coord - 10 + ord 'a' );
+ $ansi_table{ "x$plane$ch" } =
+ $ansi_table{ "x$plane\U$ch" } =
+ sub { Terminfo::setab256($close) };
+ $ansi_table{ "X$plane$ch" } =
+ $ansi_table{ "X$plane\U$ch" } =
+ sub { Terminfo::setaf256($close) };
+ }
+ }
+ for my $gray (0 .. 23) {
+ my $close = 232 + $gray;
+ my $ch = chr( $gray + ord 'a' );
+ $ansi_table{ "x7$ch" } =
+ $ansi_table{ "x7\U$ch" } =
+ sub { Terminfo::setab256($close) };
+ $ansi_table{ "X7$ch" } =
+ $ansi_table{ "X7\U$ch" } =
+ sub { Terminfo::setaf256($close) };
+ }
+ sub formats_to_ansi_basic {
+ my $o = shift;
+ $o =~ s/(%(X..|x..|.))/exists $ansi_table{$2} ? $ansi_table{$2}->() : $1/gex;
+ $o
+ }
+ }
+
+ sub _header {
+ my $str = uc ::setc();
+ my $space = int( ((abs $vars{block}) - length $str) / (1 + length $str));
+ if ($space > 0) {
+ my $ss = ' ' x $space;
+ $str = join $ss, '', (split //, $str), '';
+ }
+ my $pad = (abs $vars{block}) - length $str;
+ $str = ' ' x ($pad/2) . $str . ' ' x ($pad/2 + $pad%2);
+ $str
+ }
+
+ sub _add_item {
+ my ($i, $j, $c, $wi, $screen, $mouse) = @_;
+ $screen->[$i][$j] = "%N%n$wi";
+ if (exists $vars{mouse}{$c - 1}) {
+ $mouse->[$i][$j] = $vars{mouse}{$c - 1};
+ }
+ }
+ sub update_screen {
+ $disp_update = 0;
+ unless ($sock && exists $vars{seplen} && exists $vars{block}) {
+ leave_fs(1);
+ return;
+ }
+ enter_fs();
+ @screen = () if delete $vars{redraw};
+ %mouse_coords = ();
+ my $ncols = ($vars{seplen} + abs $vars{block}) ?
+ int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;
+ my $xenl = ($vars{seplen} + abs $vars{block})
+ && $ncols > int( ($screenWidth + $vars{seplen} - 1) / ($vars{seplen} + abs $vars{block}) );
+ my $nrows = $screenHeight - $vars{ha};
+ my @wi = @{$vars{win}//[]};
+ my $max_items = $ncols * $nrows;
+ my $c = $show_title_bar ? 1 : 0;
+ my $items = @wi + $c;
+ my $titems = $items > $max_items ? $max_items : $items;
+ my $i = 0;
+ my $j = 0;
+ my @new_screen;
+ my @new_mouse;
+ $new_screen[0][0] = _header() . ' ' x $vars{seplen}
+ if $show_title_bar;
+ unless ($nrows > $ncols) { # line layout
+ ++$j if $show_title_bar;
+ for my $wi (@wi) {
+ if ($j >= $ncols) {
+ $j = 0;
+ ++$i;
+ }
+ last if $i >= $nrows;
+ _add_item($i, $j, $show_title_bar ? $c : $c + 1,
+ $wi, \@new_screen, \@new_mouse);
+ if ($c + 1 < $titems && $j + 1 < $ncols) {
+ $new_screen[$i][$j] .= $vars{separator};
+ }
+ ++$j;
+ ++$c;
+ }
+ }
+ else { # column layout
+ ++$i if $show_title_bar;
+ for my $wi (@wi) {
+ if ($i >= $nrows) {
+ $i = 0;
+ ++$j;
+ }
+ last if $j >= $ncols;
+ _add_item($i, $j, $show_title_bar ? $c : $c + 1,
+ $wi, \@new_screen, \@new_mouse);
+ if ($c + $nrows < $titems) {
+ $new_screen[$i][$j] .= $vars{separator};
+ }
+ ++$i;
+ ++$c;
+ }
+ }
+ my $step = $vars{seplen} + abs $vars{block};
+ $i = 0;
+ my $str = Terminfo::sc . Terminfo::sgr0;
+ for (my $i = 0; $i < @new_screen; ++$i) {
+ for (my $j = 0; $j < @{$new_screen[$i]}; ++$j) {
+ if (defined $new_mouse[$i] && defined $new_mouse[$i][$j]) {
+ my $from = $j * $step;
+ $mouse_coords{$i}{$_} = $new_mouse[$i][$j]
+ for $from .. $from + abs $vars{block};
+ }
+ next if defined $screen[$i] && defined $screen[$i][$j]
+ && $screen[$i][$j] eq $new_screen[$i][$j];
+ $str .= Terminfo::cup($i, $j * $step)
+ . formats_to_ansi_basic($new_screen[$i][$j])
+ . Terminfo::sgr0;
+ $str .= Terminfo::el if $j == $#{$new_screen[$i]} && (!$xenl || $j + 1 != $ncols);
+ }
+ }
+ for (@new_screen .. $screenHeight - 1) {
+ if (!@screen || defined $screen[$_]) {
+ $str .= Terminfo::cup($_, 0) . Terminfo::sgr0 . Terminfo::el;
+ }
+ }
+ $str .= Terminfo::rc;
+ safe_print $str;
+ @screen = @new_screen;
+ }
+
+ sub handle_resize {
+ if (defined (my $r = safe_qx('stty size'))) {
+ ($screenHeight, $screenWidth) = split ' ', $r;
+ $resized = 0;
+ @screen = ();
+ $disp_update = 1;
+ if ($one_shot_integration == 2) {
+ $one_shot_resize--;
+ }
+ }
+ else {
+ }
+ }
+
+ sub _build_keymap {
+ %c2w = reverse( %{$vars{key}}, %{$vars{key2}} );
+ if (!grep { /^[+-]./ } keys %c2w) {
+ %c2w = (%c2w, map { ("-$_" => $c2w{$_}) } grep { !/^\^./ } keys %c2w);
+ }
+ %c2w = map {
+ my $key = $_;
+ s{^(-)?(\+)?(\^)?(.)}{
+ join '', (
+ ($1 ? "\e" : ''),
+ ($2 ? "\e\e" : ''),
+ ($3 ? "$4"^"@" : $4)
+ )
+ }e;
+ $_ => $c2w{$key}
+ } keys %c2w;
+ @seqlist = sort { length $b <=> length $a } keys %c2w;
+ }
+
+ sub _match_tmux {
+ (defined $ENV{TMUX} && length $ENV{TMUX}) && exists $vars{irssienv}{tmux_srv} && length $vars{irssienv}{tmux_pane}
+ && $ENV{TMUX} eq $vars{irssienv}{tmux_srv}
+ }
+
+ sub process_keys {
+ Encode::_utf8_on($keybuf);
+ my $win;
+ my $use_mouse;
+ my $maybe;
+ KEY: while (length $keybuf && !$maybe) {
+ $maybe = 0;
+ if ($keybuf =~ s/^\e\[M(.)(.)(.)//) {
+ @last_mouse = @mouse;# if @mouse && $mouse[0] < 64;
+ @mouse = map { -32 + ord } ($1, $2, $3);
+ $use_mouse = 1;
+ next KEY;
+ }
+ for my $s (@seqlist) {
+ if ($keybuf =~ s/^\Q$s//) {
+ $win = $c2w{$s};
+ $use_mouse = 0;
+ next KEY;
+ }
+ elsif (length $keybuf < length $s && $s =~ /^\Q$keybuf/) {
+ $maybe = 1;
+ }
+ }
+ unless ($maybe) {
+ substr $keybuf, 0, 1, '';
+ }
+ }
+ if ($use_mouse && @mouse && @last_mouse &&
+ $mouse[2] == $last_mouse[2] &&
+ $mouse[1] == $last_mouse[1] &&
+ ($mouse[0] == 3 || $mouse[0] == 64 || $mouse[0] == 65)) {
+ if ($mouse[0] == 64) {
+ $win = 'up';
+ }
+ elsif ($mouse[0] == 65) {
+ $win = 'down';
+ }
+ elsif (exists $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1}) {
+ $win = $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1};
+ }
+ elsif ($mouse[2] == 1 && $mouse[1] <= abs $vars{block}) {
+ $win = $last_mouse[0] != 0 ? 'last' : 'active';
+ }
+ else {
+ }
+ }
+ if (defined $win) {
+ $win =~ s/^_//;
+ safe_print_sock("$win\n");
+ if (!exists $ENV{AWL_AUTOFOCUS} || $ENV{AWL_AUTOFOCUS}) {
+ if (_match_tmux()) {
+ safe_qx("tmux selectp -t $vars{irssienv}{tmux_pane} 2>&1");
+ }
+ elsif (exists $vars{irssienv}{xwinid}) {
+ safe_qx("wmctrl -ia $vars{irssienv}{xwinid} 2>/dev/null");
+ }
+ }
+ }
+ Encode::_utf8_off($keybuf);
+ }
+
+ sub check_integration {
+ return unless $vars{irssienv};
+ return unless $sock && exists $vars{seplen} && exists $vars{block};
+ if ($one_shot_integration == 1) {
+ my $nrows = $screenHeight - $vars{ha};
+ my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;
+ my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]};
+ my $dcols_required = $nrows ? int($items/$nrows) + !!($items%$nrows) : 0;
+ my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0;
+ $rows_required = abs $vars{ml}
+ if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml}));
+ $dcols_required = abs $vars{mc}
+ if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc}));
+ my $rows = $rows_required + $vars{ha};
+ my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen};
+ if (_match_tmux()) {
+ # int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) );
+ my ($pos_flag, $before);
+ if ($integration_position eq 'left') {
+ $pos_flag = 'h';
+ $before = 1;
+ }
+ elsif ($integration_position eq 'top') {
+ $pos_flag = 'v';
+ $before = 1;
+ }
+ elsif ($integration_position eq 'right') {
+ $pos_flag = 'h';
+ }
+ else {
+ $pos_flag = 'v';
+ }
+ my @cmd = "joinp -d$pos_flag -s $ENV{TMUX_PANE} -t $vars{irssienv}{tmux_pane}";
+ push @cmd, "swapp -d -t $ENV{TMUX_PANE} -s $vars{irssienv}{tmux_pane}"
+ if $before;
+ $cols = max($cols, 2);
+ $rows = max($rows, 2);
+
+ safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1");
+ }
+ else {
+ $resized = 1;
+ #safe_qx("resize -s $screenHeight $cols 2>&1")
+ # if $cols > 0;
+ }
+ $one_shot_integration++;
+ if ($resized == 1) {
+ handle_resize();
+ resize_integration();
+ }
+ }
+ elsif ($one_shot_integration == 2) {
+ resize_integration(1);
+ }
+ }
+
+ sub resize_integration {
+ return unless $one_shot_integration;
+ return unless ($one_shot_resize//0) < 0 || shift;
+ my $nrows = $screenHeight - $vars{ha};
+ my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;
+ my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]};
+ my $dcols_required = $nrows ? (int($items/$nrows) + !!($items%$nrows)) : 0;
+ my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0;
+ $rows_required = abs $vars{ml}
+ if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml}));
+ $dcols_required = abs $vars{mc}
+ if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc}));
+ my $rows = $rows_required + $vars{ha};
+ my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen};
+ if (_match_tmux()) {
+ my $pos_flag;
+ my $before = 0;
+ if ($integration_position eq 'left') {
+ $pos_flag = 'h';
+ $before = 1;
+ }
+ elsif ($integration_position eq 'top') {
+ $pos_flag = 'v';
+ $before = 1;
+ }
+ elsif ($integration_position eq 'right') {
+ $pos_flag = 'h';
+ }
+ else {
+ $pos_flag = 'v';
+ }
+ my @cmd;
+ # hard tmux limits
+ $cols = max($cols, 2);
+ $rows = max($rows, 2);
+ if ($pos_flag eq 'h' && $cols != $screenWidth) {
+ my $change = $screenWidth - $cols;
+ my $dir = ($before ^ ($change<0)) ? 'L' : 'R';
+ push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}";
+ #push @cmd, "resizep -x $cols -t $ENV{TMUX_PANE}";
+ $one_shot_resize = 1;
+ }
+ if ($pos_flag eq 'v' && $rows != $screenHeight) {
+ #push @cmd, "resizep -y $rows -t $ENV{TMUX_PANE}";
+ my $change = $screenHeight - $rows;
+ my $dir = ($before ^ ($change<0)) ? 'U' : 'D';
+ push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}";
+ $one_shot_resize = 1;
+ }
+
+ safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1")
+ if @cmd;
+ }
+ else {
+ $cols = max($cols, 1);
+ $rows = max($rows, 1);
+ unless ($nrows > $ncols) { # line layout
+ if ($rows != $screenHeight) {
+ safe_qx("resize -s $rows $screenWidth 2>&1");
+ $one_shot_resize = 1;
+ }
+ }
+ else {
+ if ($cols != $screenWidth) {
+ safe_qx("resize -s $screenHeight $cols 2>&1");
+ $one_shot_resize = 1;
+ }
+ }
+ }
+ if ($resized == 1) {
+ handle_resize();
+ }
+ }
+
+ sub init_integration {
+ return unless $one_shot_integration;
+ if (_match_tmux()) {
+ }
+ else {
+ }
+ safe_print("\e]2;".(uc ::setc())."\e\\");
+ }
+
+ sub main {
+ require Getopt::Std;
+ my %opts;
+ Getopt::Std::getopts('1p:', \%opts);
+ my $one_shot = $opts{1};
+ $integration_position = $opts{p};
+ $one_shot_integration = 0+!!$one_shot;
+ #shift if @_ && $_[0] eq '--';
+ &init;
+ $show_title_bar = 0 if $ENV{AWL_NOTITLE};
+ init_integration();
+ until ($got_int) {
+ $timeout = undef;
+ if ($resized) {
+ if ($resized == 1) {
+ $timeout = 1;
+ $resized++;
+ }
+ else {
+ handle_resize();
+ resize_integration();
+ }
+ }
+ unless ($sock || $timeout) {
+ connect_it();
+ }
+ $timeout ||= RECONNECT_TIME unless $sock;
+ update_screen() if $disp_update;
+ SELECT: while (my @read = $loop->can_read($timeout)) {
+ for my $fh (@read) {
+ if ($fh == \*STDIN) {
+ if (read STDIN, my $buf, BLOCK_SIZE) {
+ do {
+ $keybuf .= $buf;
+ } while read STDIN, $buf, BLOCK_SIZE;
+ }
+ else {
+ $got_int = 1;
+ last SELECT;
+ }
+ }
+ else {
+ if ($fh->read(my $buf, BLOCK_SIZE)) {
+ do {
+ $rcvbuf .= $buf;
+ } while $fh->read($buf, BLOCK_SIZE);
+ }
+ else {
+ $disp_update = 1;
+ remove_conn($fh);
+ if ($one_shot) {
+ $got_int = 1;
+ last SELECT;
+ }
+ $timeout ||= RECONNECT_TIME;
+ }
+ }
+ }
+ $disp_update |= process_recv() if length $rcvbuf;
+ process_keys() if length $keybuf;
+ check_integration() if $one_shot;
+ update_screen() if $disp_update;
+ }
+ continue {
+ }
+ }
+ end_prog();
+ }
+}
+
+1;
+
+# Changelog
+# =========
+# 1.0a2
+# - new awl_viewer_launch setting and an array of related settings
+#
+# 0.9
+# - fix endless loop in awin detection code!
+# - correct colour swap in awl_viewer
+# - fix passing of alternate socket path to the viewer
+# - potential undefinedness in mouse refnum hinted at by Canopus
+# - fixed regression bug /exec -interactive
+# - add case-insensitive modifier to awl_sort
+# - run custom_xform on awl_prefer_name also
+# - avoid inconsistent active window state after awin detection
+# reported by ss
+# - revert %9-hack in the viewer prompted by discussion with pierrot
+# - fix new warning in perl 5.22
+#
+# 0.8
+# - replace fifo mode with external viewer script
+# - remove bundled cpan modules
+# - work around bogus irssi warning
+# - improve mouse support
+# - workaround for broken cumode in irssi 0.8.15
+# - fix handling of non-meta windows (uninitialized warning)
+# - add 256 colour support, strip true colour codes
+# - fix totally bogus $N padding reported by Ed S.
+# - make /window goto #name mappings work but ignore non-existant ones
+# - improve incomplete reads reported by bcode
+# - fix single % in awl_viewer reported by bcode
+# - add support for key bindings by nike and ferret
+# - coerce utf8 key binds
+# - add settings: custom_xform, last_line_shade, hide_name_data
+# - abbreviations were broken in some cases
+# - fix some misuse of / as cmdchar in mouse script reported by bcode
+# - add shared status bar mode
+# - ${type} variables for custom_xform setting
+# - crash if custom_xform had runtime error
+# - update sorting documentation
+# - fix odd case in size calculation noted by lasers
+# - add missing font styles to the viewer reported by ishanyx
+# - add italic
+#
+# 0.7g
+# - remove screen support and replace it with fifo support
+# - add double-width support to the shortener
+# - correct documentation regarding $T vs. display_header
+# - add missing refresh for window item changed (thanks vague)
+# - add visible windows
+# - add exemptions for active window
+# - workaround for hiding the window changes from trackbar
+# - hack to force 16colours in screen mode
+# - remember last window (reported by earthnative)
+# - wrong window focus on new queries (reported by emsid)
+# - dataloss bug on trying to remember last window
+#
+# 0.6d+
+# - add support for network headers
+# - fixed regression bug /exec -interactive
+#
+# 0.6ca+
+# - add screen support (from nicklist.pl)
+# - names can now have a max length and window names can be used
+# - fixed a bug with block display in screen mode and status bar mode
+# - added space handling to ir_fe and removed it again
+# - now handling formats on my own
+# - started to work on $tag display
+# - 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
+# - mouse hack
+#
+# 0.5d
+# - add setting to also hide the last status bar if empty (awl_all_disable)
+# - reverted to old utf8 code to also calculate broken utf8 length correctly
+# - simplified dealing with status bars in wlreset
+# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9
+# - fixed bug in handling channel #$$
+# - reset background colour at the beginning of an entry
+#
+# 0.4d
+# - fixed order of disabling status bars
+# - 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
+# - 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 status bars
+# - reset on /reload
+# - position/placement settings
+#
+# 0.2
+# - automated retrieval of key bindings (thanks grep.pl authors)
+# - improved removing of status bars
+# - got rid of status chop
+#
+# 0.1
+# - Based on chanact.pl which was apparently based on lightbar.c and
+# nicklist.pl with various other ideas from random scripts.
diff --git a/scripts/aspell.pl b/scripts/aspell.pl
new file mode 100644
index 0000000..b6a254e
--- /dev/null
+++ b/scripts/aspell.pl
@@ -0,0 +1,725 @@
+=pod
+
+=head1 NAME
+
+aspell.pl
+
+=head1 DESCRIPTION
+
+A spellchecker based on GNU ASpell which allows you to interactively
+select the correct spellings for misspelled words in your input field.
+
+=head1 INSTALLATION
+
+Copy into your F<~/.irssi/scripts/> directory and load with
+C</SCRIPT LOAD F<filename>>.
+
+=head1 SETUP
+
+Settings:
+
+ aspell_debug 0
+ aspell_ignore_chan_nicks 1
+ aspell_suggest_colour '%g'
+ aspell_language 'en_GB'
+ aspell_irssi_dict '~/.irssi/irssi.dict'
+
+B<Note:> Americans may wish to change the language to en_US. This can be done
+with the command C</SET aspell_language en_US> once the script is loaded.
+
+=head1 USAGE
+
+Bind a key to /spellcheck, and then invoke it when you have
+an input-line that you wish to check.
+
+If it is entirely correct, nothing will appear to happen. This is a good thing.
+Otherwise, a small split window will appear at the top of the Irssi session
+showing you the misspelled word, and a selection of 10 possible candidates.
+
+You may select one of the by pressing the appropriate number from C<0-9>, or
+skip the word entirely by hitting the C<Space> bar.
+
+If there are more than 10 possible candidates for a word, you can cycle through
+the 10-word "pages" with the C<n> (next) and C<p> (prev) keys.
+
+Pressing Escape, or any other key, will exit the spellcheck altogether, although
+it can be later restarted.
+
+=head1 AUTHORS
+
+Copyright E<copy> 2011 Isaac Good C<E<lt>irssi@isaacgood.comE<gt>>
+
+Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>>
+
+=head1 LICENCE
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+=head1 BUGS
+
+See README file.
+
+=head1 TODO
+
+See README file.
+
+=cut
+
+
+use warnings;
+use strict;
+use Data::Dumper;
+use Irssi;
+use Irssi::Irc;
+use Irssi::TextUI;
+
+use File::Spec;
+
+# Magic. Somehow remedies:
+# "Can't locate object method "nicks" via package "Irssi::Irc::Query" Bug
+# Actually, that's a bunch of lies, but I'm pretty sure there is something
+# it fixes. Otherwise, a bit of cargo-culting can't hurt.
+
+{ package Irssi::Nick }
+
+eval {
+ use Text::Aspell;
+};
+
+if ($@ && $@ =~ m/Can't locate/) {
+ print '%_Bugger, please insteall Text::Aspell%_'
+}
+
+
+our $VERSION = '1.6.1';
+our %IRSSI = (
+ authors => 'Isaac Good (yitz_), Tom Feist (shabble)',
+ contact => 'irssi@isaacgood.com, shabble+irssi@metavore.org',
+ name => 'aspell',
+ description => 'ASpell spellchecking system for Irssi',
+ license => 'MIT',
+ updated => "2011-10-27",
+ );
+
+# ---------------------------
+# Globals
+# ---------------------------
+
+# CONFIG SETTINGS
+# ===============
+
+# Settings cached vars
+my $DEBUG;
+
+# The colour that the suggestions are rendered in in the split windowpane.
+my $suggestion_colour;
+
+# Whether to bother spellchecking strings that match nicks in the current channel.
+my $ignore_chan_nicks;
+
+# path to local aspell irssi dictionary file.
+my $irssi_dict_filepath;
+
+# Language to use. It follows the same format of the LANG environment variable
+# on most systems. It consists of the two letter ISO 639 language code and an
+# optional two letter ISO 3166 country code after a dash or underscore. The
+# default value is based on the value of the LC_MESSAGES locale.
+my $aspell_language;
+
+
+# OTHER GLOBALS
+# =============
+
+# current line, broken into hashref 'objects' storing word and positional data.
+my @word_pos_array;
+# index of word we're currently processing.
+my $index;
+my $active_word_obj;
+
+# list of all possible suggestions for current misspelled word
+my @suggestions;
+# page number - we only show 10 results per page so we can select with 0-9
+my $suggestion_page;
+
+# the spellchecker object.
+my $aspell;
+
+# some window references to manage the window splitting and restoration
+my $split_win_ref;
+my $original_win_ref;
+
+# keypress handling flag.
+my $corrections_active;
+
+
+#my $bacon = 1;
+
+# ---------------------------
+# key constants
+# ---------------------------
+
+sub K_ESC () { 27 }
+sub K_RET () { 10 }
+sub K_SPC () { 32 }
+sub K_0 () { 48 }
+sub K_9 () { 57 }
+sub K_N () { 110 }
+sub K_P () { 112 }
+sub K_I () { 105 }
+
+# used for printing stuff to the split window we don't want logged.
+sub PRN_LEVEL () { MSGLEVEL_CLIENTCRAP | MSGLEVEL_NEVER }
+sub AS_CFG () { "aspellchecker" }
+
+# ---------------------------
+# Teh Codez
+# ---------------------------
+
+sub check_line {
+ my ($line) = @_;
+
+ # reset everything
+ $suggestion_page = 0;
+ $corrections_active = 0;
+ $index = 0;
+ @word_pos_array = ();
+ @suggestions = ();
+ close_temp_split();
+
+ # split into an array of words on whitespace, keeping track of
+ # positions of each, as well as the size of whitespace.
+
+ my $pos = 0;
+
+ _debug('check_line processing "%s"', $line);
+
+ while ($line =~ m/\G(\S+)(\s*)/g) {
+ my ($word, $ws) = ($1, $2); # word, whitespace
+
+ my $prefix_punct = '';
+ my $suffix_punct = '';
+
+ if ($word =~ m/^([^a-zA-Z0-9]+)/) {
+ $prefix_punct = $1;
+ }
+ if ($word =~ m/([^a-zA-Z0-9]+)$/) {
+ $suffix_punct = $1;
+ }
+
+ my $pp_len = length($prefix_punct);
+ my $sp_len = length($suffix_punct);
+
+ my $actual_len = length($word) - ($pp_len + $sp_len);
+ my $actual_word = substr($word, $pp_len, $actual_len);
+
+ if($DEBUG and ($pp_len or $sp_len)) {
+ _debug("prefix punc: %s, suffix punc: %s, actual word: %s",
+ $prefix_punct, $suffix_punct, $actual_word);
+ }
+
+
+ my $actual_pos = $pos + $pp_len;
+
+ my $obj = {
+ word => $actual_word,
+ pos => $actual_pos,
+ len => $actual_len,
+ prefix_punct => $prefix_punct,
+ suffix_punct => $suffix_punct,
+ };
+
+ push @word_pos_array, $obj;
+ $pos += length ($word . $ws);
+ }
+
+ return unless @word_pos_array > 0;
+
+ process_word($word_pos_array[0]);
+}
+
+sub process_word {
+ my ($word_obj) = @_;
+
+ my $word = $word_obj->{word};
+
+ # That's a whole lotta tryin'!
+ my $channel = $original_win_ref->{active};
+ if (not defined $channel) {
+ if (exists Irssi::active_win()->{active}) {
+ $channel = Irssi::active_win()->{active};
+ } elsif (defined Irssi::active_win()) {
+ my @items = Irssi::active_win()->items;
+ $channel = $items[0] if @items;
+ } else {
+ $channel = Irssi::parse_special('$C');
+ }
+ }
+
+ if ($word =~ m/^\d+$/) {
+
+ _debug("Skipping $word that is entirely numeric");
+ spellcheck_next_word(); # aspell thinks numbers are wrong.
+
+ } elsif (word_matches_chan_nick($channel, $word_obj)) {
+ # skip to next word if it's actually a nick
+ # (and the option is set) - checked for in the matches() func.
+ _debug("Skipping $word that matches nick in channel");
+ spellcheck_next_word();
+
+ } elsif (not $aspell->check($word)) {
+
+ _debug("Word '%s' is incorrect", $word);
+
+ my $sugg_ref = get_suggestions($word);
+
+ if (defined $sugg_ref && ref($sugg_ref) eq 'ARRAY') {
+ @suggestions = @$sugg_ref;
+ }
+
+ if (scalar(@suggestions) == 0) {
+
+ spellcheck_next_word();
+
+ } elsif (not temp_split_active()) {
+
+ $corrections_active = 1;
+ highlight_incorrect_word($word_obj);
+ _debug("Creating temp split to show candidates");
+ create_temp_split();
+
+ } else {
+
+ print_suggestions();
+ }
+ } else {
+
+ spellcheck_next_word();
+ }
+}
+
+sub get_suggestions {
+ my ($word) = @_;
+ my @candidates = $aspell->suggest($word);
+ _debug("Candidates for '$word' are %s", join(", ", @candidates));
+ # if ($bacon) {
+ return \@candidates;
+ # } else {
+ # return undef;
+ # }
+}
+
+sub word_matches_chan_nick {
+ my ($channel, $word_obj) = @_;
+
+ return 0 unless $ignore_chan_nicks;
+ return 0 unless defined $channel and ref $channel;
+
+ my @nicks;
+ if (not exists ($channel->{type})) {
+ return 0;
+ } elsif ($channel->{type} eq 'QUERY') {
+
+ # TODO: Maybe we need to parse ->{address} instead, but
+ # it appears empty on test dumps.
+
+ exists $channel->{name}
+ and push @nicks, { nick => $channel->{name} };
+
+ exists $channel->{visible_name}
+ and push @nicks, { nick => $channel->{visible_name} };
+
+ } elsif($channel->{type} eq 'CHANNEL') {
+ @nicks = $channel->nicks();
+ }
+
+ my $nick_hash;
+
+ $nick_hash->{$_}++ for (map { $_->{nick} } @nicks);
+
+ _debug("Nicks: %s", Dumper($nick_hash));
+
+ # try various combinations of the word with its surrounding
+ # punctuation.
+ my $plain_word = $word_obj->{word};
+ return 1 if exists $nick_hash->{$plain_word};
+ my $pp_word = $word_obj->{prefix_punct} . $word_obj->{word};
+ return 1 if exists $nick_hash->{$pp_word};
+ my $sp_word = $word_obj->{word} . $word_obj->{suffix_punct};
+ return 1 if exists $nick_hash->{$pp_word};
+ my $full_word =
+ $word_obj->{prefix_punct}
+ . $word_obj->{word}
+ . $word_obj->{suffix_punct};
+ return 1 if exists $nick_hash->{$full_word};
+
+ return 0;
+}
+
+# Read from the input line
+sub cmd_spellcheck_line {
+ my ($args, $server, $witem) = @_;
+
+ if (defined $witem) {
+ $original_win_ref = $witem->window;
+ } else {
+ $original_win_ref = Irssi::active_win;
+ }
+
+ my $inputline = _input();
+ check_line($inputline);
+}
+
+sub spellcheck_finish {
+ $corrections_active = 0;
+ close_temp_split();
+
+ # stick the cursor at the end of the input line?
+ my $input = _input();
+ my $end = length($input);
+ Irssi::gui_input_set_pos($end);
+}
+
+sub sig_gui_key_pressed {
+ my ($key) = @_;
+ return unless $corrections_active;
+
+ my $char = chr($key);
+
+ if ($key == K_ESC) {
+ spellcheck_finish();
+
+ } elsif ($key >= K_0 && $key <= K_9) {
+ _debug("Selecting word: $char of page: $suggestion_page");
+ spellcheck_select_word($char + ($suggestion_page * 10));
+
+ } elsif ($key == K_SPC) {
+ _debug("skipping word");
+ spellcheck_next_word();
+ } elsif ($key == K_I) {
+
+ my $current_word = $word_pos_array[$index];
+ $aspell->add_to_personal($current_word->{word});
+ $aspell->save_all_word_lists();
+
+ _print('Saved %s to personal dictionary', $current_word->{word});
+
+ spellcheck_next_word();
+
+ } elsif ($key == K_N) { # next 10 results
+
+ if ((scalar @suggestions) > (10 * ($suggestion_page + 1))) {
+ $suggestion_page++;
+ } else {
+ $suggestion_page = 0;
+ }
+ print_suggestions();
+
+ } elsif ($key == K_P) { # prev 10 results
+ if ($suggestion_page > 0) {
+ $suggestion_page--;
+ }
+ print_suggestions();
+
+ } else {
+ spellcheck_finish();
+ }
+
+ Irssi::signal_stop();
+}
+
+sub spellcheck_next_word {
+ $index++;
+ $suggestion_page = 0;
+
+ if ($index >= @word_pos_array) {
+ _debug("End of words");
+ spellcheck_finish();
+ return;
+ }
+
+ _debug("moving onto the next word: $index");
+ process_word($word_pos_array[$index]);
+
+}
+sub spellcheck_select_word {
+ my ($num) = @_;
+
+ if ($num > $#suggestions) {
+ _debug("$num past end of suggestions list.");
+ return 0;
+ }
+
+ my $word = $suggestions[$num];
+ _debug("Selected word $num: $word as correction");
+ correct_input_line_word($word_pos_array[$index], $word);
+ return 1;
+}
+
+sub _debug {
+ my ($fmt, @args) = @_;
+ return unless $DEBUG;
+
+ $fmt = '%%RDEBUG:%%n ' . $fmt;
+ my $str = sprintf($fmt, @args);
+ Irssi::window_find_refnum(1)->print($str);
+}
+
+sub _print {
+ my ($fmt, @args) = @_;
+ my $str = sprintf($fmt, @args);
+ Irssi::active_win->print('%g' . $str . '%n');
+}
+
+sub temp_split_active () {
+ return defined $split_win_ref;
+}
+
+sub create_temp_split {
+ #$original_win_ref = Irssi::active_win();
+ Irssi::signal_add_first('window created', 'sig_win_created');
+ Irssi::command('window new split');
+ Irssi::signal_remove('window created', 'sig_win_created');
+}
+
+sub UNLOAD {
+ _print("%%RASpell spellchecker Version %s unloading...%%n", $VERSION);
+ close_temp_split();
+}
+
+sub close_temp_split {
+
+ my $original_refnum = -1;
+ my $active_refnum = -2;
+
+ my $active_win = Irssi::active_win();
+
+ if (defined $active_win && ref($active_win) =~ m/^Irssi::/) {
+ if (exists $active_win->{refnum}) {
+ $active_refnum = $active_win->{refnum};
+ }
+ }
+
+ if (defined $original_win_ref && ref($original_win_ref) =~ m/^Irssi::/) {
+ if (exists $original_win_ref->{refnum}) {
+ $original_refnum = $original_win_ref->{refnum};
+ }
+ }
+
+ if ($original_refnum != $active_refnum && $original_refnum > 0) {
+ Irssi::command("window goto $original_refnum");
+ }
+
+ if (defined($split_win_ref) && ref($split_win_ref) =~ m/^Irssi::/) {
+ if (exists $split_win_ref->{refnum}) {
+ my $split_refnum = $split_win_ref->{refnum};
+ _debug("split_refnum is %d", $split_refnum);
+ _debug("splitwin has: %s", join(", ", map { $_->{name} }
+ $split_win_ref->items()));
+ Irssi::command("window close $split_refnum");
+ undef $split_win_ref;
+ } else {
+ _debug("refnum isn't in the split_win_ref");
+ }
+ } else {
+ _debug("winref is undef or broken");
+ }
+}
+
+sub sig_win_created {
+ my ($win) = @_;
+ $split_win_ref = $win;
+ # printing directly from this handler causes irssi to segfault.
+ Irssi::timeout_add_once(10, \&configure_split_win, {});
+}
+
+sub configure_split_win {
+ $split_win_ref->command('window size 3');
+ $split_win_ref->command('window name ASpell Suggestions');
+
+ print_suggestions();
+}
+
+sub correct_input_line_word {
+ my ($word_obj, $correction) = @_;
+ my $input = _input();
+
+ my $word = $word_obj->{word};
+ my $pos = $word_obj->{pos};
+ my $len = $word_obj->{len};
+
+ # handle punctuation.
+ # - Internal punctuation: "they're" "Bob's" should be replaced if necessary
+ # - external punctuation: "eg:" should not.
+ # this will also have impact on the position adjustments.
+
+ _debug("Index of incorrect word is %d", $index);
+ _debug("Correcting word %s (%d) with %s", $word, $pos, $correction);
+
+
+ #my $corrected_word = $prefix_punct . $correction . $suffix_punct;
+
+ my $new_length = length $correction;
+
+ my $diff = $new_length - $len;
+ _debug("diff between $word and $correction is $diff");
+
+ # record the fix in the array.
+ $word_pos_array[$index] = { word => $correction, pos => $pos + $diff };
+ # do the actual fixing of the input string
+ substr($input, $pos, $len) = $correction;
+
+
+ # now we have to go through and fix up all teh positions since
+ # the correction might be a different length.
+
+ foreach my $new_obj (@word_pos_array[$index..$#word_pos_array]) {
+ #starting at $index, add the diff to each position.
+ $new_obj->{pos} += $diff;
+ }
+
+ _debug("Setting input to new value: '%s'", $input);
+
+ # put the corrected string back into the input field.
+ Irssi::gui_input_set($input);
+
+ _debug("-------------------------------------------------");
+ spellcheck_next_word();
+}
+
+# move the cursor to the beginning of the word in question.
+sub highlight_incorrect_word {
+ my ($word_obj) = @_;
+ Irssi::gui_input_set_pos($word_obj->{pos});
+}
+
+sub print_suggestions {
+ my $count = scalar @suggestions;
+ my $pages = int ($count / 10);
+ my $bot = $suggestion_page * 10;
+ my $top = $bot + 9;
+
+ $top = $#suggestions if $top > $#suggestions;
+
+ my @visible = @suggestions[$bot..$top];
+ my $i = 0;
+
+ @visible = map {
+ '(%_' . $suggestion_colour . ($i++) . '%n) ' # bold/coloured selection num
+ . $suggestion_colour . $_ . '%n' # coloured selection option
+ } @visible;
+
+ # disable timestamps to ensure a clean window.
+ my $orig_ts_level = Irssi::parse_special('$timestamp_level');
+ $split_win_ref->command("^set timestamp_level $orig_ts_level -CLIENTCRAP");
+
+ # clear the window
+ $split_win_ref->command("/^scrollback clear");
+ my $msg = sprintf('%s [Pg %d/%d] Select a number or <SPC> to skip this '
+ . 'word. Press <i> to save this word to your personal '
+ . 'dictionary. Any other key cancels%s',
+ '%_', $suggestion_page + 1, $pages + 1, '%_');
+
+ my $word = $word_pos_array[$index]->{word};
+
+ $split_win_ref->print($msg, PRN_LEVEL); # header
+ $split_win_ref->print('%_%R"' . $word . '"%n ' # erroneous word
+ . join(" ", @visible), PRN_LEVEL); # suggestions
+
+ # restore timestamp settings.
+ $split_win_ref->command("^set timestamp_level $orig_ts_level");
+
+}
+
+sub sig_setup_changed {
+ $DEBUG
+ = Irssi::settings_get_bool('aspell_debug');
+ $suggestion_colour
+ = Irssi::settings_get_str('aspell_suggest_colour');
+ $ignore_chan_nicks
+ = Irssi::settings_get_bool('aspell_ignore_chan_nicks');
+
+
+
+ my $old_lang = $aspell_language;
+
+ $aspell_language
+ = Irssi::settings_get_str('aspell_language');
+
+
+ my $old_filepath = $irssi_dict_filepath;
+
+ $irssi_dict_filepath
+ = Irssi::settings_get_str('aspell_irssi_dict');
+
+ _debug("Filepath: $irssi_dict_filepath");
+
+ if ((not defined $old_filepath) or
+ ($irssi_dict_filepath ne $old_filepath)) {
+ reinit_aspell();
+ }
+
+ _debug("Language: $aspell_language");
+
+ if ((not defined $old_lang) or
+ ($old_lang ne $aspell_language)) {
+ reinit_aspell();
+ }
+
+}
+
+sub _input {
+ return Irssi::parse_special('$L');
+}
+
+sub reinit_aspell {
+ $aspell = Text::Aspell->new;
+ $aspell->set_option('lang', $aspell_language);
+ $aspell->set_option('personal', $irssi_dict_filepath);
+ $aspell->create_speller();
+}
+
+# sub cmd_break_cands {
+# $bacon = !$bacon;
+# _print("Bacon is now: %s", $bacon?"true":"false");
+# }
+
+sub init {
+ my $default_dict_path
+ = File::Spec->catfile(Irssi::get_irssi_dir, "irssi.dict");
+ Irssi::settings_add_bool(AS_CFG, 'aspell_debug', 0);
+ Irssi::settings_add_bool(AS_CFG, 'aspell_ignore_chan_nicks', 1);
+ Irssi::settings_add_str(AS_CFG, 'aspell_suggest_colour', '%g');
+ Irssi::settings_add_str(AS_CFG, 'aspell_language', 'en_GB');
+ Irssi::settings_add_str(AS_CFG, 'aspell_irssi_dict', $default_dict_path);
+
+ sig_setup_changed();
+
+ Irssi::signal_add('setup changed' => \&sig_setup_changed);
+
+ _print("%%RASpell spellchecker Version %s loaded%%n", $VERSION);
+
+ $corrections_active = 0;
+ $index = 0;
+
+ Irssi::signal_add_first('gui key pressed' => \&sig_gui_key_pressed);
+ Irssi::command_bind('spellcheck' => \&cmd_spellcheck_line);
+ #Irssi::command_bind('breakon' => \&cmd_break_cands);
+}
+
+init();
diff --git a/scripts/clearable.pl b/scripts/clearable.pl
new file mode 100644
index 0000000..79fef1a
--- /dev/null
+++ b/scripts/clearable.pl
@@ -0,0 +1,71 @@
+use strict;
+use warnings;
+
+our $VERSION = '0.1'; # 5ef9502616f1301
+our %IRSSI = (
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'clearable',
+ description => 'make some command output clearable',
+ license => 'ISC',
+ );
+
+use Irssi 20140701;
+
+sub cmd_help {
+ return unless $_[0] =~ /^clearable\s*$/i;
+ print CLIENTCRAP <<HELP
+%9Syntax:%9
+
+CLEARABLE <command>
+
+%9Description:%9
+
+ Runs command and tags each line of immediate output with the
+ lastlog-flag so it can be cleared with /LASTLOG -clear
+
+%9Example:%9
+
+ /CLEARABLE NAMES
+ /LASTLOG -clear
+
+%9See also:%9 LASTLOG, SCROLLBACK CLEAR
+HELP
+}
+
+my %refreshers;
+
+sub sig_prt {
+ my $win = $_[0]{window};
+ my $view = $win && $win->view;
+ return unless $view;
+ my $llp = $view->{buffer}{cur_line}{_irssi}//0;
+ &Irssi::signal_continue;
+ $view = $win->view;
+ my $l2 = $view->{buffer}{cur_line};
+ return unless ($l2 && $l2->{_irssi} != $llp);
+ for (my $line = $l2; $line && $line->{_irssi} != $llp; ) {
+ $win->gui_printtext_after($line->prev, $line->{info}{level} | MSGLEVEL_NEVER | MSGLEVEL_LASTLOG, $line->get_text(1)."\n", $line->{info}{time});
+ my $ll = $win->last_line_insert;
+ $view->remove_line($line);
+ $line = $ll && $ll->prev;
+ $refreshers{ $win->{refnum} } //= $view->{bottom};
+ }
+}
+
+sub cmd_clearable {
+ my ($data, $server, $item) = @_;
+ Irssi::signal_add_first('print text' => 'sig_prt');
+ Irssi::signal_emit('send command' => Irssi::parse_special('$k').$data, $server, $item);
+ Irssi::signal_remove('print text' => 'sig_prt');
+ for my $refnum (keys %refreshers) {
+ my $bottom = delete $refreshers{$refnum};
+ my $win = Irssi::window_find_refnum($refnum) // next;
+ my $view = $win->view;
+ $win->command('^scrollback end') if $bottom && !$view->{bottom};
+ $view->redraw;
+ }
+}
+
+Irssi::command_bind('clearable' => 'cmd_clearable');
+Irssi::command_bind_last('help' => 'cmd_help');
diff --git a/scripts/cmpchans.pl b/scripts/cmpchans.pl
new file mode 100644
index 0000000..80324b0
--- /dev/null
+++ b/scripts/cmpchans.pl
@@ -0,0 +1,64 @@
+use strict;
+use warnings;
+
+our $VERSION = "0.5";
+our %IRSSI = (
+ authors => 'Jari Matilainen, init[1]@irc.freenode.net',
+ contact => 'vague@vague.se',
+ name => 'cmpchans',
+ description => 'Compare nicks in two channels',
+ license => 'Public Domain',
+ url => 'http://vague.se'
+);
+
+use Irssi::TextUI;
+use Data::Dumper;
+
+sub cmd_cmp {
+ local $/ = " ";
+ my ($args, $server, $witem) = @_;
+ my (@channels) = split /\s+/, $args;
+
+ my $server1 = $server;
+ if ($channels[0] =~ s,(.*?)/,,) {
+ $server1 = Irssi::server_find_tag($1) || $server;
+ }
+ my $chan1 = $server1->channel_find($channels[0]);
+ if(!$chan1) {
+ Irssi::active_win()->{active}->print("You have to specify atleast one channel to compare nicks to");
+ return;
+ }
+
+ my @nicks_1;
+ my @nicks_2;
+
+ @nicks_1 = $chan1->nicks() if(defined $chan1);
+
+ if(not defined $channels[1]) {
+ @nicks_2 = $witem->nicks();
+ }
+ else {
+ if ($channels[1] =~ s,(.*?)/,,) {
+ $server1 = Irssi::server_find_tag($1) || $server;
+ }
+ my ($chan2) = $server1->channel_find($channels[1]);
+ @nicks_2 = $chan2->nicks() if(defined $chan2);
+ }
+
+ return if(scalar @nicks_1 == 0 || scalar @nicks_2 == 0);
+
+ my %count = ();
+ my @intersection;
+
+ foreach (@nicks_1, @nicks_2) { $count{$_->{nick}}++; }
+ foreach my $key (keys %count) {
+ if($count{$key} > 1) {
+ push @{\@intersection}, $key;
+ }
+ }
+
+ my $common = join(", ", @intersection);
+ $witem->print("Common nicks: " . $common);
+}
+
+Irssi::command_bind("cmp", \&cmd_cmp);
diff --git a/scripts/colorize_nicks.pl b/scripts/colorize_nicks.pl
new file mode 100644
index 0000000..4f8d26b
--- /dev/null
+++ b/scripts/colorize_nicks.pl
@@ -0,0 +1,136 @@
+use strict;
+use warnings;
+
+our $VERSION = '0.3.6'; # e54c56e8922561d
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'colorize_nicks',
+ description => 'Colourise mention of nicks in the message body.',
+ license => 'GNU GPLv2 or later',
+ );
+
+# inspired by mrwright's nickcolor.pl and xt's colorize_nicks.pl
+#
+# you need nickcolor_expando or another nickcolor script providing the
+# get_nick_color2 function
+
+# Usage
+# =====
+# should start working once loaded
+
+# Options
+# =======
+# /set colorize_nicks_skip_formats <num>
+# * how many forms (blocks of irssi format codes or non-letters) to
+# skip at the beginning of line before starting to colourise nicks
+# (you usually want to skip the speaker's nick itself and the
+# timestamp)
+#
+# /set colorize_nicks_ignore_list <words to ignore>
+# * list of nicks (words) that should never be coloured
+#
+# /set colorize_nicks_repeat_formats <ON|OFF>
+# * repeat the format stack from the beginning of line, enable when
+# using per-line colours and colorize_nicks breaks it
+
+# Commands
+# ========
+# you can use this alias:
+#
+# /alias nocolorize set colorize_nicks_ignore_list $colorize_nicks_ignore_list
+#
+# /nocolorize <nick>
+# * quickly add nick to the bad word list of nicks that should not be
+# colourised
+
+no warnings 'redefine';
+use Irssi;
+
+my $irssi_mumbo = qr/\cD[`-i]|\cD[&-@\xff]./;
+
+my $nickchar = qr/[][[:alnum:]\\|`^{}_-]/;
+my $nick_pat = qr/($nickchar+)/;
+
+my @ignore_list;
+
+my $colourer_script;
+
+sub prt_text_issue {
+ my ( $dest,
+ $text,
+ $stripped
+ ) = @_;
+ my $colourer;
+ unless ($colourer_script
+ && ($colourer = "Irssi::Script::$colourer_script"->can('get_nick_color2'))) {
+ for my $script (sort map { s/::$//r } grep { /^nickcolor|nm/ } keys %Irssi::Script::) {
+ if ($colourer = "Irssi::Script::$script"->can('get_nick_color2')) {
+ $colourer_script = $script;
+ last;
+ }
+ }
+ }
+ return unless $colourer;
+ return unless $dest->{level} & MSGLEVEL_PUBLIC;
+ return unless defined $dest->{target};
+ my $chanref = ref $dest->{server} && $dest->{server}->channel_find($dest->{target});
+ return unless $chanref;
+ my %nicks = map { $_->[0] => $colourer->($dest->{server}{tag}, $chanref->{name}, $_->[1], 1) }
+ grep { defined }
+ map { if (my $nr = $chanref->nick_find($_)) {
+ [ $_ => $nr->{nick} ]
+ } }
+ keys %{ +{ map { $_ => undef } $stripped =~ /$nick_pat/g } };
+ delete @nicks{ @ignore_list };
+ my @forms = split /((?:$irssi_mumbo|\s|[.,*@%+&!#$()=~'";:?\/><]+(?=$irssi_mumbo|\s))+)/, $text, -1;
+ my $ret = '';
+ my $fmtstack = '';
+ my $nick_re = join '|', map { quotemeta } sort { length $b <=> length $a } grep { length $nicks{$_} } keys %nicks;
+ my $skip = Irssi::settings_get_int('colorize_nicks_skip_formats');
+ return if $skip < 0;
+ while (@forms) {
+ my ($t, $form) = splice @forms, 0, 2;
+ if ($skip > 0) {
+ --$skip;
+ $ret .= $t;
+ $ret .= $form if defined $form;
+ if (Irssi::settings_get_bool('colorize_nicks_repeat_formats')) {
+ $fmtstack .= join '', $form =~ /$irssi_mumbo/g if defined $form;
+ $fmtstack =~ s/\cDe//g;
+ }
+ }
+ elsif (length $nick_re
+ && $t =~ s/((?:^|\s)\W{0,3}?)(?<!$nickchar|')($nick_re)(?!$nickchar)/$1$nicks{$2}$2\cDg$fmtstack/g) {
+ $ret .= "$t\cDg$fmtstack";
+ $ret .= $form if defined $form;
+ $fmtstack .= join '', $form =~ /$irssi_mumbo/g if defined $form;
+ $fmtstack =~ s/\cDe//g;
+ }
+ else {
+ $ret .= $t;
+ $ret .= $form if defined $form;
+ }
+ }
+ Irssi::signal_continue($dest, $ret, $stripped);
+}
+
+sub setup_changed {
+ @ignore_list = split /\s+|,/, Irssi::settings_get_str('colorize_nicks_ignore_list');
+}
+
+sub init {
+ setup_changed();
+}
+
+Irssi::signal_add({
+ 'print text' => 'prt_text_issue',
+});
+Irssi::signal_add_last('setup changed' => 'setup_changed');
+
+Irssi::settings_add_int('colorize_nicks', 'colorize_nicks_skip_formats' => 2);
+Irssi::settings_add_str('colorize_nicks', 'colorize_nicks_ignore_list' => '');
+Irssi::settings_add_bool('colorize_nicks', 'colorize_nicks_repeat_formats' => 0);
+
+init();
diff --git a/scripts/complete_at.pl b/scripts/complete_at.pl
new file mode 100644
index 0000000..597e81e
--- /dev/null
+++ b/scripts/complete_at.pl
@@ -0,0 +1,40 @@
+use strict;
+use warnings;
+
+our $VERSION = '0.2'; # 49f841075725906
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'complete_at',
+ description => 'Complete nicks after @ (twitter-style)',
+ license => 'ISC',
+ );
+
+# Usage
+# =====
+# write @ and type on the Tab key to complete nicks
+
+{ package Irssi::Nick }
+
+my $complete_char = '@';
+
+sub complete_at {
+ my ($cl, $win, $word, $start, $ws) = @_;
+ if ($cl && !@$cl
+ && $win && $win->{active}
+ && $win->{active}->isa('Irssi::Channel')) {
+ if ((my $pos = rindex $word, $complete_char) > -1) {
+ my ($pre, $post) = ((substr $word, 0, $pos), (substr $word, $pos + 1));
+ my $pre2 = length $start ? "$start $pre" : $pre;
+ my $pre3 = length $pre2 ? "$pre2$complete_char" : "";
+ Irssi::signal_emit('complete word', $cl, $win, $post, $pre3, $ws);
+ unless (@$cl) {
+ push @$cl, grep { /^\Q$post/i } map { $_->{nick} } $win->{active}->nicks();
+ }
+ map { $_ = "$pre$complete_char$_" } @$cl;
+ }
+ }
+}
+
+Irssi::signal_add_last('complete word' => 'complete_at');
diff --git a/scripts/dim_nicks.pl b/scripts/dim_nicks.pl
new file mode 100644
index 0000000..5c19633
--- /dev/null
+++ b/scripts/dim_nicks.pl
@@ -0,0 +1,392 @@
+use strict;
+use warnings;
+
+our $VERSION = '0.4.6'; # 373036720cc131b
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'dim_nicks',
+ description => 'Dims nicks that are not in channel anymore.',
+ license => 'GNU GPLv2 or later',
+ );
+
+# Usage
+# =====
+# Once loaded, this script will record the nicks of each new
+# message. If the user leaves the room, the messages will be rewritten
+# with the nick in another colour/style.
+#
+# Depending on your theme, tweaking the forms settings may be
+# necessary. With the default irssi theme, this script should just
+# work.
+
+# Options
+# =======
+# /set dim_nicks_color <colour>
+# * the colour code to use for dimming the nick, or a string of format
+# codes with the special token $* in place of the nick (e.g. %I$*%I
+# for italic)
+#
+# /set dim_nicks_history_lines <num>
+# * only this many lines of messages are remembered/rewritten (per
+# window)
+#
+# /set dim_nicks_forms_skip <num>
+# /set dim_nicks_forms_search_max <num>
+# * these two settings limit the range where to search for the
+# nick.
+# It sets how many forms (blocks of irssi format codes or
+# non-letters) to skip at the beginning of line before starting to
+# search for the nick, and from then on how many forms to search
+# before stopping.
+# You should set this to the appropriate values to avoid (a) dimming
+# your timestamp (b) dimming message content instead of the nick.
+# To check your settings, you can use the command
+# /script exec Irssi::Script::dim_nicks::debug_forms
+
+
+no warnings 'redefine';
+use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK};
+use Irssi 20140701;
+use Irssi::TextUI;
+use Encode;
+
+
+sub setc () {
+ $IRSSI{name}
+}
+
+sub set ($) {
+ setc . '_' . $_[0]
+}
+
+my $history_lines = 100;
+my $skip_forms = 1;
+my $search_forms_max = 5;
+my $color_letter = 'K';
+
+my (%nick_reg, %chan_reg, %history, %history_st, %lost_nicks, %lost_nicks_backup);
+
+my ($dest, $chanref, $nickref);
+
+sub clear_ref {
+ $dest = undef;
+ $chanref = undef;
+ $nickref = undef;
+}
+
+sub msg_line_tag {
+ my ($srv, $msg, $nick, $addr, $targ) = @_;
+ $chanref = $srv->channel_find($targ);
+ $nickref = ref $chanref ? $chanref->nick_find($nick) : undef;
+}
+
+sub msg_line_clear {
+ clear_ref();
+}
+
+my @color_code;
+
+sub color_to_code {
+ my $win = Irssi::active_win;
+ my $view = $win->view;
+ if (-1 == index $color_letter, '$*') {
+ $color_letter = "%$color_letter\$*";
+ }
+ $win->print_after(undef, MSGLEVEL_NEVER, "$color_letter ");
+ my $lp = $win->last_line_insert;
+ my $color_code = $lp->get_text(1);
+ $color_code =~ s/ $//;
+ $view->remove_line($lp);
+ @color_code = split /\$\*/, $color_code, 2;
+}
+
+sub setup_changed {
+ $history_lines = Irssi::settings_get_int( set 'history_lines' );
+ $skip_forms = Irssi::settings_get_int( set 'forms_skip' );
+ $search_forms_max = Irssi::settings_get_int( set 'forms_search_max' );
+ my $new_color = Irssi::settings_get_str( set 'color' );
+ if ($new_color ne $color_letter) {
+ $color_letter = $new_color;
+ color_to_code();
+ }
+}
+
+sub init_dim_nicks {
+ setup_changed();
+}
+
+sub prt_text_issue {
+ ($dest) = @_;
+ clear_ref() unless defined $dest->{target};
+ clear_ref() unless $dest->{level} & MSGLEVEL_PUBLIC;
+}
+
+sub expire_hist {
+ for my $ch (keys %history_st) {
+ if (@{$history_st{$ch}} > 2 * $history_lines) {
+ my @del = splice @{$history_st{$ch}}, 0, $history_lines;
+ delete @history{ @del };
+ }
+ }
+}
+
+sub prt_text_ref {
+ return unless $nickref;
+ my ($win) = @_;
+ my $view = $win->view;
+ my $line_id = $view->{buffer}{_irssi} .','. $view->{buffer}{cur_line}{_irssi};
+ $chan_reg{ $chanref->{_irssi} } = $chanref;
+ $nick_reg{ $nickref->{_irssi} } = $nickref;
+ if (exists $history{ $line_id }) {
+ }
+ $history{ $line_id } = [ $win->{_irssi}, $chanref->{_irssi}, $nickref->{_irssi}, $nickref->{nick} ];
+ push @{$history_st{ $chanref->{_irssi} }}, $line_id;
+ expire_hist();
+ my @lost_forever = grep { $view->{buffer}{first_line}{info}{time} > $lost_nicks{ $chanref->{_irssi} }{ $_ } }
+ keys %{$lost_nicks{ $chanref->{_irssi} }};
+ delete @{$lost_nicks{ $chanref->{_irssi} }}{ @lost_forever };
+ delete @{$lost_nicks_backup{ $chanref->{_irssi} }}{ @lost_forever };
+ clear_ref();
+}
+
+sub win_del {
+ my ($win) = @_;
+ for my $ch (keys %history_st) {
+ @{$history_st{$ch}} = grep { exists $history{ $_ } &&
+ $history{ $_ }[0] != $win->{_irssi} } @{$history_st{$ch}};
+ }
+ my @del = grep { $history{ $_ }[0] == $win->{_irssi} } keys %history;
+ delete @history{ @del };
+}
+
+sub _alter_lines {
+ my ($chan, $check_lr, $ad) = @_;
+ my $win = $chan->window;
+ return unless ref $win;
+ my $view = $win->view;
+ my $count = $history_lines;
+ my $buffer_id = $view->{buffer}{_irssi} .',';
+ my $lp = $view->{buffer}{cur_line};
+ my %check_lr = map { $_ => undef } @$check_lr;
+ my $redraw;
+ my $bottom = $view->{bottom};
+ while ($lp && $count) {
+ my $line_id = $buffer_id . $lp->{_irssi};
+ if (exists $check_lr{ $line_id }) {
+ $lp = _alter_line($buffer_id, $line_id, $win, $view, $lp, $chan->{_irssi}, $ad);
+ unless ($lp) {
+ last;
+ }
+ $redraw = 1;
+ }
+ } continue {
+ --$count;
+ $lp = $lp->prev;
+ }
+ if ($redraw) {
+ $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
+ $view->redraw;
+ }
+}
+
+my $irssi_mumbo = qr/\cD[`-i]|\cD[&-@\xff]./;
+my $irssi_mumbo_no_partial = qr/(?<!\cD)(?<!\cD[&-@\xff])/;
+my $irssi_skip_form_re = qr/((?:$irssi_mumbo|[.,*@%+&!#$()=~'";:?\/><]+(?=$irssi_mumbo|\s))+|\s+)/;
+
+sub debug_forms {
+ my $win = Irssi::active_win;
+ my $view = $win->view;
+ my $lp = $view->{buffer}{cur_line};
+ my $count = $history_lines;
+ my $buffer_id = $view->{buffer}{_irssi} .',';
+ while ($lp && $count) {
+ my $line_id = $buffer_id . $lp->{_irssi};
+ # $history{ $line_id } = [ $win->{_irssi}, $chanref->{_irssi}, $nickref->{_irssi}, $nickref->{nick} ];
+ if (exists $history{ $line_id }) {
+ my $line_nick = $history{ $line_id }[3];
+ my $text = $lp->get_text(1);
+ pos $text = 0;
+ my $from = 0;
+ for (my $i = 0; $i < $skip_forms; ++$i) {
+ last unless
+ scalar $text =~ /$irssi_skip_form_re/g;
+ $from = pos $text;
+ }
+ my $to = $from;
+ for (my $i = 0; $i < $search_forms_max; ++$i) {
+ last unless
+ scalar $text =~ /$irssi_skip_form_re/g;
+ $to = pos $text;
+ }
+ my $pre = substr $text, 0, $from;
+ my $search = substr $text, $from, $to-$from;
+ my $post = substr $text, $to;
+ unless ($to > $from) {
+ } else {
+ my @nick_reg;
+ unshift @nick_reg, quotemeta substr $line_nick, 0, $_ for 1 .. length $line_nick;
+ no warnings 'uninitialized';
+ for my $nick_reg (@nick_reg) {
+ last if $search
+ =~ s/(\Q$color_code[0]\E\s*)?((?:$irssi_mumbo)+)?$irssi_mumbo_no_partial($nick_reg)((?:$irssi_mumbo)+)?(\s*\Q$color_code[0]\E)?/<match>$1$2<nick>$3<\/nick>$4$5<\/match>/;
+ last if $search
+ =~ s/(?:\Q$color_code[0]\E)?(?:(?:$irssi_mumbo)+?)?$irssi_mumbo_no_partial($nick_reg)(?:(?:$irssi_mumbo)+?)?(?:\Q$color_code[1]\E)?/<nick>$1<\/nick>/;
+ }
+ }
+ my $msg = "$pre<search>$search</search>$post";
+ #$msg =~ s/([^[:print:]])/sprintf '\\x%02x', ord $1/ge;
+ $msg =~ s/\cDe/%|/g; $msg =~ s/%/%%/g;
+ $win->print(setc." form debug: [$msg]", MSGLEVEL_CLIENTCRAP);
+ return;
+ }
+ } continue {
+ --$count;
+ $lp = $lp->prev;
+ }
+ $win->print(setc." form debug: no usable line found", MSGLEVEL_CLIENTCRAP);
+}
+
+sub _alter_line {
+ my ($buffer_id, $lrp, $win, $view, $lp, $cid, $ad) = @_;
+ my $line_nick = $history{ $lrp }[3];
+ my $text = $lp->get_text(1);
+ pos $text = 0;
+ my $from = 0;
+ for (my $i = 0; $i < $skip_forms; ++$i) {
+ last unless
+ scalar $text =~ /$irssi_skip_form_re/g;
+ $from = pos $text;
+ }
+ my $to = $from;
+ for (my $i = 0; $i < $search_forms_max; ++$i) {
+ last unless
+ scalar $text =~ /$irssi_skip_form_re/g;
+ $to = pos $text;
+ }
+ return $lp unless $to > $from;
+ my @nick_reg;
+ unshift @nick_reg, quotemeta substr $line_nick, 0, $_ for 1 .. length $line_nick;
+ { no warnings 'uninitialized';
+ if ($ad) {
+ if (exists $lost_nicks_backup{ $cid }{ $line_nick }) {
+ my ($fs, $fc, $bc, $bs) = @{$lost_nicks_backup{ $cid }{ $line_nick }};
+ my $sen = length $bs ? $color_code[0] : '';
+ for my $nick_reg (@nick_reg) {
+ last if
+ (substr $text, $from, $to-$from)
+ =~ s/(?:\Q$color_code[0]\E)?(?:(?:$irssi_mumbo)+?)?$irssi_mumbo_no_partial($nick_reg)(?:(?:$irssi_mumbo)+?)?(?:\Q$color_code[1]\E)?/$fc$1$bc$sen/;
+ }
+ }
+ }
+ else {
+ for my $nick_reg (@nick_reg) {
+ if (
+ (substr $text, $from, $to-$from)
+ =~ s/(\Q$color_code[0]\E\s*)?((?:$irssi_mumbo)+)?$irssi_mumbo_no_partial($nick_reg)((?:$irssi_mumbo)+)?(\s*\Q$color_code[0]\E)?/$1$2$color_code[0]$3$color_code[1]$4$5/) {
+ $lost_nicks_backup{ $cid }{ $line_nick } = [ $1, $2, $4, $5 ];
+ last;
+ }
+ }
+ } }
+ $win->gui_printtext_after($lp->prev, $lp->{info}{level} | MSGLEVEL_NEVER, "$text\n", $lp->{info}{time});
+ my $ll = $win->last_line_insert;
+ my $line_id = $buffer_id . $ll->{_irssi};
+ if (exists $history{ $line_id }) {
+ }
+ grep { $_ eq $lrp and $_ = $line_id } @{$history_st{ $cid }};
+ $history{ $line_id } = delete $history{ $lrp };
+ $view->remove_line($lp);
+ $ll;
+}
+
+sub nick_add {
+ my ($chan, $nick) = @_;
+ if (delete $lost_nicks{ $chan->{_irssi} }{ $nick->{nick} }) {
+ my @check_lr = grep { $history{ $_ }[1] == $chan->{_irssi} &&
+ $history{ $_ }[2] eq $nick->{nick} } keys %history;
+ if (@check_lr) {
+ $nick_reg{ $nick->{_irssi} } = $nick;
+ for my $li (@check_lr) {
+ $history{ $li }[2] = $nick->{_irssi};
+ }
+ _alter_lines($chan, \@check_lr, 1);
+ }
+ }
+ delete $lost_nicks_backup{ $chan->{_irssi} }{ $nick->{nick} };
+}
+
+sub nick_del {
+ my ($chan, $nick) = @_;
+ my @check_lr = grep { $history{ $_ }[2] eq $nick->{_irssi} } keys %history;
+ for my $li (@check_lr) {
+ $history{ $li }[2] = $nick->{nick};
+ }
+ if (@check_lr) {
+ $lost_nicks{ $chan->{_irssi} }{ $nick->{nick} } = time;
+ _alter_lines($chan, \@check_lr, 0);
+ }
+ delete $nick_reg{ $nick->{_irssi} };
+}
+
+sub nick_change {
+ my ($chan, $nick, $oldnick) = @_;
+ nick_add($chan, $nick);
+}
+
+sub chan_del {
+ my ($chan) = @_;
+ if (my $del = delete $history_st{ $chan->{_irssi} }) {
+ delete @history{ @$del };
+ }
+ delete $chan_reg{ $chan->{_irssi} };
+ delete $lost_nicks{$chan->{_irssi}};
+ delete $lost_nicks_backup{$chan->{_irssi}};
+}
+
+Irssi::settings_add_int( setc, set 'history_lines', $history_lines);
+Irssi::signal_add_last({
+ 'setup changed' => 'setup_changed',
+});
+Irssi::signal_add({
+ 'print text' => 'prt_text_issue',
+ 'gui print text finished' => 'prt_text_ref',
+ 'nicklist new' => 'nick_add',
+ 'nicklist changed' => 'nick_change',
+ 'nicklist remove' => 'nick_del',
+ 'window destroyed' => 'win_del',
+ 'message public' => 'msg_line_tag',
+ 'message own_public' => 'msg_line_clear',
+ 'channel destroyed' => 'chan_del',
+});
+
+sub dumphist {
+ my $win = Irssi::active_win;
+ my $view = $win->view;
+ my $buffer_id = $view->{buffer}{_irssi} .',';
+ for (my $lp = $view->{buffer}{first_line}; $lp; $lp = $lp->next) {
+ my $line_id = $buffer_id . $lp->{_irssi};
+ if (exists $history{ $line_id }) {
+ my $k = $history{ $line_id };
+ if (exists $chan_reg{ $k->[1] }) {
+ }
+ if (exists $nick_reg{ $k->[2] }) {
+ }
+ if (exists $lost_nicks{ $k->[1] } && exists $lost_nicks{ $k->[1] }{ $k->[2] }) {
+ }
+ }
+ }
+}
+Irssi::settings_add_str( setc, set 'color', $color_letter);
+Irssi::settings_add_int( setc, set 'forms_skip', $skip_forms);
+Irssi::settings_add_int( setc, set 'forms_search_max', $search_forms_max);
+
+init_dim_nicks();
+
+{ package Irssi::Nick }
+
+# Changelog
+# =========
+# 0.4.6
+# - fix crash on some lines reported by pierrot
diff --git a/scripts/hideshow.pl b/scripts/hideshow.pl
new file mode 100644
index 0000000..6ef0bf8
--- /dev/null
+++ b/scripts/hideshow.pl
@@ -0,0 +1,286 @@
+use strict;
+use warnings;
+
+our $VERSION = '0.4.4';
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'hideshow',
+ description => 'Removes and re-adds lines to the Irssi buffer view.',
+ license => 'GNU GPLv2 or later',
+ );
+
+# Usage
+# =====
+# Use this script to hide and re-add lines into your Irssi view. You
+# can grab a custom-modified recentdepart.pl to hide smart-filtered
+# messages instead of ignore, if you do
+#
+# /set recdep_use_hideshow ON
+#
+# You can use trigger.pl with:
+#
+# /trigger add ... -command 'script exec $$Irssi::scripts::hideshow::hide_next = 1'
+#
+# instead of -stop
+
+# Options
+# =======
+# /set hideshow_level <levels>
+# * list of levels that should be hidden from view
+#
+# /set hideshow_hide <ON|OFF>
+# * if hiding is currently enabled or not. make a key binding to
+# conveniently toggle this setting (see below)
+
+# Commands
+# ========
+# you can use this key binding:
+#
+# /bind meta-= command ^toggle hideshow_hide
+#
+# /scrollback status hidden
+# * like /scrollback status, but for the hidden part (some statistics)
+
+no warnings 'redefine';
+use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK};
+use Irssi;
+use Irssi::TextUI;
+use Encode;
+
+
+
+sub setc () {
+ $IRSSI{name}
+}
+
+sub set ($) {
+ setc . '_' . $_[0]
+}
+
+my (%hidden);
+
+my $dest;
+
+my $HIDE;
+my $hide_level;
+my $ext_hidden_level = MSGLEVEL_LASTLOG << 1;
+
+
+sub show_win_lines {
+ my $win = shift;
+ my $view = $win->view;
+ my $vid = $view->{_irssi};
+ my $hl = delete $hidden{$vid};
+ return unless $hl && %$hl;
+ my $redraw;
+ my $bottom = $view->{bottom};
+ for (my $lp = $view->{buffer}{cur_line}; $lp; $lp = $lp->prev) {
+ my $nl = delete $hl->{ $lp->{_irssi} };
+ next unless $nl;
+ my $ll = $lp;
+ for my $i (@$nl) {
+ $win->gui_printtext_after($ll, $i->[1] | MSGLEVEL_NEVER, "${$i}[0]\n", $i->[2]);
+ $ll = $win->last_line_insert;
+ $redraw = 1;
+ }
+ }
+ if ($redraw) {
+ $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
+ $view->redraw;
+ }
+ delete $hidden{$vid};
+}
+sub show_lines {
+ for my $win (Irssi::windows) {
+ show_win_lines($win);
+ }
+ %hidden=();
+}
+
+sub hide_win_lines {
+ my $win = shift;
+ my $view = $win->view;
+ my $vid = $view->{_irssi};
+ my $bottom = $view->{bottom};
+ my $redraw;
+ my $prev;
+ my $lid;
+ for (my $lp = $view->{buffer}{cur_line}; $lp; $lp = $prev) {
+ $prev = $lp->prev;
+ if ($prev && $lp->{info}{level} & ($hide_level | $ext_hidden_level)) {
+ push @{ $hidden{ $vid }
+ { $prev->{_irssi} }
+ }, [ $lp->get_text(1), $lp->{info}{level}, $lp->{info}{time} ],
+ $hidden{$vid}{ $lp->{_irssi } } ? @{ (delete $hidden{$vid}{ $lp->{_irssi } }) } : ();
+ $view->remove_line($lp);
+ $redraw = 1;
+ }
+ }
+ if ($redraw) {
+ $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
+ $view->redraw;
+ }
+}
+sub hide_lines {
+ Irssi::signal_remove('gui textbuffer line removed' => 'fix_lines');
+ for my $win (Irssi::windows) {
+ hide_win_lines($win);
+ }
+ Irssi::signal_add_last('gui textbuffer line removed' => 'fix_lines');
+}
+
+my %hideshow_timed;
+sub show_one_timed {
+ my $hide = shift;
+ for my $win (Irssi::windows) {
+ next if $hideshow_timed{ $win->{_irssi} };
+ if ($hide) {
+ Irssi::signal_remove('gui textbuffer line removed' => 'fix_lines');
+ hide_win_lines($win);
+ Irssi::signal_add_last('gui textbuffer line removed' => 'fix_lines');
+ }
+ else {
+ show_win_lines($win);
+ }
+ $hideshow_timed{$win->{_irssi}} = 1;
+ $hideshow_timed{_timer} = Irssi::timeout_add_once(10 + int rand 10, 'show_one_timed', $hide);
+ return;
+ }
+ unless ($hide) {
+ show_lines();
+ }
+ %hideshow_timed = ();
+ hideshow() if !!$hide != !!$HIDE;
+ return 1;
+}
+sub hideshow {
+ if (exists $hideshow_timed{_timer}) {
+ Irssi::timeout_remove(delete $hideshow_timed{_timer});
+ }
+ %hideshow_timed = ();
+ $hideshow_timed{_timer} = Irssi::timeout_add_once(10 + int rand 10, 'show_one_timed', !!$HIDE);
+}
+
+sub setup_changed {
+ my $old_level = $hide_level;
+ $hide_level = Irssi::settings_get_level( set 'level' );
+ my $old_hidden = $HIDE;
+ $HIDE = Irssi::settings_get_bool( set 'hide' );
+ if (!defined $old_hidden || $HIDE != $old_hidden || $old_level != $hide_level) {
+ hideshow();
+ }
+}
+
+sub init_hideshow {
+ setup_changed();
+ $Irssi::scripts::hideshow::hide_next = undef;
+}
+
+sub UNLOAD {
+ show_lines();
+}
+
+my $multi_msgs_last;
+
+sub prt_text_issue {
+ $dest = $_[0];
+ my $stripd = $_[2];
+ if (ref $dest && $Irssi::scripts::hideshow::hide_next) {
+ $multi_msgs_last = undef;
+ $dest->{hide} = 1;
+ if ($dest->{level} & (MSGLEVEL_QUITS|MSGLEVEL_NICKS)) {
+ $multi_msgs_last = $stripd;
+ }
+ }
+ elsif (ref $dest && $dest->{level} & (MSGLEVEL_QUITS|MSGLEVEL_NICKS)
+ && defined $multi_msgs_last && $multi_msgs_last eq $stripd) {
+ $dest->{hide} = 1;
+ }
+ else {
+ $multi_msgs_last = undef;
+ }
+ $Irssi::scripts::hideshow::hide_next = undef;
+}
+
+sub prt_text_ref {
+ return unless ref $dest;
+ my ($win) = @_;
+ if ($HIDE) {
+ my $view = $win->view;
+ my $vid = $view->{_irssi};
+ my $lp = $view->{buffer}{cur_line};
+ my $prev = $lp->prev;
+ if ($prev && ($dest->{hide} || $lp->{info}{level} & $hide_level)) {
+ my $level = $lp->{info}{level};
+ $level |= $ext_hidden_level if $dest->{hide};
+ push @{ $hidden{ $vid }
+ { $prev->{_irssi} }
+ }, [ $lp->get_text(1), $level, $lp->{info}{time} ];
+ $view->remove_line($lp);
+ delete @{ $hidden{ $vid } }
+ { (grep {
+ $view->{buffer}{first_line}{info}{time} > $hidden{$vid}{$_}[-1][2]
+ } keys %{$hidden{$vid}}) };
+ $view->redraw;
+ }
+ }
+ $dest = undef;
+}
+
+sub fix_lines {
+ my ($view, $rem_line, $prev_line) = @_;
+ my $vid = $view->{_irssi};
+ my $nl = delete $hidden{$vid}{ $rem_line->{_irssi} };
+ if ($nl && $prev_line) {
+ push @{ $hidden{$vid} { $prev_line->{_irssi} } }, @$nl
+ }
+}
+
+sub win_del {
+ my ($win) = @_;
+ delete $hidden{ $win->view->{_irssi} };
+}
+Irssi::signal_register({
+ 'gui textbuffer line removed' => [ qw/Irssi::TextUI::TextBufferView Irssi::TextUI::Line Irssi::TextUI::Line/ ]
+});
+
+Irssi::signal_add_last({
+ 'setup changed' => 'setup_changed',
+ 'gui print text finished' => 'prt_text_ref',
+ 'gui textbuffer line removed' => 'fix_lines',
+});
+Irssi::signal_add({
+ 'print text' => 'prt_text_issue',
+ 'window destroyed' => 'win_del',
+});
+Irssi::settings_add_level( setc, set 'level', '' );
+Irssi::settings_add_bool( setc, set 'hide', 1 );
+Irssi::command_bind({
+ 'scrollback status' => sub {
+ if ($_[0] =~ /\S/) {
+ &Irssi::command_runsub('scrollback status', @_);
+ Irssi::signal_stop;
+ }
+ },
+ 'scrollback status hidden' => sub {
+ my %vw = map { ($_->view->{_irssi}, $_->{refnum}) } Irssi::windows;
+ my ($tl, $ta, $td) = (0, 0, 0);
+ for my $v (keys %hidden) {
+ my $hl = $hidden{$v};
+ my ($lc, $dc, $ac) = (0, 0, scalar keys %$hl);
+ for my $k (keys %$hl) {
+ my $ls = $hl->{$k};
+ $lc += @$ls;
+ $dc += 16 + length $_->[0] for @$ls;
+ }
+ $tl += $lc; $ta += $ac; $td += $dc;
+ print CLIENTCRAP sprintf "Window %d: %d lines hidden, %d anchors, %dkB of data", ($vw{$v}//"??"), $lc, $ac, int($dc/1024);
+ }
+ print CLIENTCRAP sprintf "Total: %d lines hidden, %d anchors, %dkB of data", $tl, $ta, int($td/1024);
+ }
+});
+init_hideshow();
+
+{ package Irssi::Nick }
diff --git a/scripts/hlscroll.pl b/scripts/hlscroll.pl
new file mode 100644
index 0000000..47a4066
--- /dev/null
+++ b/scripts/hlscroll.pl
@@ -0,0 +1,83 @@
+use strict;
+use Irssi qw(command_bind MSGLEVEL_HILIGHT);
+use vars qw($VERSION %IRSSI);
+
+# Recommended key bindings: alt+pgup, alt+pgdown:
+# /bind meta2-5;3~ /scrollback hlprev
+# /bind meta2-6;3~ /scrollback hlnext
+
+$VERSION = '0.02';
+%IRSSI = (
+ authors => 'Juerd, Eevee',
+ contact => '#####@juerd.nl',
+ name => 'Scroll to hilights',
+ description => 'Scrolls to previous or next highlight',
+ license => 'Public Domain',
+ url => 'http://juerd.nl/site.plp/irssi',
+ changed => 'Fri Apr 13 05:48 CEST 2012',
+ inspiration => '@eevee on Twitter: "i really want irssi keybindings that will scroll to the next/previous line containing a highlight. why does this not exist"',
+);
+
+sub _hlscroll{
+ my ($direction, $data, $server, $witem) = @_;
+ $witem or return;
+ my $window = $witem->window or return;
+
+ my $view = $window->view;
+ my $line = $view->{buffer}->{cur_line};
+ my $delta = $direction eq 'prev' ? -1 : 1;
+
+ my $linesleft = $view->{ypos} - $view->{height} + 1;
+ my $scrollby = 0; # how many display lines to scroll to the next highlight
+
+ # find the line currently at the bottom of the screen
+ while (1) {
+ my $line_height = $view->get_line_cache($line)->{count};
+
+ if ($linesleft < $line_height) {
+ # found it!
+ if ($direction eq 'prev') {
+ # skip however much of $line is on the screen
+ $scrollby = $linesleft - $line_height;
+ }
+ else {
+ # skip however much of $line is off the screen
+ $scrollby = $linesleft;
+ }
+
+ last;
+ }
+
+ $linesleft -= $line_height;
+
+ last if not $line->prev;
+ $line = $line->prev;
+ }
+
+ while ($line->$direction) {
+ $line = $line->$direction;
+ my $line_height = $view->get_line_cache($line)->{count};
+
+ if ($line->{info}{level} & MSGLEVEL_HILIGHT) {
+ # this algorithm scrolls to the "border" between lines -- if
+ # scrolling down, add in the line's entire height so it's entirely
+ # visible
+ if ($direction eq 'next') {
+ $scrollby += $delta * $line_height;
+ }
+
+ $view->scroll($scrollby);
+ return;
+ }
+
+ $scrollby += $delta * $line_height;
+ }
+
+ if ($direction eq 'next' and not $line->next) {
+ # scroll all the way to the bottom, after the last highlight
+ $view->scroll_line($line);
+ }
+};
+
+command_bind 'scrollback hlprev' => sub { _hlscroll('prev', @_) };
+command_bind 'scrollback hlnext' => sub { _hlscroll('next', @_) };
diff --git a/scripts/ido_switcher.pl b/scripts/ido_switcher.pl
new file mode 100644
index 0000000..ddcf06e
--- /dev/null
+++ b/scripts/ido_switcher.pl
@@ -0,0 +1,1166 @@
+=pod
+
+=head1 NAME
+
+ido_switcher.pl
+
+=head1 DESCRIPTION
+
+Search and select windows similar to ido-mode for emacs
+
+=head1 INSTALLATION
+
+This script requires that you have first installed and loaded F<uberprompt.pl>
+
+Uberprompt can be downloaded from:
+
+L<https://github.com/shabble/irssi-scripts/raw/master/prompt_info/uberprompt.pl>
+
+and follow the instructions at the top of that file or its README for installation.
+
+If uberprompt.pl is available, but not loaded, this script will make one
+attempt to load it before giving up. This eliminates the need to precisely
+arrange the startup order of your scripts.
+
+=head2 SETUP
+
+C</bind ^G /ido_switch_start [options]>
+
+Where C<^G> is a key of your choice.
+
+=head2 USAGE
+
+C<C-g> (or whatever you've set the above bind to), enters IDO window switching mode.
+You can then type either a search string, or use one of the additional key-bindings
+to change the behaviour of the search. C<C-h> provides online help regarding
+the possible interactive options.
+
+=head3 EXTENDED USAGE:
+
+It is possible to pass arguments to the C</ido_switch_start> command, which
+correspond to some of the interactively settable parameters listed below.
+
+The following options are available:
+
+=over 4
+
+=item C<-channels>
+
+Search through only channels.
+
+=item C<-queries>
+
+Search through only queries.
+
+=item C<-all>
+
+search both queries and channels (Default).
+
+=item C<-active>
+
+Lmit search to only window items with activity.
+
+=item C<-exact>
+
+Enable exact-substring matching
+
+=item C<-flex>
+
+Enable flex-string matching
+
+=back
+
+I<If neither of C<-exact>, C<-flex> or C<-regex> are given, the default is the value of
+C</set ido_use_flex>>
+
+=head4 EXAMPLE
+
+=over 2
+
+=item C</bind ^G /ido_switch_start -channels>
+
+=item C</bind ^F /ido_switch_start -queries -active>
+
+=back
+
+B<NOTE:> When entering window switching mode, the contents of your input line will
+be saved and cleared, to avoid visual clutter whilst using the switching
+interface. It will be restored once you exit the mode using either C<C-g>, C<Esc>,
+or C<RET>.
+
+=head3 INTERACTIVE COMMANDS
+
+The following key-bindings are available only once the mode has been
+activated:
+
+=over 4
+
+=item C<C-g>
+
+ Exit the mode without changing windows.
+
+=item C<Esc>
+
+Exit, as above.
+
+=item C<C-s>
+
+Rotate the list of window candidates forward by one item
+
+=item C<C-r>
+
+Rotate the list of window candidates backward by one item
+
+=item C<C-e>
+
+Toggle 'Active windows only' filter
+
+=item C<C-f>
+
+Switch between 'Flex' and 'Exact' matching.
+
+=item C<C-d>
+
+Select a network or server to filter candidates by
+
+=item C<C-u>
+
+Clear the current search string
+
+=item C<C-q>
+
+Cycle between showing only queries, channels, or all.
+
+=item C<C-SPC>
+
+Filter candidates by current search string, and then reset
+the search string
+
+=item C<RET>
+
+Select the current head of the candidate list (the green one)
+
+=item C<SPC>
+
+Select the current head of the list, without exiting the
+switching mode. The head is then moved one place to the right,
+allowing one to cycle through channels by repeatedly pressing space.
+
+=item C<TAB>
+
+B<[currently in development]> displays all possible completions
+at the bottom of the current window.
+
+=item I<All other keys> (C<a-z, A-Z>, etc)
+
+Add that character to the current search string.
+
+=back
+
+=head3 USAGE NOTES
+
+=over 4
+
+=item *
+
+Using C-e (show actives), followed by repeatedly pressing space will cycle
+through all your currently active windows.
+
+=item *
+
+If you enter a search string fragment, and realise that more than one candidate
+is still presented, rather than delete the whole string and modify it, you
+can use C-SPC to 'lock' the current matching candidates, but allow you to
+search through those matches alone.
+
+=back
+
+=head1 AUTHORS
+
+Based originally on L<window_switcher.pl|http://scripts.irssi.org/scripts/window_switcher.pl> script Copyright 2007 Wouter Coekaerts
+C<E<lt>coekie@irssi.orgE<gt>>.
+
+Primary functionality Copyright 2010-2011 Tom Feist
+C<E<lt>shabble+irssi@metavore.orgE<gt>>.
+
+=head1 LICENCE
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+=head1 BUGS:
+
+=over 4
+
+=item B<FIXED> Sometimes selecting a channel with the same name on a different
+ network will take you to the wrong channel.
+
+=back
+
+=head1 TODO
+
+=over 4
+
+=item B<DONE> C-g - cancel
+
+=item B<DONE> C-spc - narrow
+
+=item B<DONE> flex matching (on by default, but optional)
+
+=item TODO server/network narrowing
+
+=item B<DONE> colourised output (via uberprompt)
+
+=item B<DONE> C-r / C-s rotate matches
+
+=item B<DONE> toggle queries/channels
+
+=item B<DONE> remove inputline content, restore it afterwards.
+
+=item TODO tab - display all possibilities in window (clean up afterwards)
+how exactly will this work?
+
+=item B<DONE> sort by recent activity/recently used windows (separate commands?)
+
+=item B<TODO> need to be able to switch ordering of active ones (numerical, or most
+recently active, priority to PMs/hilights, etc?)
+
+=item B<DONE> should space auto-move forward to next window for easy stepping
+ through sequential/active windows?
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use Irssi;
+use Irssi::TextUI;
+use Data::Dumper;
+
+
+our $VERSION = '2.3'; # 1dc0a53a2df38e9
+our %IRSSI =
+ (
+ authors => 'Tom Feist, Wouter Coekaerts',
+ contact => 'shabble+irssi@metavore.org, shabble@#irssi/freenode',
+ name => 'ido_switcher',
+ description => 'Select window[-items] using an ido-mode like search interface',
+ license => 'GPLv2 or later',
+ url => 'http://github.com/shabble/irssi-scripts/tree/master/ido-mode/',
+ changed => '24/7/2010'
+ );
+
+
+
+my $CMD_NAME = 'ido_switch_start';
+my $CMD_OPTS = '-channels -queries -all -active -exact -flex -regex';
+
+
+my $input_copy = '';
+my $input_pos_copy = 0;
+
+my $ido_switch_active = 0; # for intercepting keystrokes
+
+my @window_cache = ();
+my @search_matches = ();
+
+my $match_index = 0;
+my $search_str = '';
+my $active_only = 0;
+my $regex_valid = 1;
+
+my $mode_type = 'ALL';
+my @mode_cache;
+my $showing_help = 0;
+
+my $need_clear = 0;
+
+my $sort_ordering = "start-asc";
+my $sort_active_first = 0;
+
+# /set configurable settings
+my $ido_show_count;
+my $ido_use_flex;
+
+my $DEBUG_ENABLED = 0;
+sub DEBUG () { $DEBUG_ENABLED }
+
+
+sub MODE_WIN () { 0 } # windows
+sub MODE_NET () { 1 } # chatnets
+#sub MODE_C () { 2 } # channels
+#sub MODE_S () { 3 } # select server
+#sub MODE_W () { 4 } # select window
+
+my $MODE = MODE_WIN;
+
+# check we have uberprompt loaded.
+
+my %need_clear;
+
+sub _print {
+ my $win = Irssi::active_win;
+ my $str = join('', @_);
+ $need_clear = 1;
+ $win->print($str, MSGLEVEL_NEVER);
+ push @{ $need_clear{ $win->{_irssi} } }, $win->view->{buffer}{cur_line};
+}
+
+sub _debug_print {
+ return unless DEBUG;
+ my $win = Irssi::active_win;
+ my $str = join('', @_);
+ $win->print($str, MSGLEVEL_CLIENTCRAP);
+}
+
+sub _print_clear {
+ return unless $need_clear;
+ for my $win (Irssi::windows) {
+ if (my $lines = delete $need_clear{ $win->{_irssi} }) {
+ my $view = $win->view;
+ my $bottom = $view->{bottom};
+ for my $line (@$lines) {
+ $view->remove_line($line);
+ }
+ $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
+ $view->redraw;
+ }
+ }
+ %need_clear=();
+}
+
+# TODO: use the code from rl_history_search to put this into a disposable
+# split win.
+# TODO: create formats for this.
+sub display_help {
+
+ my @message =
+ ('%_IDO Window Switching Help:%_',
+ '',
+ '%_Ctrl-g%_ %|- cancel out of the mode without changing windows.',
+ '%_Esc%_ %|- cancel out, as above.',
+ '%_Ctrl-s%_ %|- rotate the list of window candidates forward by 1',
+ '%_Ctrl-r%_ %|- rotate the list of window candidates backward by 1',
+ '%_Ctrl-e%_ %|- Toggle \'Active windows only\' filter',
+ '%_Ctrl-f%_ %|- Switch between \'Regex\', \'Flex\' and \'Exact\' matching.',
+# '%_Ctrl-d%_ %|- Select a network or server to filter candidates by',
+ '%_Ctrl-u%_ %|- Clear the current search string',
+ '%_Ctrl-q%_ %|- Cycle between showing only queries, channels, or all.',
+ '%_Ctrl-SPC%_ %|- Filter candidates by current search string, and then reset the search string',
+ '%_RET%_ %|- Select the current head of the candidate list (the %_green%n one)',
+ '%_SPC%_ %|- Select the current head of the list, without exiting switching mode. The head '
+ .'is then moved one place to the right, allowing one to cycle through channels by repeatedly '
+ .'pressing space.',
+ '%_TAB%_ %|- displays all possible completions at the bottom of the current window.',
+ '',
+ ' %_All other keys (a-z, A-Z, etc) - Add that character to the',
+ ' %_current search string.',
+ '',
+ '%_Press Any Key to return%_',
+ );
+
+ _print($_) for @message;
+ $showing_help = 1;
+}
+
+sub print_all_matches {
+ my $message_header = "Windows:";
+ my $win = Irssi::active_win();
+ my $win_width = $win->{width} || 80;
+
+ # TODO: needs to prefix ambig things with chatnet, or maybe order in groups
+ # by chatnet with newlines.
+
+ # Also, colourise the channel list.
+
+ my $col_width = 1;
+
+ for (@search_matches) {
+ my $len = length($_->{num} . ':' . _format_display_tag($_) . $_->{name});
+ $col_width = $len if $len > $col_width;
+ }
+
+ my $cols = int($win_width / $col_width);
+
+ my @lines;
+ my $i = 0;
+ my @line;
+
+ for my $item (@search_matches) {
+ ++$i;
+ my $name = $item->{name};
+ push @line, sprintf('%*s', -(10+$col_width), "\cD4/".$item->{num}.":\cD3/"._format_display_tag($item)."\cD4/".$name);
+ if ($i == $cols) {
+ push @lines, join ' ', @line;
+ @line = ();
+ $i = 0;
+ }
+ }
+ # flush rest out.
+ push @lines, join ' ', @line;
+
+ _print($message_header);
+ _print($_) for (@lines);
+ #_print("Longtest name: $longest_name");
+}
+
+unless ("Irssi::Script::uberprompt"->can('init')) {
+
+ print "Warning, this script requires '\%_uberprompt.pl\%_' in order to work. ";
+
+}
+
+sub ido_switch_init {
+ #Irssi::settings_add_bool('ido_switch', 'ido_switch_debug', 0);
+ Irssi::settings_add_str('ido_switch', 'ido_use_flex', 'flex');
+ Irssi::settings_add_bool('ido_switch', 'ido_show_active_first', 1);
+ Irssi::settings_add_int ('ido_switch', 'ido_show_count', 5);
+
+
+ Irssi::command_bind($CMD_NAME, \&ido_switch_start);
+ Irssi::command_set_options($CMD_NAME, $CMD_OPTS);
+
+ Irssi::signal_add ('setup changed' => \&setup_changed);
+ Irssi::signal_add_first('gui key pressed' => \&handle_keypress);
+
+ setup_changed();
+}
+
+sub setup_changed {
+ #$DEBUG_ENABLED = Irssi::settings_get_bool('ido_switch_debug');
+ $ido_show_count = Irssi::settings_get_int ('ido_show_count');
+ $ido_use_flex = _flex_mode(Irssi::settings_get_str('ido_use_flex'));
+ $sort_active_first = Irssi::settings_get_bool('ido_show_active_first');
+}
+
+sub ido_switch_start {
+
+ my ($args, $server, $witem) = @_;
+
+ # store copy of input line to restore later.
+ $input_copy = Irssi::parse_special('$L');
+ $input_pos_copy = Irssi::gui_input_get_pos();
+
+ Irssi::gui_input_set('');
+
+ my $options = {};
+ my @opts = Irssi::command_parse_options($CMD_NAME, $args);
+ if (@opts and ref($opts[0]) eq 'HASH') {
+ $options = $opts[0];
+ #print "Options: " . Dumper($options);
+ }
+
+ # clear / initialise match variables.
+ $ido_switch_active = 1;
+ $search_str = '';
+ $match_index = 0;
+ @mode_cache = ();
+
+ # configure settings from provided arguments.
+
+ # use provided options first, or fall back to /setting.
+ $ido_use_flex = _flex_mode(exists $options->{exact}
+ ? 'exact'
+ : exists $options->{flex}
+ ? 'flex'
+ : exists $options->{regex}
+ ? 'regex'
+ : Irssi::settings_get_str('ido_use_flex'));
+
+ # only select active items
+ $active_only = exists $options->{active};
+
+ # what type of items to search.
+ $mode_type = exists $options->{queries}
+ ? 'QUERY'
+ : exists $options->{channels}
+ ? 'CHANNEL'
+ : 'ALL';
+
+ _debug_print "Win cache: " . join(", ", map { $_->{name} } @window_cache);
+
+ _update_cache();
+
+ update_matches();
+ update_window_select_prompt();
+}
+
+sub _flex_mode {
+ if ($_[0] =~ /flex/i) {
+ 'Flex'
+ } elsif ($_[0] =~ /regex/i) {
+ 'Regex'
+ } else {
+ 'Exact'
+ }
+}
+
+sub _update_cache {
+ @window_cache = get_all_windows();
+}
+
+sub _build_win_obj {
+ my ($win, $win_item) = @_;
+
+ my @base = (
+ b_pos => -1,
+ e_pos => -1,
+ hilight_field => 'name',
+ active => $win->{data_level} > 0,
+ num => $win->{refnum},
+ server => $win->{active_server},
+
+ );
+
+ if (defined($win_item)) {
+ return (
+ @base,
+ name => $win_item->{visible_name},
+ type => $win_item->{type},
+ itemname => $win_item->{name},
+ active => $win_item->{data_level} > 0,
+ server => $win_item->{server},
+
+ )
+ } else {
+ return (
+ @base,
+ name => $win->{name},
+ type => 'WIN',
+ );
+ }
+}
+
+sub get_all_windows {
+ my @ret;
+
+ foreach my $win (Irssi::windows()) {
+ my @items = $win->items();
+
+ if ($win->{name} ne '') {
+ _debug_print "Adding window: " . $win->{name};
+ push @ret, { _build_win_obj($win, undef) };
+ }
+ if (scalar @items) {
+ foreach my $item (@items) {
+ _debug_print "Adding windowitem: " . $item->{visible_name};
+ push @ret, { _build_win_obj($win, $item) };
+ }
+ } else {
+ if (not grep { $_->{num} == $win->{refnum} } @ret) {
+ my $item = { _build_win_obj($win, undef) };
+ $item->{name} = "Unknown";
+ push @ret, $item;
+ }
+ #_debug_print "Error occurred reading info from window: $win";
+ #_debug_print Dumper($win);
+ }
+ }
+ @ret = _sort_windows(\@ret);
+
+ return @ret;
+
+}
+
+sub _sort_windows {
+ my $list_ref = shift;
+ my @ret = @$list_ref;
+
+ @ret = sort { $a->{num} <=> $b->{num} } @ret;
+ if ($sort_active_first) {
+ my @active = grep { $_->{active} } @ret;
+ my @inactive = grep { not $_->{active} } @ret;
+
+ return (@active, @inactive);
+ } else {
+ return @ret;
+ }
+}
+
+sub ido_switch_select {
+ my ($selected, $tag) = @_;
+ if (!$selected) {
+ _debug_print "Error, selection invalid";
+ return;
+ }
+ _debug_print sprintf("Selecting window: %s (%d)",
+ $selected->{name}, $selected->{num});
+
+ Irssi::command("WINDOW GOTO " . $selected->{num});
+
+ if ($selected->{type} ne 'WIN') {
+ _debug_print "Selecting window item: " . $selected->{itemname};
+ my $i = 1; my $found;
+ for (Irssi::active_win->items) {
+ if ($_->{name} eq $selected->{itemname}) {
+ if (!defined $selected->{server} && !defined $_->{server}) {
+ $found = 1;
+ last;
+ }
+ if (defined $selected->{server} && defined $_->{server}
+ && $selected->{server}{tag} eq $_->{server}{tag}) {
+ $found = 1;
+ last;
+ }
+ }
+ ++$i;
+ }
+ Irssi::command("WINDOW ITEM GOTO " . ($found ? $i : $selected->{itemname}));
+ }
+
+ update_matches();
+}
+
+sub ido_switch_exit {
+ $ido_switch_active = 0;
+
+ _print_clear();
+
+ Irssi::gui_input_set($input_copy);
+ Irssi::gui_input_set_pos($input_pos_copy);
+ Irssi::signal_emit('change prompt', '', 'UP_INNER');
+}
+
+sub _order_matches {
+ return @_[$match_index .. $#_,
+ 0 .. $match_index - 1]
+}
+
+sub update_window_select_prompt {
+
+ # take the top $ido_show_count entries and display them.
+ my $match_count = scalar @search_matches;
+ my $show_count = $ido_show_count;
+ my $match_string = '[No matches]';
+
+ $show_count = $match_count if $match_count < $show_count;
+
+ if ($show_count > 0) { # otherwise, default message above.
+ _debug_print "Showing: $show_count matches";
+
+ my @ordered_matches = _order_matches(@search_matches);
+
+ my @display = @ordered_matches[0..$show_count - 1];
+
+ # determine which items are non-unique, if any.
+
+ my %uniq;
+
+ foreach my $res (@display) {
+ my $name = $res->{name};
+
+ if (!exists $uniq{$name}) {
+ $uniq{$name} = [];
+ }
+ push @{$uniq{$name}}, $res;
+ }
+
+ # and set a flag to ensure they have their network tag applied
+ # to them when drawn.
+ foreach my $name (keys %uniq) {
+ my @values = @{$uniq{$name}};
+ if (@values > 1) {
+ $_->{display_net} = 1 for @values;
+ }
+ }
+
+ # show the first entry in green
+
+ my $first = shift @display;
+ my $formatted_first = _format_display_entry($first, '%g');
+ unshift @display, $formatted_first;
+
+ # and array-slice-map the rest to be red.
+ # or yellow, if they have unviewed activity
+
+ @display[1..$#display]
+ = map
+ {
+ _format_display_entry($_, $_->{active}?'%y':'%r')
+
+ } @display[1..$#display];
+
+ # join em all up
+ $match_string = join ', ', @display;
+ }
+
+ my @indicators;
+
+ # indicator if flex mode is being used (C-f to toggle)
+ push @indicators, $ido_use_flex;
+ push @indicators, 'Active' if $active_only;
+ push @indicators, ucfirst(lc($mode_type));
+
+ my $flex = sprintf(' %%b[%%n%s%%b]%%n ', join ', ', @indicators);
+
+ my $search = '';
+ $search = (sprintf '`%s\': ', $search_str) if length $search_str;
+ $search = (sprintf '`%%R%s%%n\': ', $search_str) if (length $search_str && !$regex_valid);
+
+ Irssi::signal_emit('change prompt', $flex . $search . $match_string,
+ 'UP_INNER');
+}
+
+
+
+sub _format_display_entry {
+ my ($obj, $colour) = @_;
+
+ my $field = $obj->{hilight_field};
+ my $hilighted = { netname => _format_display_tag($obj).$obj->{name},
+ name => $obj->{name}, num => $obj->{num} };
+ my $show_tag = $obj->{display_net} || 0;
+
+ if ($obj->{b_pos} >= 0 && $obj->{e_pos} > $obj->{b_pos}) {
+ substr($hilighted->{$field}, $obj->{e_pos}, 0) = '%_';
+ substr($hilighted->{$field}, $obj->{b_pos}, 0) = '%_';
+ _debug_print "Showing $field as: " . $hilighted->{$field}
+ }
+
+ return sprintf('%s%s:%s%%n',
+ $colour,
+ $hilighted->{num},
+ $hilighted->{netname})
+ if $field eq 'netname';
+
+ return sprintf('%s%s:%s%s%%n',
+ $colour,
+ $hilighted->{num},
+ $show_tag ? _format_display_tag($obj) : '',
+ $hilighted->{name});
+}
+
+sub _format_display_tag {
+ my $obj = shift;
+ if (defined $obj->{server}) {
+ my $server = $obj->{server};
+ my $tag = $server->{tag};
+ return $tag . '/' if length $tag;
+ }
+ return '';
+}
+
+sub _check_active {
+ my ($obj) = @_;
+ return 1 unless $active_only;
+ return $obj->{active};
+}
+
+sub update_matches {
+ my $current_match = get_window_match();
+
+ _update_cache() unless $search_str;
+
+ if ($mode_type ne 'ALL') {
+ @mode_cache = @window_cache;
+ @window_cache = grep { $_->{type} eq $mode_type } @window_cache;
+ } else {
+ @window_cache = @mode_cache if @mode_cache;
+ }
+
+ my $field = 'name';
+ my $search_str2;
+ if ($search_str =~ m:^(.*)/(.*?)$:) {
+ $field = 'netname';
+ $search_str2 = "$2/$1";
+ }
+
+ $regex_valid = 1;
+ if ($search_str =~ m/^\d+$/) {
+
+ @search_matches =
+ grep {
+ _check_active($_) and regex_match($search_str, $_, 'num')
+ } @window_cache;
+
+ } elsif ($ido_use_flex eq 'Flex') {
+
+ @search_matches =
+ grep {
+ _check_active($_) and (flex_match($search_str, $_, $field) >= 0
+ || (defined $search_str2 && flex_match($search_str2, $_, $field) >= 0))
+ } @window_cache;
+
+ } elsif ($ido_use_flex eq 'Regex') {
+ my $regex = do { local $@;
+ my $ret = eval { qr/$search_str/ } || qr/\Q$search_str/;
+ if ($@) { $regex_valid = 0 }
+ $ret;
+ };
+ my $regex2 = defined $search_str2 ?
+ do { local $@; eval { qr/$search_str2/ } || qr/\Q$search_str2/ }
+ : undef;
+ @search_matches =
+ grep {
+ _check_active($_) and (regex_match($regex, $_, $field)
+ || (defined $regex2 && regex_match($regex2, $_, $field)))
+ } @window_cache;
+ } else {
+ @search_matches =
+ grep {
+ _check_active($_) and (regex_match(qr/\Q$search_str/, $_, $field)
+ || (defined $search_str2 && regex_match(qr/\Q$search_str2/, $_, $field)))
+ } @window_cache;
+ }
+
+ $match_index = 0;
+ if ($current_match) {
+ for my $idx (0..$#search_matches) {
+ if ($search_matches[$idx]{num} == $current_match->{num}
+ && $search_matches[$idx]{type} eq $current_match->{type}) {
+ $match_index = $idx;
+ if ($current_match->{type} eq 'WIN') {
+ last;
+ } elsif ($search_matches[$idx]{itemname} eq $current_match->{itemname}) {
+ last;
+ }
+ }
+ }
+ }
+
+}
+
+sub regex_match {
+ my ($regex, $obj, $field) = @_;
+ my $data = $field eq 'netname'
+ ? _format_display_tag($obj).$obj->{name} : $obj->{$field};
+ if ($data =~ m/$regex/i) {
+ $obj->{hilight_field} = $field;
+ $obj->{b_pos} = $-[0];
+ $obj->{e_pos} = $+[0];
+ return 1;
+ }
+ return 0;
+}
+
+sub flex_match {
+ my ($search_str, $obj, $field) = @_;
+
+ my $pattern = $search_str;
+ my $source = $field eq 'netname'
+ ? _format_display_tag($obj).$obj->{name} : $obj->{$field};
+
+ _debug_print "Flex match: $pattern / $source";
+
+ # default to matching everything if we don't have a pattern to compare
+ # against.
+
+ return 0 unless $pattern;
+
+ my @chars = split '', lc($pattern);
+ my $ret = -1;
+ my $first = 0;
+
+ my $lc_source = lc($source);
+
+ $obj->{hilight_field} = $field;
+
+ foreach my $char (@chars) {
+ my $pos = index($lc_source, $char, $ret);
+ if ($pos > -1) {
+
+ # store the beginning of the match
+ $obj->{b_pos} = $pos unless $first;
+ $first = 1;
+
+ _debug_print("matched: $char at $pos in $source");
+ $ret = $pos + 1;
+
+ } else {
+
+ $obj->{b_pos} = $obj->{e_pos} = -1;
+ _debug_print "Flex returning: -1";
+
+ return -1;
+ }
+ }
+
+ _debug_print "Flex returning: $ret";
+
+ #store the end of the match.
+ $obj->{e_pos} = $ret;
+
+ return $ret;
+}
+
+sub prev_match {
+
+ $match_index++;
+ if ($match_index > $#search_matches) {
+ $match_index = 0;
+ }
+
+ _debug_print "index now: $match_index";
+}
+
+sub next_match {
+
+ $match_index--;
+ if ($match_index < 0) {
+ $match_index = $#search_matches;
+ }
+ _debug_print "index now: $match_index";
+}
+
+sub get_window_match {
+ return $search_matches[$match_index];
+}
+
+sub handle_keypress {
+ my ($key) = @_;
+
+ return unless $ido_switch_active;
+
+ if ($showing_help) {
+ _print_clear();
+ $showing_help = 0;
+ Irssi::signal_stop();
+ }
+
+ if ($key == 0) { # C-SPC?
+ _debug_print "\%_Ctrl-space\%_";
+
+ $search_str = '';
+ @window_cache = @search_matches;
+ update_window_select_prompt();
+
+ Irssi::signal_stop();
+ return;
+ }
+
+ if ($key == 3) { # C-c
+ _print_clear();
+ Irssi::signal_stop();
+ return;
+ }
+ if ($key == 4) { # C-d
+# update_network_select_prompt();
+ Irssi::signal_stop();
+ return;
+ }
+
+ if ($key == 5) { # C-e
+ $active_only = not $active_only;
+ Irssi::signal_stop();
+ update_matches();
+ update_window_select_prompt();
+ return;
+ }
+
+ if ($key == 6) { # C-f
+
+ $ido_use_flex = ($ido_use_flex eq 'Regex' ? 'Flex'
+ : $ido_use_flex eq 'Flex' ? 'Exact'
+ : 'Regex');
+ _update_cache();
+
+ update_matches();
+ update_window_select_prompt();
+
+ Irssi::signal_stop();
+ return;
+ }
+ if ($key == 9) { # TAB
+ _debug_print "Tab complete";
+ _print_clear();
+ print_all_matches();
+ Irssi::signal_stop();
+ }
+
+ if ($key == 10) { # enter
+ _debug_print "selecting history and quitting";
+ my $selected_win = get_window_match();
+ ido_switch_select($selected_win);
+
+ ido_switch_exit();
+ Irssi::signal_stop();
+ return;
+ }
+ if ($key == 11) { # Ctrl-K
+ my $sel = get_window_match();
+ _debug_print("deleting entry: " . $sel->{num});
+ Irssi::command("window close " . $sel->{num});
+ _update_cache();
+ update_matches();
+ update_window_select_prompt();
+ Irssi::signal_stop();
+
+ }
+
+ if ($key == 18) { # Ctrl-R
+ _debug_print "skipping to prev match";
+ #update_matches();
+ next_match();
+
+ update_window_select_prompt();
+ Irssi::signal_stop(); # prevent the bind from being re-triggered.
+ return;
+ }
+
+ if ($key == 17) { # Ctrl-q
+ if ($mode_type eq 'CHANNEL') {
+ $mode_type = 'QUERY';
+ } elsif ($mode_type eq 'QUERY') {
+ $mode_type = 'ALL';
+ } else { # ALL
+ $mode_type = 'CHANNEL';
+ }
+ update_matches();
+ update_window_select_prompt();
+ Irssi::signal_stop();
+ }
+
+ if ($key == 19) { # Ctrl-s
+ _debug_print "skipping to next match";
+ prev_match();
+
+ #update_matches();
+ update_window_select_prompt();
+
+ Irssi::signal_stop();
+ return;
+ }
+
+ if ($key == 7) { # Ctrl-g
+ _debug_print "aborting search";
+ ido_switch_exit();
+ Irssi::signal_stop();
+ return;
+ }
+
+ if ($key == 8) { # Ctrl-h
+ display_help();
+ Irssi::signal_stop();
+ return;
+ }
+
+ if ($key == 21) { # Ctrl-u
+ $search_str = '';
+ update_matches();
+ update_window_select_prompt();
+
+ Irssi::signal_stop();
+ return;
+
+ }
+
+ if ($key == 127) { # DEL
+
+ if (length $search_str) {
+ $search_str = substr($search_str, 0, -1);
+ _debug_print "Deleting char, now: $search_str";
+ }
+
+ update_matches();
+ update_window_select_prompt();
+
+ Irssi::signal_stop();
+ return;
+ }
+
+ # TODO: handle esc- sequences and arrow-keys?
+
+ if ($key == 27) { # Esc
+ ido_switch_exit();
+ return;
+ }
+
+ if ($key == 32) { # space
+ my $selected_win = get_window_match();
+ ido_switch_select($selected_win);
+
+ prev_match();
+ update_window_select_prompt();
+
+ Irssi::signal_stop();
+
+ return;
+ }
+
+ if ($key > 32) { # printable
+ $search_str .= chr($key);
+
+ update_matches();
+ update_window_select_prompt();
+
+ Irssi::signal_stop();
+ return;
+ }
+
+ # ignore all other keys.
+ Irssi::signal_stop();
+}
+
+ido_switch_init();
+
+sub update_network_select_prompt {
+
+ my @servers = map
+ {
+ {
+ name => $_->{tag},
+ type => 'SERVER',
+ active => 0,
+ e_pos => -1,
+ b_pos => -1,
+ hilight_field => 'name',
+ }
+ } Irssi::servers();
+
+ my $match_count = scalar @servers;
+ my $show_count = $ido_show_count;
+ my $match_string = '(no matches) ';
+
+ $show_count = $match_count if $match_count < $show_count;
+
+ if ($show_count > 0) {
+ _debug_print "Showing: $show_count matches";
+
+ my @ordered_matches = _order_matches(@servers);
+ my @display = @ordered_matches[0..$show_count - 1];
+
+ # show the first entry in green
+
+ unshift(@display, _format_display_entry(shift(@display), '%g'));
+
+ # and array-slice-map the rest to be red (or yellow for active)
+ @display[1..$#display]
+ = map
+ {
+ _format_display_entry($_, $_->{active}?'%y':'%r')
+
+ } @display[1..$#display];
+
+ # join em all up
+ $match_string = join ', ', @display;
+ }
+
+ my @indicators;
+
+ # indicator if flex mode is being used (C-f to toggle)
+ push @indicators, $ido_use_flex;
+ push @indicators, 'Active' if $active_only;
+
+ my $flex = sprintf(' %%k[%%n%s%%k]%%n ', join ',', @indicators);
+
+ my $search = '';
+ $search = (sprintf '`%s\': ', $search_str) if length $search_str;
+ $search = (sprintf '`%%R%s%%n\': ', $search_str) if (length $search_str && !$regex_valid);
+
+ Irssi::signal_emit('change prompt', $flex . $search . $match_string,
+ 'UP_INNER');
+
+}
diff --git a/scripts/ircuwhois.pl b/scripts/ircuwhois.pl
new file mode 100644
index 0000000..4a01de0
--- /dev/null
+++ b/scripts/ircuwhois.pl
@@ -0,0 +1,84 @@
+use strict;
+use Irssi;
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '1.2';
+
+%IRSSI = (
+ authors => 'Valentin Batz',
+ contact => 'vb\@g-23.org',
+ name => 'ircuwhois',
+ description => 'show the accountname (330) and real host on ircu',
+ license => 'GPLv2',
+ url => 'http://www.hurzelgnom.homepage.t-online.de/irssi/scripts/quakenet.pl'
+);
+
+# adapted by Nei
+
+Irssi::theme_register([
+ 'whois_auth', '{whois account %|$1}',
+ 'whois_ip', '{whois actualip %|$1}',
+ 'whois_host', '{whois act.host %|$1}',
+ 'whois_oper', '{whois privile. %|$1}',
+ 'whois_ssl', '{whois connect. %|$1}'
+]);
+
+sub event_whois_default_event {
+ #'server event', SERVER_REC, char *data, char *sender_nick, char *sender_address
+ my ($server, $data, $snick, $sender) = @_;
+ my $numeric = $server->parse_special('$H');
+ if ($numeric eq '313') { &event_whois_oper }
+ if ($numeric eq '330') { &event_whois_auth }
+ if ($numeric eq '337') { &event_whois_ssl }
+ if ($numeric eq '338') { &event_whois_userip }
+}
+
+sub event_whois_oper {
+ my ($server, $data) = @_;
+ my ($num, $nick, $privileges) = split(/ /, $data, 3);
+ $privileges =~ s/^:(?:is an? )?//;
+ $server->printformat($nick, MSGLEVEL_CRAP, 'whois_oper', $nick, $privileges);
+ Irssi::signal_stop();
+}
+
+sub event_whois_auth {
+ my ($server, $data) = @_;
+ my ($num, $nick, $auth_nick, $isircu) = split(/ /, $data, 4);
+ return unless $isircu =~ / as/; #:is logged in as
+ $server->printformat($nick, MSGLEVEL_CRAP, 'whois_auth', $nick, $auth_nick);
+ Irssi::signal_stop();
+}
+
+sub event_whois_ssl {
+ my ($server, $data) = @_;
+ my ($num, $nick, $connection) = split(/ /, $data, 3);
+ $connection =~ s/^:(?:is using an? )?//;
+ $server->printformat($nick, MSGLEVEL_CRAP, 'whois_ssl', $nick, $connection);
+ Irssi::signal_stop();
+}
+
+sub event_whois_userip {
+ my ($server, $data) = @_;
+ my ($num, $nick, $userhost, $ip, $isircu) = split(/ /, $data, 5);
+ return unless $isircu =~ /ctual /; #:Actual user@host, Actual IP
+ $server->printformat($nick, MSGLEVEL_CRAP, 'whois_ip', $nick, $ip);
+ $server->printformat($nick, MSGLEVEL_CRAP, 'whois_host', $nick, $userhost);
+ Irssi::signal_stop();
+}
+
+sub debug {
+ use Data::Dumper;
+ Irssi::print(Dumper(\@_));
+}
+Irssi::signal_register({
+ 'whois oper' => [ 'iobject', 'string', 'string', 'string' ],
+}); # fixes oper display in 0.8.10
+Irssi::signal_add({
+ 'whois oper' => 'event_whois_oper',
+ 'event 313' => 'event_whois_oper',
+ 'event 330' => 'event_whois_auth',
+ 'event 337' => 'event_whois_ssl',
+ 'event 338' => 'event_whois_userip',
+ 'whois default event' => 'event_whois_default_event',
+});
+
diff --git a/scripts/linebuffer.pl b/scripts/linebuffer.pl
new file mode 100644
index 0000000..9e002bb
--- /dev/null
+++ b/scripts/linebuffer.pl
@@ -0,0 +1,278 @@
+use strict;
+use warnings;
+use Irssi;
+use Irssi::TextUI;
+use Hash::Util qw();
+our $VERSION = '0.2'; # c1eddc6a0d6385a
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'linebuffer',
+ description => 'dump the linebuffer content',
+ license => 'GNU GPLv2 or later',
+ );
+
+sub cmd_help {
+ my ($args) = @_;
+ if ($args =~ /^dumplines *$/i) {
+ print CLIENTCRAP <<HELP
+
+DUMPLINES [-file <filename>] [-format] [-ids] [-levels[-prepend|-hex]] [-time] [<count> [<refnum>]]
+
+ Dump the content of the line buffer to a window or file.
+
+ -file: Output to this file.
+ -format: Format the text output.
+ -ids: Print line IDs.
+ -levels: Print levels. -prepend: before text, -hex: as hex value
+ -time: Print time stamp.
+ count: Number of lines to reproduce.
+ refnum: Specifies the window to dump.
+HELP
+
+ }
+}
+
+{
+ my %control2format_d = (
+ 'a' => 'F',
+ 'c' => '_',
+ 'e' => '|',
+ 'i' => '#',
+ 'f' => 'I',
+ 'g' => 'n',
+ );
+ my %control2format_c = (
+ "\c_" => 'U',
+ "\cV" => '8',
+ );
+ my %base_bg = (
+ '0' => '0',
+ '1' => '4',
+ '2' => '2',
+ '3' => '6',
+ '4' => '1',
+ '5' => '5',
+ '6' => '3',
+ '7' => '7',
+ '8' => 'x08',
+ '9' => 'x09',
+ ':' => 'x0a',
+ ';' => 'x0b',
+ '<' => 'x0c',
+ '=' => 'x0d',
+ '>' => 'x0e',
+ '?' => 'x0f',
+ );
+ my %base_fg = (
+ '0' => 'k',
+ '1' => 'b',
+ '2' => 'g',
+ '3' => 'c',
+ '4' => 'r',
+ '5' => 'm', # p
+ '6' => 'y',
+ '7' => 'w',
+ '8' => 'K',
+ '9' => 'B',
+ ':' => 'G',
+ ';' => 'C',
+ '<' => 'R',
+ '=' => 'M', # P
+ '>' => 'Y',
+ '?' => 'W',
+ );
+
+ my $to_true_color = sub {
+ my (@rgbx) = map { ord } @_;
+ $rgbx[3] -= 0x20;
+ for (my $i = 0; $i < 3; ++$i) {
+ if ($rgbx[3] & (0x10 << $i)) {
+ $rgbx[$i] -= 0x20;
+ }
+ }
+ my $color = $rgbx[0] << 16 | $rgbx[1] << 8 | $rgbx[2];
+ ($rgbx[3] & 0x1 ? 'z' : 'Z') . sprintf '%06X', $color;
+ };
+
+ my %ext_color_off = (
+ '.' => [0, 0x10],
+ '-' => [0, 0x60],
+ ',' => [0, 0xb0],
+ '+' => [1, 0x10],
+ "'" => [1, 0x60],
+ '&' => [1, 0xb0],
+ );
+ my @ext_color_al = (0..9, 'A' .. 'Z');
+ my $to_ext_color = sub {
+ my ($sig, $chr) = @_;
+ my ($bg, $off) = @{ $ext_color_off{$sig} };
+ my $color = $off - 0x3f + ord $chr;
+ $color += 10 if $color > 213;
+ ($bg ? 'x' : 'X') . (1+int($color / 36)) . $ext_color_al[$color % 36];
+ };
+ sub control2format {
+ my $line = shift;
+ $line =~ s/%/%%/g;
+ $line =~ s{( \c_ | \cV )
+ |(?:\cD(?:
+ ([aceigf])
+ |(?:\#(.)(.)(.)(.))
+ |(?:([-.,+'&])(.))
+ |(?:(?:/|([0-?]))(?:/|([/0-?])))
+ |\xff/|(/\xff)
+ ))
+ }{
+ '%'.(defined $1 ? $control2format_c{$1} :
+ defined $2 ? $control2format_d{$2} :
+ defined $6 ? $to_true_color->($3,$4,$5,$6) :
+ defined $8 ? $to_ext_color->($7,$8) :
+ defined $10 ? ($base_bg{$10} . (defined $9 ? '%'.$base_fg{$9} : '')) :
+ defined $9 ? $base_fg{$9} :
+ defined $11 ? 'o' : 'n')
+ }gex;
+ $line
+ }
+}
+
+sub simpletime {
+ my ($sec, $min, $hour, $mday, $mon, $year) = localtime $_[0];
+ sprintf "%04d"."%02d"x5, 1900+$year, 1+$mon, $mday, $hour, $min, $sec;
+}
+
+sub prt_report {
+ my $fh = shift;
+ if ($fh->isa('Irssi::UI::Window')) {
+ for (split "\n", (join $,//'', @_)) {
+ my $line;
+ for (split "\t") {
+ if (defined $line) {
+ $line .= ' ' x (5 - (length $line) % 6);
+ $line .= ' ';
+ }
+ $line .= $_;
+ }
+ $line .= '';
+ $fh->print($line, MSGLEVEL_NEVER);
+ }
+ }
+ else {
+ $fh->print(@_);
+ }
+}
+
+sub dump_lines {
+ my ($data, $server, $item) = @_;
+ my ($args, $rest) = Irssi::command_parse_options('dumplines', $data);
+ ref $args or return;
+ my $win = Irssi::active_win;
+ my ($count, $winnum) = $data =~ /(-?\d+)/g;
+ if (defined $winnum) {
+ $win = Irssi::window_find_refnum($winnum) // do {
+ print CLIENTERROR "Window #$winnum not found";
+ return;
+ };
+ }
+ my $fh;
+ my $is_file;
+ if (defined $args->{file}) {
+ unless (length $args->{file}) {
+ print CLIENTERROR "Missing argument to option: file";
+ return;
+ }
+ open $fh, '>', $args->{file} or do {
+ print CLIENTERROR "Error opening ".$args->{file}.": $!";
+ return;
+ };
+ $is_file = 1;
+ }
+ else {
+ $fh = Irssi::Windowitem::window_create(undef, 0);
+ $fh->command('^scrollback home');
+ $fh->command('^scrollback clear');
+ $fh->command('^window scroll off');
+ }
+ prt_report($fh, "\n==========\nwindow: ", $win->{refnum}, "\n");
+ my $view = $win->view;
+ my $lclength = length $view->{buffer}{lines_count};
+ $lclength = 3 if $lclength < 3;
+ my $padlen = $lclength;
+ my $hdr = sprintf "%${lclength}s", " # ";
+ my $hllen = length sprintf '%x', MSGLEVEL_LASTLOG << 1;
+ #123456789012345
+ if (defined $args->{ids}) { $padlen += 10; $hdr .= '| ID ' }
+ if (defined $args->{time}) { $padlen += 15; $hdr .= '| date & time ' }
+ if (defined $args->{'levels-hex'}) { $padlen += $hllen + 1; $hdr .= sprintf "|%${hllen}s", ' levels ' }
+
+ prt_report($fh,
+ " "x$padlen,"\t/buffer first line\n",
+ " "x$padlen,"\t|/buffer cur line\n",
+ " "x$padlen,"\t||/bottom start line\n",
+ $hdr,"\t|||/start line\n");
+ my $j = 1;
+ $count = $view->{height} unless $count;
+ my $start_line;
+ if ($count < 0) {
+ $start_line = $view->get_lines;
+ }
+ else {
+ $j = $view->{buffer}{lines_count} - $count + 1;
+ $j = 1 if $j < 1;
+ $start_line = $view->{buffer}{cur_line};
+ for (my $line = $start_line;
+ $line && $count--;
+ ($start_line, $line) = ($line, $line->prev))
+ {}
+ }
+ for (my $line = $start_line; $line; $line = $line->next) {
+ my $i = 0;
+ my $t = sprintf "%${lclength}d", $j++;
+ $t .= sprintf " %9d", $line->{_irssi} if defined $args->{ids};
+ $t .= ' '.simpletime($line->{info}{time}) if defined $args->{time};
+ $t .= sprintf " %${hllen}x", $line->{info}{level} if defined $args->{'levels-hex'};
+ $t .= "\t" . (join '', map {;++$i; $_->{_irssi} == $line->{_irssi} ? $i : ' ' }
+ $view->{buffer}{first_line}, $view->{buffer}{cur_line},
+ $view->{bottom_startline}, $view->{startline});
+ $t .= "\t";
+ my $text = $line->get_text(1);
+ if (defined $args->{format}) {
+ if (!$is_file) {
+ $text = control2format($text);
+ $text =~ s{(%.)}{ $1 eq "%o" ? "\cD/\xff" : $1 }ge;
+ }
+ }
+ else {
+ $text = control2format($text);
+ if (!$is_file) {
+ $text =~ s/%/%%/g;
+ }
+ }
+ my $lst;
+ if (defined $args->{'levels-prepend'} || defined $args->{levels}) {
+ my $levels = Irssi::bits2level($line->{info}{level});
+ if (!$is_file) {
+ $lst = "%n%r[%n$levels%r]%n";
+ }
+ else {
+ $lst = "[$levels]";
+ }
+ }
+ $t .= "$lst\t" if defined $args->{'levels-prepend'};
+ $t .= $text;
+ $t .= "\t$lst" if defined $args->{levels};
+ $t .= "\n";
+ prt_report($fh, $t);
+ }
+ prt_report($fh, "----------\n", map { $_ // 'NULL' }
+ "view w", $view->{width}, " h", $view->{height}, " scroll ", $view->{scroll}, "\n",
+ " ypos ", $view->{ypos}, "\n",
+ " bottom subline ", $view->{bottom_subline}, " subline ", $view->{subline}, ", is bottom: ", $view->{bottom}, "\n",
+ "buffer: lines count ", $view->{buffer}{lines_count}, ", was last eol: ", $view->{buffer}{last_eol}, "\n",
+ "win: last line ", simpletime($win->{last_line}),"\n\n");
+}
+
+
+Irssi::command_bind('dumplines' => 'dump_lines');
+Irssi::command_set_options('dumplines' => 'format ids time levels levels-prepend levels-hex 1 -file');
+Irssi::command_bind_last('help' => 'cmd_help');
diff --git a/scripts/messages_bottom.pl b/scripts/messages_bottom.pl
new file mode 100644
index 0000000..8bf329d
--- /dev/null
+++ b/scripts/messages_bottom.pl
@@ -0,0 +1,29 @@
+use strict;
+use Irssi ();
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '1.0';
+
+%IRSSI = (
+ authors => 'Wouter Coekaerts',
+ contact => 'coekie@irssi.org',
+ name => 'messages_bottom',
+ description => 'makes all window text start at the bottom of windows',
+ license => q(send-me-beer-or-i'll-sue-you-if-you-use-it license),
+ url => 'http://bugs.irssi.org/index.php?do=details&id=290'
+);
+
+##########################
+#
+# add this line to the very top of your ~/.irssi/startup file:
+#
+# script exec Irssi::active_win->print('\n' x Irssi::active_win->{'height'}, Irssi::MSGLEVEL_NEVER)
+#
+#
+
+Irssi::signal_add_last
+ 'window created' => sub {
+ my $win = shift;
+ $win->print(
+ "\n" x $win->{'height'},
+ Irssi::MSGLEVEL_NEVER ) }
diff --git a/scripts/mouse-awl.pl b/scripts/mouse-awl.pl
new file mode 100644
index 0000000..f395199
--- /dev/null
+++ b/scripts/mouse-awl.pl
@@ -0,0 +1,144 @@
+# cooperates with adv_windowlist
+# See http://wouter.coekaerts.be/site/irssi/mouse
+# based on irssi mouse patch by mirage: http://darksun.com.pt/mirage/irssi/
+
+# Copyright (C) 2005-2009 Wouter Coekaerts <wouter@coekaerts.be>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+use strict;
+use Irssi qw(signal_emit settings_get_str active_win signal_stop settings_add_str settings_add_bool settings_get_bool signal_add signal_add_first);
+use Math::Trig;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '1.0.0-awl';
+%IRSSI = (
+ authors => 'Wouter Coekaerts',
+ contact => 'wouter@coekaerts.be',
+ name => 'mouse',
+ description => 'control irssi using mouse clicks and gestures',
+ license => 'GPLv2 or later',
+ url => 'http://wouter.coekaerts.be/irssi/',
+ changed => '2009-05-16',
+);
+
+my @BUTTONS = ('', '_middle', '_right');
+
+my $mouse_xterm_status = -1; # -1:off 0,1,2:filling mouse_xterm_combo
+my @mouse_xterm_combo = (3, 0, 0); # 0:button 1:x 2:y
+my @mouse_xterm_previous; # previous contents of mouse_xterm_combo
+
+sub mouse_enable {
+ Irssi::command '^set awl_mouse on'
+}
+
+sub mouse_disable {
+ Irssi::command '^set awl_mouse off'
+}
+
+# Handle mouse event (button press or release)
+sub mouse_event {
+ my ($b, $x, $y, $oldb, $oldx, $oldy) = @_;
+ Irssi::signal_stop();
+ my ($xd, $yd);
+ my ($distance, $angle);
+
+ # uhm, in the patch the scrollwheel didn't work for me, but this does:
+ if ($b == 64) {
+ cmd("mouse_scroll_up");
+ } elsif ($b == 65) {
+ cmd("mouse_scroll_down")
+ }
+
+ # proceed only if a button is being released
+ return if ($b != 3);
+
+ return unless (0 <= $oldb && $oldb <= 2);
+ my $button = $BUTTONS[$oldb];
+
+ # if it was a mouse click of the left button (press and release in the same position)
+ if ($x == $oldx && $y == $oldy) {
+ cmd("mouse" . $button . "_click");
+ return;
+ }
+
+ # otherwise, find mouse gestures
+ $xd = $x - $oldx;
+ $yd = -1 * ($y - $oldy);
+ $distance = sqrt($xd*$xd + $yd*$yd);
+ # ignore small gestures
+ if ($distance < 3) {
+ return;
+ }
+ $angle = asin($yd/$distance) * 180 / 3.14159265358979;
+ if ($angle < 20 && $angle > -20 && $xd > 0) {
+ if ($distance <= 40) {
+ cmd("mouse" . $button . "_gesture_right");
+ } else {
+ cmd("mouse" . $button . "_gesture_bigright");
+ }
+ } elsif ($angle < 20 && $angle > -20 && $xd < 0) {
+ if ($distance <= 40) {
+ cmd("mouse" . $button . "_gesture_left");
+ } else {
+ cmd("mouse" . $button . "_gesture_bigleft");
+ }
+ } elsif ($angle > 40) {
+ cmd("mouse" . $button . "_gesture_up");
+ } elsif ($angle < -40) {
+ cmd("mouse" . $button . "_gesture_down");
+ }
+}
+
+# executes the command configured in the given setting
+sub cmd
+{
+ my ($setting) = @_;
+ signal_emit("send command", settings_get_str($setting), active_win->{'active_server'}, active_win->{'active'});
+}
+
+Irssi::command_bind 'mouse' => sub {
+ my ($data, $server, $item) = @_;
+ $data =~ s/\s+$//g;
+ Irssi::command_runsub('mouse', $data, $server, $item);
+};
+
+# temporarily disable mouse handling. Useful for copy-pasting without touching the keyboard (pressing shift)
+Irssi::command_bind 'mouse tempdisable' => sub {
+ my ($data, $server, $item) = @_;
+ my $seconds = ($data eq '') ? 5 : $data; # optional argument saying how many seconds, defaulting to 5
+ mouse_disable();
+ Irssi::timeout_add_once($seconds * 1000, 'mouse_enable', undef); # turn back on after $second seconds
+};
+
+for my $button (@BUTTONS) {
+ settings_add_str("lookandfeel", "mouse" . $button . "_click", "/mouse tempdisable 5");
+ settings_add_str("lookandfeel", "mouse" . $button . "_gesture_up", "/window last");
+ settings_add_str("lookandfeel", "mouse" . $button . "_gesture_down", "/window goto active");
+ settings_add_str("lookandfeel", "mouse" . $button . "_gesture_left", "/window prev");
+ settings_add_str("lookandfeel", "mouse" . $button . "_gesture_bigleft", "/eval window prev;window prev");
+ settings_add_str("lookandfeel", "mouse" . $button . "_gesture_right", "/window next");
+ settings_add_str("lookandfeel", "mouse" . $button . "_gesture_bigright", "/eval window next;window next");
+}
+
+settings_add_str("lookandfeel", "mouse_scroll_up", "/scrollback goto -10");
+settings_add_str("lookandfeel", "mouse_scroll_down", "/scrollback goto +10");
+
+Irssi::signal_register({
+ 'gui mouse' => [qw/int int int int int int/],
+ });
+
+Irssi::signal_add_priority('gui mouse' => 'mouse_event', 101);
diff --git a/scripts/mouse_soliton.pl b/scripts/mouse_soliton.pl
new file mode 100644
index 0000000..91f86e0
--- /dev/null
+++ b/scripts/mouse_soliton.pl
@@ -0,0 +1,146 @@
+# based on irssi mouse patch by mirage: http://darksun.com.pt/mirage/irssi/
+# It should probably indeed be done in C, and go into irssi, or as a module,
+# but I translated it to perl just for the fun of it, and to prove it's possible maybe
+
+use strict;
+use Irssi qw(signal_emit settings_get_str active_win signal_stop settings_add_str settings_add_bool settings_get_bool signal_add signal_add_first);
+use Math::Trig;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '0.0.0';
+%IRSSI = (
+ authors => 'Wouter Coekaerts',
+ contact => 'wouter@coekaerts.be',
+ name => 'trigger',
+ description => 'experimental perl version of the irssi mouse patch',
+ license => 'GPLv2',
+ url => 'http://wouter.coekaerts.be/irssi/',
+ changed => '2005-11-21',
+);
+
+# minor changes by Soliton:
+# added mouse_enable and mouse_disable functions to make for example copy & pasting possible for a second after clicking with the left mouse button
+# also changed the mouse button for the gestures to the right button
+
+my $mouse_xterm_status = -1; # -1:off 0,1,2:filling mouse_xterm_combo
+my @mouse_xterm_combo; # 0:button 1:x 2:y
+my @mouse_xterm_previous; # previous contents of mouse_xterm_combo
+
+sub mouse_enable {
+ print STDERR "\e[?1000h"; # start tracking
+}
+
+sub mouse_disable {
+ print STDERR "\e[?1000l"; # stop tracking
+ Irssi::timeout_add_once(2000, 'mouse_enable', undef); # turn back on after 1 sec
+}
+
+# Handle mouse event (button press or release)
+sub mouse_event {
+ my ($b, $x, $y, $oldb, $oldx, $oldy) = @_;
+ my ($xd, $yd);
+ my ($distance, $angle);
+
+ #print "DEBUG: mouse_event $b $x $y";
+
+ # uhm, in the patch the scrollwheel didn't work for me, but this does:
+ if ($b == 64) {
+ cmd("mouse_scroll_up");
+ } elsif ($b == 65) {
+ cmd("mouse_scroll_down")
+ }
+
+ # proceed only if a button is being released
+ return if ($b != 3);
+
+ # if it was a mouse click of the left button (press and release in the same position)
+ if ($x == $oldx && $y == $oldy && $oldb == 0) {
+ #signal_emit("mouse click", $oldb, $x, $y);
+ #mouse_click($oldb, $x, $y);
+ mouse_disable();
+ return;
+ }
+
+ # otherwise, find mouse gestures on button
+ return if ($oldb != 2);
+ $xd = $x - $oldx;
+ $yd = -1 * ($y - $oldy);
+ $distance = sqrt($xd*$xd + $yd*$yd);
+ # ignore small gestures
+ if ($distance < 3) {
+ return;
+ }
+ $angle = asin($yd/$distance) * 180 / 3.14159265358979;
+ if ($angle < 20 && $angle > -20 && $xd > 0) {
+ if ($distance <= 40) {
+ cmd("mouse_gesture_right");
+ } else {
+ cmd("mouse_gesture_bigright");
+ }
+ } elsif ($angle < 20 && $angle > -20 && $xd < 0) {
+ if ($distance <= 40) {
+ cmd("mouse_gesture_left");
+ } else {
+ cmd("mouse_gesture_bigleft");
+ }
+ } elsif ($angle > 40) {
+ cmd("mouse_gesture_up");
+ } elsif ($angle < -40) {
+ cmd("mouse_gesture_down");
+ }
+}
+
+sub cmd
+{
+ my ($setting) = @_;
+ signal_emit("send command", settings_get_str($setting), active_win->{'active_server'}, active_win->{'active'});
+}
+
+
+signal_add_first("gui key pressed", sub {
+ my ($key) = @_;
+ if ($mouse_xterm_status != -1) {
+ if ($mouse_xterm_status == 0) {
+ @mouse_xterm_previous = @mouse_xterm_combo;
+ }
+ $mouse_xterm_combo[$mouse_xterm_status] = $key-32;
+ $mouse_xterm_status++;
+ if ($mouse_xterm_status == 3) {
+ $mouse_xterm_status = -1;
+ # match screen coordinates
+ $mouse_xterm_combo[1]--;
+ $mouse_xterm_combo[2]--;
+ # TODO signal_emit("mouse event", $mouse_xterm_combo[0], $mouse_xterm_combo[1], $mouse_xterm_combo[2], $mouse_xterm_previous[0], $mouse_xterm_previous[1], $mouse_xterm_previous[2]);
+ mouse_event($mouse_xterm_combo[0], $mouse_xterm_combo[1], $mouse_xterm_combo[2], $mouse_xterm_previous[0], $mouse_xterm_previous[1], $mouse_xterm_previous[2]);
+ }
+ signal_stop();
+ }
+});
+
+sub sig_command_script_unload {
+ my $script = shift;
+ if ($script =~ /(.*\/)?$IRSSI{'name'}(\.pl)? *$/) {
+ print STDERR "\e[?1000l"; # stop tracking
+ }
+}
+Irssi::signal_add_first('command script load', 'sig_command_script_unload');
+Irssi::signal_add_first('command script unload', 'sig_command_script_unload');
+
+if ($ENV{"TERM"} !~ /^rxvt|screen|xterm(-color)?$/) {
+ die "Your terminal doesn't seem to support this.";
+}
+
+print STDERR "\e[?1000h"; # start tracking
+
+Irssi::command("/^bind meta-[M /mouse_xterm"); # FIXME evil
+Irssi::command_bind("mouse_xterm", sub {$mouse_xterm_status = 0;});
+
+settings_add_str("lookandfeel", "mouse_gesture_up", "/window last");
+settings_add_str("lookandfeel", "mouse_gesture_down", "/window goto active");
+settings_add_str("lookandfeel", "mouse_gesture_left", "/window prev");
+settings_add_str("lookandfeel", "mouse_gesture_bigleft", "/eval window prev;window prev");
+settings_add_str("lookandfeel", "mouse_gesture_right", "/window next");
+settings_add_str("lookandfeel", "mouse_gesture_bigright", "/eval window next;window next");
+settings_add_str("lookandfeel", "mouse_scroll_up", "/scrollback goto -10");
+settings_add_str("lookandfeel", "mouse_scroll_down", "/scrollback goto +10");
diff --git a/scripts/nickcolor_expando.pl b/scripts/nickcolor_expando.pl
new file mode 100644
index 0000000..ef9b084
--- /dev/null
+++ b/scripts/nickcolor_expando.pl
@@ -0,0 +1,1048 @@
+use strict;
+use warnings;
+
+our $VERSION = '0.3.7'; # 6edfe656246780e
+our %IRSSI = (
+ authors => 'Nei',
+ name => 'nickcolor_expando',
+ description => 'colourise nicks',
+ license => 'GPL v2',
+ );
+
+# inspired by bc-bd's nm.pl and mrwright's nickcolor.pl
+
+# Usage
+# =====
+# after loading the script, add the colour expando to the format
+# (themes are not supported)
+#
+# /format pubmsg {pubmsgnick $2 {pubnick $nickcolor$0}}$1
+#
+# alternatively, use it together with nm2 script
+
+# Options
+# =======
+# /set neat_colors <list of colours>
+# * the list of colours for automatic colouring (you can edit it more
+# conveniently with /neatcolor colors)
+#
+# /set neat_ignorechars <regex>
+# * regular expression of characters to remove from nick before
+# calculating the hash function
+#
+# /set neat_color_reassign_time <time>
+# * if the user has not spoken for so long, the assigned colour is
+# forgotten and another colour may be picked next time the user
+# speaks
+#
+# /set neat_global_colors <ON|OFF>
+# * more strongly prefer one global colour per nickname regardless of
+# channel
+
+# Commands
+# ========
+# /neatcolor
+# * show the current colour distribution of nicks
+#
+# /neatcolor set [<network>/<#channel>] <nick> <colour>
+# * set a fixed colour for nick
+#
+# /neatcolor reset [<network>/<#channel>] <nick>
+# * remove a set colour of nick
+#
+# /neatcolor get [<network>/<#channel>] <nick>
+# * query the current or set colour of nick
+#
+# /neatcolor re [<network>/<#channel>] <nick>
+# * force change the colour of nick to a random other colour (to
+# manually resolve clashes)
+#
+# /neatcolor save
+# * save the colours to ~/.irssi/saved_nick_colors
+#
+# /neatcolor reset --all
+# * re-set all colours
+#
+# /neatcolor colors
+# * show currently configured colours, in colour
+#
+# /neatcolor colors add <list of colours>
+# /neatcolor colors remove <list of colours>
+# * add or remove these colours from the neat_colors setting
+
+
+sub cmd_help_neatcolor {
+ print CLIENTCRAP <<HELP
+%9Syntax:%9
+
+NEATCOLOR
+NEATCOLOR SET [<network>/<#channel>] <nick> <colour>
+NEATCOLOR RESET [<network>/<#channel>] <nick>
+NEATCOLOR GET [<network>/<#channel>] <nick>
+NEATCOLOR RE [<network>/<#channel>] <nick>
+NEATCOLOR SAVE
+NEATCOLOR RESET --all
+NEATCOLOR COLORS
+NEATCOLOR COLORS ADD <list of colours>
+NEATCOLOR COLORS REMOVE <list of colours>
+
+%9Parameters:%9
+
+ SET: set a fixed colour for nick
+ RESET: remove a set colour of nick
+ GET: query the current or set colour of nick
+ RE: force change the colour of nick to a random other
+ colour (to manually resolve clashes)
+ SAVE: save the colours to ~/.irssi/saved_nick_colors
+ RESET --all: re-set all colours
+ COLORS: show currently configured colours, in colour
+ COLORS ADD/REMOVE: add or remove these colours from the
+ neat_colors setting
+
+ If no parameters are given, the current colour distribution of
+ nicks is shown.
+
+%9Description:%9
+
+ Manages nick based colouring
+
+HELP
+}
+
+use Hash::Util qw(lock_keys);
+use Irssi;
+
+
+{ package Irssi::Nick }
+
+my @action_protos = qw(irc silc xmpp);
+my (%set_colour, %avoid_colour, %has_colour, %last_time, %netchan_hist);
+my ($expando, $ignore_re, $ignore_setting, $global_colours, $retain_colour_time, @colours, $exited, $session_load_time);
+
+# the numbers for the scoring system, highest colour value will be chosen
+my %scores = (
+ set => 200,
+ keep => 5,
+ global => 4,
+ hash => 3,
+
+ avoid => -20,
+ hist => -10,
+ used => -2,
+ );
+lock_keys(%scores);
+
+my $history_lines = 40;
+my $global_mode = 1; # start out with global nick colour
+
+my @colour_bags = (
+ [qw[20 30 40 50 04 66 0C 61 60 67 6L]], # RED
+ [qw[37 3D 36 4C 46 5C 56 6C 6J 47 5D 6K 6D 57 6E 5E 4E 4K 4J 5J 4D 5K 6R]], # ORANGE
+ [qw[3C 4I 5I 6O 6I 06 4O 5O 3U 0E 5U 6U 6V 6P 6Q 6W 5P 4P 4V 4W 5W 4Q 5Q 5R 6Y 6X]], # YELLOW
+ [qw[26 2D 2C 3I 3O 4U 5V 2J 3V 3P 3J 5X]], # YELLOW-GREEN
+ [qw[16 1C 2I 2U 2O 1I 1O 1V 1P 02 0A 1U 2V 4X]], # GREEN
+ [qw[1D 1J 1Q 1W 1X 2Y 2S 2R 3Y 3Z 3S 3R 2K 3K 4S 5Z 5Y 4R 3Q 2Q 2X 2W 3X 3W 2P 4Y]], # GREEN-TURQUOIS
+ [qw[17 1E 1L 1K 1R 1S 03 1M 1N 1T 0B 1Y 1Z 2Z 4Z]], # TURQUOIS
+ [qw[28 2E 18 1F 19 1G 1A 1B 1H 2N 2H 09 3H 3N 2T 3T 2M 2G 2A 2F 2L 3L 3F 4M 3M 3G 29 4T 5T]], # LIGHT-BLUE
+ [qw[11 12 23 25 24 13 14 01 15 2B 4N]], # DARK-BLUE
+ [qw[22 33 44 0D 45 5B 6A 5A 5H 3B 4H 3A 4G 39 4F 6S 6T 5L 5N]], # VIOLET
+ [qw[21 32 42 53 63 52 43 34 35 55 65 6B 4B 4A 48 5G 6H 5M 6M 6N]], # PINK
+ [qw[38 31 05 64 54 41 51 62 69 68 59 5F 6F 58 49 6G]], # ROSE
+ [qw[7A 00 10 7B 7C 7D 7E 7G 7F]], # DARK-GRAY
+ [qw[7H 7I 27 7K 7J 08 7L 3E 7O 7Q 7N 7M 7P]], # GRAY
+ [qw[7S 7T 7R 4L 7W 7U 7V 5S 07 7X 6Z 0F]], # LIGHT-GRAY
+ );
+my %colour_bags;
+{ my $idx = 0;
+ for my $bag (@colour_bags) {
+ @colour_bags{ @$bag } = ($idx)x@$bag;
+ }
+ continue {
+ ++$idx;
+ }
+}
+my @colour_list = map { @$_ } @colour_bags;
+my @bases = split //, 'kbgcrmywKBGCRMYW04261537';
+my %base_map = map { $bases[$_] => sprintf '%02X', ($_ % 0x10) } 0..$#bases;
+my %ext_to_base_map = map { (sprintf '%02X', $_) => $bases[$_] } 0..15;
+
+sub expando_neatcolour {
+ return $expando;
+}
+
+# one-at-a-time hash
+sub simple_hash {
+ use integer;
+ my $hash = 0x5065526c + length $_[0];
+ for my $ord (unpack 'U*', $_[0]) {
+ $hash += $ord;
+ $hash += $hash << 10;
+ $hash &= 0xffffffff;
+ $hash ^= $hash >> 6;
+ }
+ $hash += $hash << 3;
+ $hash &= 0xffffffff;
+ $hash ^= $hash >> 11;
+ $hash = $hash + ($hash << 15);
+ $hash &= 0xffffffff;
+}
+
+{ my %lut1;
+ my @z = (0 .. 9, 'A' .. 'Z');
+ for my $x (16..255) {
+ my $idx = $x - 16;
+ my $col = 1+int($idx / @z);
+ $lut1{ $col . @z[(($col > 6 ? 10 : 0) + $idx) % @z] } = $x;
+ }
+ for my $idx (0..15) {
+ $lut1{ (sprintf "%02X", $idx) } = ($idx&8) | ($idx&4)>>2 | ($idx&2) | ($idx&1)<<2;
+ }
+
+ sub debug_ansicolour {
+ my ($col, $bg) = @_;
+ return '' unless defined $col && exists $lut1{$col};
+ $bg = $bg ? 48 : 38;
+ "\e[$bg;5;$lut1{$col}m"
+ }
+}
+sub debug_colour {
+ my ($col, $bg) = @_;
+ defined $col ? (debug_ansicolour($col, $bg) . $col . "\e[0m") : '(none)'
+}
+sub debug_score {
+ my ($score) = @_;
+ if ($score == 0) {
+ return $score
+ }
+ my @scale = $score > 0 ? (qw(16 1C 1I 1U 2V 4X)) : (qw(20 30 40 60 67 6L));;
+ my $v = (log 1+ abs $score)*(log 20);
+ debug_ansicolour($scale[$v >= $#scale ? -1 : $v], 1) . $score . "\e[0m"
+}
+sub debug_reused {
+ my ($netchan, $nick, $col) = @_;
+ my $chc = simple_hash($netchan);
+ my $hashcolour = @colours ? $colours[ $chc % @colours ] : 0;
+}
+sub debug_scores {
+ my ($netchan, $nick, $col, $prios, $colours) = @_;
+ my $inprogress;
+ unless (ref $prios) {
+ $inprogress = $prios;
+ $prios = [ sort { $colours->{$b} <=> $colours->{$a} } grep { exists $colours->{$_} } @colour_list ];
+ }
+ my $chc = simple_hash($netchan);
+ my $hashcolour = @colours ? $colours[ $chc % @colours ] : 0;
+ unless ($inprogress) {
+ }
+ else {
+ }
+ for my $i (0..$#$prios) {
+ }
+}
+
+sub colourise_nt {
+ my ($netchan, $nick, $weak) = @_;
+ my $time = time;
+
+ my $g_or_n = $global_colours ? '' : $netchan;
+
+ my $old_colour = $has_colour{$g_or_n}{$nick} // $has_colour{$netchan}{$nick};
+ my $last_time = $last_time{$g_or_n}{$nick} // $last_time{$netchan}{$nick};
+
+ my $keep_score = $weak ? $scores{keep} + $scores{set} : $scores{keep};
+
+ unless ($weak) {
+ $last_time{$netchan}{$nick}
+ = $last_time{''}{$nick} = $time;
+ }
+ else {
+ $last_time{$netchan}{$nick} ||= 0;
+ }
+
+ my $colour;
+ if (defined $old_colour && ($weak || (defined $last_time
+ && ($last_time + $retain_colour_time > $time
+ || ($last_time > 0 && grep { $_->[0] eq $nick } @{ $netchan_hist{$netchan} // [] }))))) {
+ $colour = $old_colour;
+ }
+ else {
+ # search for a suitable colour
+ my %colours = map { $_ => 0 } @colours;
+ my $hashnick = $nick;
+ $hashnick =~ s/$ignore_re//g if (defined $ignore_re && length $ignore_re);
+ my $hash = simple_hash($global_mode ? "/$hashnick" : "$netchan/$hashnick");
+
+ if (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$nick}) {
+ $colours{ $set_colour{$netchan}{$nick} } += $scores{set};
+ }
+ elsif (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$hashnick}) {
+ $colours{ $set_colour{$netchan}{$hashnick} } += $scores{set};
+ }
+ elsif (exists $set_colour{''} && exists $set_colour{''}{$nick}) {
+ $colours{ $set_colour{''}{$nick} } += $scores{set};
+ }
+ elsif (exists $set_colour{''} && exists $set_colour{''}{$hashnick}) {
+ $colours{ $set_colour{''}{$hashnick} } += $scores{set};
+ }
+
+ if (exists $avoid_colour{$netchan} && exists $avoid_colour{$netchan}{$nick}) {
+ for (@{ $avoid_colour{$netchan}{$nick} }) {
+ $colours{ $_ } += $scores{avoid} if exists $colours{ $_ };
+ }
+ }
+ elsif (exists $avoid_colour{$g_or_n} && exists $avoid_colour{$g_or_n}{$nick}) {
+ for (@{ $avoid_colour{$g_or_n}{$nick} }) {
+ $colours{ $_ } += $scores{avoid} if exists $colours{ $_ };
+ }
+ }
+
+ if (defined $old_colour) {
+ $colours{$old_colour} += $keep_score
+ if exists $colours{$old_colour};
+ }
+ elsif (exists $has_colour{''}{$nick}) {
+ $colours{ $has_colour{''}{$nick} } += $scores{global}
+ if exists $colours{ $has_colour{''}{$nick} };
+ }
+
+ if (@colours) {
+ my $hashcolour = $colours[ $hash % @colours ];
+ if (!defined $old_colour || $hashcolour ne $old_colour) {
+ $colours{ $hashcolour } += $scores{hash};
+ }
+ }
+
+ { my @netchans = $global_mode ? keys %has_colour : $netchan;
+ my $total;
+ my %colour_pens;
+ for my $gnc (@netchans) {
+ for my $onick (keys %{ $has_colour{$gnc} }) {
+ next if $gnc ne $netchan && exists $has_colour{$netchan}{$onick};
+ next unless exists $last_time{$gnc}{$onick};
+ if ($last_time{$gnc}{$onick} + $retain_colour_time > $time # XXX
+ || ($last_time{$gnc}{$onick} == 0 && $session_load_time + $retain_colour_time > $time)) {
+ if (exists $colours{ $has_colour{$gnc}{$onick} }) {
+ $colour_pens{ $has_colour{$gnc}{$onick} } += $scores{used};
+ ++$total;
+ }
+ }
+ }
+ }
+ for (keys %colour_pens) {
+ $colours{ $_ } += $colour_pens{ $_ } / $total * @colours
+ if @colours;
+ }
+ }
+
+ { my $fac = 1;
+ for my $gnetchan ($netchan, '') {
+ my $idx = exp(-log($history_lines)/$scores{hist});
+ for my $hent (reverse @{ $netchan_hist{$gnetchan} // [] }) {
+ next unless defined $hent->[1];
+ if ($hent->[0] ne $nick) {
+ my $pen = 1;
+ $pen *= 3 if length $nick == length $hent->[0];
+ $pen *= 2 if (substr $nick, 0, 1) eq (substr $hent->[0], 0, 1)
+ || 1 == abs +(length $nick) - (length $hent->[0]);
+ $colours{ $hent->[1] } -= log($pen*$history_lines)/log($idx) / $fac
+ if exists $colours{ $hent->[1] };
+ }
+ ++$idx;
+ last if $idx > $history_lines;
+ }
+ ++$fac;
+ }
+ }
+
+ { my %bag_pens;
+ for my $co (keys %colours) {
+ $bag_pens{ $colour_bags{$co} } -= $colours{$co}/2 if $colours{$co} < 0;
+ }
+ for my $bag (keys %bag_pens) {
+ for my $co (@{ $colour_bags[$bag] }) {
+ $colours{$co} -= $bag_pens{$bag} / @colours
+ if @colours && exists $colours{$co};
+ }
+ }
+ }
+
+ my @prio_colours = sort { $colours{$b} <=> $colours{$a} } grep { exists $colours{$_} } @colour_list;
+ my $stop_at = 0;
+ while ($stop_at < $#prio_colours
+ && $colours{ $prio_colours[$stop_at] } <= $colours{ $prio_colours[$stop_at + 1] }) {
+ ++$stop_at;
+ }
+ $colour = $prio_colours[ $hash % ($stop_at + 1) ]
+ if @prio_colours;
+
+ }
+
+ unless ($weak) {
+ expire_hist($netchan, '');
+
+ my $ent = [$nick, $colour];
+ push @{ $netchan_hist{$netchan} }, $ent;
+ push @{ $netchan_hist{''} }, $ent;
+ }
+
+ defined $colour ? ($has_colour{$g_or_n}{$nick} = $has_colour{$netchan}{$nick} = $colour) : $colour
+}
+
+sub expire_hist {
+ for my $ch (@_) {
+ if ($netchan_hist{$ch}
+ && @{$netchan_hist{$ch}} > 2 * $history_lines) {
+ splice @{$netchan_hist{$ch}}, 0, $history_lines;
+ }
+ }
+}
+
+sub msg_line_tag {
+ my ($srv, $msg, $nick, $addr, $targ) = @_;
+ my $obj = $srv->channel_find($targ);
+ clear_ref(), return unless $obj;
+ my $nickobj = $obj->nick_find($nick);
+ $nick = $nickobj->{nick} if $nickobj;
+ my $colour = colourise_nt($srv->{tag}.'/'.$obj->{name}, $nick);
+ $expando = $colour ? format_expand('%X'.$colour) : '';
+}
+
+sub msg_line_tag_xmppaction {
+ clear_ref(), return unless @_;
+ my ($srv, $msg, $nick, $targ) = @_;
+ msg_line_tag($srv, $msg, $nick, undef, $targ);
+}
+
+sub msg_line_clear {
+ clear_ref();
+}
+
+sub prnt_clear_public {
+ my ($dest) = @_;
+ clear_ref() if $dest->{level} & MSGLEVEL_PUBLIC;
+}
+
+sub clear_ref {
+ $expando = '';
+}
+
+sub nicklist_changed {
+ my ($chanobj, $nickobj, $old_nick) = @_;
+
+ my $netchan = $chanobj->{server}{tag}.'/'.$chanobj->{name};
+ my $nickstr = $nickobj->{nick};
+
+ if (!exists $has_colour{''}{$nickstr} && exists $has_colour{''}{$old_nick}) {
+ $has_colour{''}{$nickstr} = delete $has_colour{''}{$old_nick};
+ }
+ if (exists $has_colour{$netchan}{$old_nick}) {
+ $has_colour{$netchan}{$nickstr} = delete $has_colour{$netchan}{$old_nick};
+ }
+
+ $last_time{$netchan}{$nickstr}
+ = $last_time{''}{$nickstr} = time;
+
+ for my $old_ent (@{ $netchan_hist{$netchan} }) {
+ $old_ent->[0] = $nickstr if $old_ent->[0] eq $old_nick;
+ }
+
+}
+
+{
+ my %format2control = (
+ 'F' => "\cDa", '_' => "\cDc", '|' => "\cDe", '#' => "\cDi", "n" => "\cDg", "N" => "\cDg",
+ 'U' => "\c_", '8' => "\cV", 'I' => "\cDf",
+ );
+ my %bg_base = (
+ '0' => '0', '4' => '1', '2' => '2', '6' => '3', '1' => '4', '5' => '5', '3' => '6', '7' => '7',
+ 'x08' => '8', 'x09' => '9', 'x0a' => ':', 'x0b' => ';', 'x0c' => '<', 'x0d' => '=', 'x0e' => '>', 'x0f' => '?',
+ );
+ my %fg_base = (
+ 'k' => '0', 'b' => '1', 'g' => '2', 'c' => '3', 'r' => '4', 'm' => '5', 'p' => '5', 'y' => '6', 'w' => '7',
+ 'K' => '8', 'B' => '9', 'G' => ':', 'C' => ';', 'R' => '<', 'M' => '=', 'P' => '=', 'Y' => '>', 'W' => '?',
+ );
+ my @ext_colour_off = (
+ '.', '-', ',',
+ '+', "'", '&',
+ );
+ sub format_expand {
+ my $copy = $_[0];
+ $copy =~ s{%(Z.{6}|z.{6}|X..|x..|.)}{
+ my $c = $1;
+ if (exists $format2control{$c}) {
+ $format2control{$c}
+ }
+ elsif (exists $bg_base{$c}) {
+ "\cD/$bg_base{$c}"
+ }
+ elsif (exists $fg_base{$c}) {
+ "\cD$fg_base{$c}/"
+ }
+ elsif ($c =~ /^[{}%]$/) {
+ $c
+ }
+ elsif ($c =~ /^(z|Z)([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})$/) {
+ my $bg = $1 eq 'z';
+ my (@rgb) = map { hex $_ } $2, $3, $4;
+ my $x = $bg ? 0x1 : 0;
+ my $out = "\cD" . (chr -13 + ord '0');
+ for (my $i = 0; $i < 3; ++$i) {
+ if ($rgb[$i] > 0x20) {
+ $out .= chr $rgb[$i];
+ }
+ else {
+ $x |= 0x10 << $i; $out .= chr 0x20 + $rgb[$i];
+ }
+ }
+ $out .= chr 0x20 + $x;
+ $out
+ }
+ elsif ($c =~ /^(x)(?:0([[:xdigit:]])|([1-6])(?:([0-9])|([a-z]))|7([a-x]))$/i) {
+ my $bg = $1 eq 'x';
+ my $col = defined $2 ? hex $2
+ : defined $6 ? 232 + (ord lc $6) - (ord 'a')
+ : 16 + 36 * ($3 - 1) + (defined $4 ? $4 : 10 + (ord lc $5) - (ord 'a'));
+ if ($col < 0x10) {
+ my $chr = chr $col + ord '0';
+ "\cD" . ($bg ? "/$chr" : "$chr/")
+ }
+ else {
+ "\cD" . $ext_colour_off[($col - 0x10) / 0x50 + $bg * 3] . chr (($col - 0x10) % 0x50 - 1 + ord '0')
+ }
+ }
+ else {
+ "%$c"
+ }
+ }ge;
+ $copy
+ }
+}
+
+sub save_colours {
+ open my $fid, '>', Irssi::get_irssi_dir() . '/saved_nick_colors'
+ or do {
+ Irssi::print("Error saving nick colours: $!", MSGLEVEL_CLIENTERROR)
+ unless $exited;
+ return;
+ };
+
+ local $\ = "\n";
+ if (%set_colour) {
+ print $fid '[set]';
+ for my $netch (sort keys %set_colour) {
+ for my $nick (sort keys %{ $set_colour{$netch} }) {
+ print $fid "$netch/$nick:".$set_colour{$netch}{$nick};
+ }
+ }
+ print $fid '';
+ }
+ my $time = time;
+ print $fid '[session]';
+ my %session_colour;
+ for my $netch (sort keys %last_time) {
+ for my $nick (sort keys %{ $last_time{$netch} }) {
+ if (exists $has_colour{$netch} && exists $has_colour{$netch}{$nick}
+ && ($last_time{$netch}{$nick} + $retain_colour_time > $time
+ || ($last_time{$netch}{$nick} == 0 && $session_load_time + $retain_colour_time > $time)
+ || grep { $_->[0] eq $nick } @{ $netchan_hist{$netch} // [] })) {
+ $session_colour{$netch}{$nick} = $has_colour{$netch}{$nick};
+ if (exists $session_colour{''}{$nick}) {
+ if (defined $session_colour{''}{$nick}
+ && $session_colour{''}{$nick} ne $session_colour{$netch}{$nick}) {
+ $session_colour{''}{$nick} = undef;
+ }
+ }
+ else {
+ $session_colour{''}{$nick} = $session_colour{$netch}{$nick};
+ }
+ }
+ }
+ }
+ for my $nick (sort keys %{ $session_colour{''} }) {
+ if (defined $session_colour{''}{$nick}) {
+ print $fid "/$nick:".$session_colour{''}{$nick};
+ }
+ else {
+ for my $netch (sort keys %session_colour) {
+ print $fid "$netch/$nick:".$session_colour{$netch}{$nick}
+ if exists $session_colour{$netch}{$nick} && defined $session_colour{$netch}{$nick};
+ }
+ }
+ }
+
+ close $fid;
+}
+
+sub load_colours {
+ $session_load_time = time;
+
+ open my $fid, '<', Irssi::get_irssi_dir() . '/saved_nick_colors'
+ or return;
+ my $mode;
+ while (my $line = <$fid>) {
+ chomp $line;
+ if ($line =~ /^\[(.*)\]$/) {
+ $mode = $1;
+ next;
+ }
+
+ my $colon = rindex $line, ':';
+ next if $colon < 0;
+ my $slash = rindex $line, '/', $colon;
+ next if $slash < 0;
+ my $col = substr $line, $colon +1;
+ next unless length $col;
+ my $netch = substr $line, 0, $slash;
+ my $nick = substr $line, $slash +1, $colon-$slash -1;
+ if ($mode eq 'set') {
+ $set_colour{$netch}{$nick} = $col;
+ }
+ elsif ($mode eq 'session') {
+ $has_colour{$netch}{$nick} = $col;
+ $last_time{$netch}{$nick} = 0;
+ }
+ }
+ close $fid;
+}
+
+sub UNLOAD {
+ return if $exited;
+ exit_save();
+}
+
+sub exit_save {
+ $exited = 1;
+ save_colours() if Irssi::settings_get_bool('settings_autosave');
+}
+
+sub get_nick_color2 {
+ my ($tag, $chan, $nick, $format) = @_;
+ my $col = colourise_nt($tag.'/'.$chan, $nick, 1);
+ $col ? $format ? format_expand('%X'.$col) : $col : ''
+}
+
+sub _cmd_colours_check {
+ my ($add, $data) = @_;
+ my @to_check = grep { defined && length } map {
+ length == 1 ? $base_map{$_}
+ : length == 3 ? substr $_, 1
+ : $_ } map { /(?|x(..)|([0-7].)|(.))/gi }
+ split ' ', $data;
+ my @valid;
+ my %scolours = map { $_ => undef } @colours;
+ for my $c (@to_check) {
+ if ((grep { $_ eq $c } @colour_list)) {
+ if ($add) { next if exists $scolours{$c} }
+ else { next if !exists $scolours{$c} }
+ push @valid, $c;
+ if ($add) { $scolours{$c} = undef; }
+ else { delete $scolours{$c}; }
+ }
+ }
+ (\@valid, \%scolours)
+}
+
+sub _cmd_colours_set {
+ my $scolours = shift;
+ Irssi::settings_set_str('neat_colors', join '', map { $ext_to_base_map{$_} // "X$_" } grep { exists $scolours->{$_} } @colour_list);
+}
+
+sub _cmd_colours_list {
+ map { "%X$_".($ext_to_base_map{$_} // "X$_").'%n' } @{+shift}
+}
+
+sub cmd_neatcolor_colors_add {
+ my ($data, $server, $witem) = @_;
+ my ($added, $scolours) = _cmd_colours_check(1, $data);
+ if (@$added) {
+ _cmd_colours_set($scolours);
+ Irssi::print("%_nce2%_: added @{[ _cmd_colours_list($added) ]} to neat_colors", MSGLEVEL_CLIENTCRAP);
+ setup_changed();
+ }
+ else {
+ Irssi::print("%_nce2%_: nothing added", MSGLEVEL_CLIENTCRAP);
+ }
+}
+sub cmd_neatcolor_colors_remove {
+ my ($data, $server, $witem) = @_;
+ my ($removed, $scolours) = _cmd_colours_check(0, $data);
+ if (@$removed) {
+ _cmd_colours_set($scolours);
+ Irssi::print("%_nce2%_: removed @{[ _cmd_colours_list($removed) ]} from neat_colors", MSGLEVEL_CLIENTCRAP);
+ setup_changed();
+ }
+ else {
+ Irssi::print("%_nce2%_: nothing removed", MSGLEVEL_CLIENTCRAP);
+ }
+}
+
+sub cmd_neatcolor_colors {
+ my ($data, $server, $witem) = @_;
+ $data =~ s/\s+$//;
+ unless (length $data) {
+ Irssi::print("%_nce2%_: current colours: @{[ @colours ? _cmd_colours_list(\@colours) : '(none)' ]}");
+ }
+ Irssi::command_runsub('neatcolor colors', $data, $server, $witem);
+}
+
+sub cmd_neatcolor {
+ my ($data, $server, $witem) = @_;
+ $data =~ s/\s+$//;
+ unless (length $data) {
+ $witem ||= Irssi::active_win;
+ my $time = time;
+ my %distribution = map { $_ => 0 } @colours;
+ for my $netch (keys %has_colour) {
+ next unless length $netch;
+ for my $nick (keys %{ $has_colour{$netch} }) {
+ if (exists $last_time{$netch}{$nick}
+ && ($last_time{$netch}{$nick} + $retain_colour_time > $time
+ || grep { $_->[0] eq $nick } @{ $netchan_hist{$netch} // [] })) {
+ $distribution{ $has_colour{$netch}{$nick} }++
+ }
+ }
+ }
+ $witem->print('%_nce2%_ Colour distribution: '.
+ (join ', ',
+ map { "%X$_$_:$distribution{$_}" }
+ sort { $distribution{$b} <=> $distribution{$a} }
+ grep { exists $distribution{$_} } @colour_list), MSGLEVEL_CLIENTCRAP);
+ }
+ Irssi::command_runsub('neatcolor', $data, $server, $witem);
+}
+
+sub _cmd_check_netchan_arg {
+ my ($cmd, $netchan, $nick) = @_;
+ my %global = map { $_ => undef } qw(set get reset);
+ unless (length $netchan) {
+ Irssi::print('%_nce2%_: no network/channel argument given for neatcolor '.$cmd
+ .(exists $global{$cmd} ? ', use / to '.$cmd.' global colours' : ''),
+ MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ elsif (-1 == index $netchan, '/') {
+ Irssi::print('%_nce2%_: missing network/ in argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ elsif ($netchan =~ m\^[^/]+/$\) {
+ Irssi::print('%_nce2%_: missing /channel in argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR);
+ return;
+ }
+
+ unless (length $nick) {
+ Irssi::print('%_nce2%_: no nick argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ elsif (-1 != index $nick, '/') {
+ Irssi::print('%_nce2%_: / not supported in nicks in argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR);
+ return;
+ }
+
+ return 1;
+}
+
+sub _cmd_check_colour {
+ my ($cmd, $colour) = @_;
+ $colour = substr $colour, 1 if length $colour == 3;
+ $colour = $base_map{$colour} if length $colour == 1;
+ unless (length $colour && grep { $_ eq $colour } @colour_list) {
+ Irssi::print('%_nce2%_: no colour or invalid colour argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ return $colour;
+}
+
+sub cmd_neatcolor_set {
+ my ($data, $server, $witem) = @_;
+ my @args = split ' ', $data;
+ if (@args < 2) {
+ Irssi::print('%_nce2%_: not enough arguments for neatcolor set', MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ my $netchan;
+ if (ref $witem) {
+ $netchan = $witem->{server}{tag}.'/'.$witem->{name};
+ }
+ my $nick;
+ my $colour;
+ if (@args < 3) {
+ ($nick, $colour) = @args;
+ }
+ else {
+ ($netchan, $nick, $colour) = @args;
+ }
+
+ return unless _cmd_check_netchan_arg('set', $netchan, $nick);
+ return unless defined ($colour = _cmd_check_colour('set', $colour));
+
+ $set_colour{$netchan eq '/' ? '' : $netchan}{$nick} = $colour;
+ for my $netch ($netchan eq '/' ? keys %has_colour
+ : $global_colours ? ('', $netchan)
+ : $netchan) {
+ delete $has_colour{$netch}{$nick} unless
+ exists $has_colour{$netch}{$nick} && $has_colour{$netch}{$nick} eq $colour;
+ }
+ Irssi::print("%_nce2%_: %X$colour$nick%n colour set to: %X$colour$colour%n ".($netchan eq '/' ? 'globally' : "in $netchan"), MSGLEVEL_CLIENTCRAP);
+}
+sub cmd_neatcolor_get {
+ my ($data, $server, $witem) = @_;
+ my @args = split ' ', $data;
+ if (@args < 1) {
+ Irssi::print('%_nce2%_: not enough arguments for neatcolor get', MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ my $netchan;
+ if (ref $witem) {
+ $netchan = $witem->{server}{tag}.'/'.$witem->{name};
+ }
+ my $nick;
+ if (@args < 2) {
+ $nick = $args[0];
+ }
+ else {
+ ($netchan, $nick) = @args;
+ }
+
+ return unless _cmd_check_netchan_arg('get', $netchan, $nick);
+
+ if ($netchan ne '/') {
+ unless (exists $has_colour{$netchan} && exists $has_colour{$netchan}{$nick}) {
+ Irssi::print("%_nce2%_: $nick is not coloured (yet) in $netchan", MSGLEVEL_CLIENTCRAP);
+ }
+ else {
+ my $colour = $has_colour{$netchan}{$nick};
+ Irssi::print("%_nce2%_: %X$colour$nick%n has colour: %X$colour$colour%n in $netchan", MSGLEVEL_CLIENTCRAP);
+ }
+ }
+ my $hashnick = $nick;
+ $hashnick =~ s/$ignore_re//g if (defined $ignore_re && length $ignore_re);
+ if (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$nick}) {
+ my $colour = $set_colour{$netchan}{$nick};
+ Irssi::print("%_nce2%_: set colour for %X$colour$nick%n in $netchan: %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP);
+ }
+ elsif (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$hashnick}) {
+ my $colour = $set_colour{$netchan}{$hashnick};
+ Irssi::print("%_nce2%_: set colour for %X$colour$hashnick%n in $netchan: %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP);
+ }
+ elsif (exists $set_colour{''} && exists $set_colour{''}{$nick}) {
+ my $colour = $set_colour{''}{$nick};
+ Irssi::print("%_nce2%_: set colour for %X$colour$nick%n (global): %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP);
+ }
+ elsif (exists $set_colour{''} && exists $set_colour{''}{$hashnick}) {
+ my $colour = $set_colour{''}{$hashnick};
+ Irssi::print("%_nce2%_: set colour for %X$colour$hashnick%n (global): %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP);
+ }
+ elsif ($netchan eq '/') {
+ Irssi::print("%_nce2%_: no global colour set for $nick", MSGLEVEL_CLIENTCRAP);
+ }
+}
+sub cmd_neatcolor_reset {
+ my ($data, $server, $witem) = @_;
+ my @args = split ' ', $data;
+ if (@args < 1) {
+ Irssi::print('%_nce2%_: not enough arguments for neatcolor reset', MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ my $netchan;
+ if (ref $witem) {
+ $netchan = $witem->{server}{tag}.'/'.$witem->{name};
+ }
+ my $nick;
+ if (@args == 1 && $args[0] eq '--all') {
+ %set_colour = %avoid_colour = %has_colour = ();
+ Irssi::print("%_nce2%_: re-set all colouring");
+ return;
+ }
+ if (@args < 2) {
+ $nick = $args[0];
+ }
+ else {
+ ($netchan, $nick) = @args;
+ }
+
+ return unless _cmd_check_netchan_arg('reset', $netchan, $nick);
+
+ $netchan = '' if $netchan eq '/';
+ unless (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$nick}) {
+ Irssi::print("%_nce2%_: $nick has no colour set ". (length $netchan ? "in $netchan" : "globally"), MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ my $colour = delete $set_colour{$netchan}{$nick};
+ for my $netch ($netchan eq '' ? keys %has_colour
+ : $global_colours ? ('', $netchan)
+ : $netchan) {
+ delete $has_colour{$netch}{$nick} if exists $has_colour{$netch} && exists $has_colour{$netch}{$nick}
+ && $has_colour{$netch}{$nick} eq $colour;
+ }
+ Irssi::print("%_nce2%_: ".($netchan eq '' ? 'global ' : '')."colouring re-set for $nick".($netchan eq '' ? '' : " in $netchan"), MSGLEVEL_CLIENTERROR);
+}
+sub cmd_neatcolor_re {
+ my ($data, $server, $witem) = @_;
+ my @args = split ' ', $data;
+ if (@args < 1) {
+ Irssi::print('%_nce2%_: not enough arguments for neatcolor re', MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ my $netchan;
+ if (ref $witem) {
+ $netchan = $witem->{server}{tag}.'/'.$witem->{name};
+ }
+ my $nick;
+ if (@args < 2) {
+ $nick = $args[0];
+ }
+ else {
+ ($netchan, $nick) = @args;
+ }
+
+ return unless _cmd_check_netchan_arg('re', $netchan, $nick);
+
+ unless (exists $has_colour{$netchan} && exists $has_colour{$netchan}{$nick}) {
+ Irssi::print("%_nce2%_: could not find $nick in $netchan", MSGLEVEL_CLIENTERROR);
+ return;
+ }
+ my $colour = delete $has_colour{$netchan}{$nick};
+ if (grep { $colour eq $_ } @{ $avoid_colour{$netchan}{$nick} || [] }) {
+ $avoid_colour{$netchan}{$nick} = [ $colour ]
+ }
+ else {
+ push @{ $avoid_colour{$netchan}{$nick} }, $colour;
+ }
+ if ($global_colours) {
+ delete $has_colour{''}{$nick} if defined $colour;
+
+ if (grep { $colour eq $_ } @{ $avoid_colour{''}{$nick} || [] }) {
+ $avoid_colour{''}{$nick} = [ $colour ]
+ }
+ else {
+ push @{ $avoid_colour{''}{$nick} }, $colour;
+ }
+ }
+ Irssi::print("%_nce2%_: re-colouring $nick in $netchan", MSGLEVEL_CLIENTERROR);
+}
+sub cmd_neatcolor_save {
+ Irssi::print("%_nce2%_: saving colours to file", MSGLEVEL_CLIENTCRAP);
+ save_colours();
+}
+
+sub setup_changed {
+ $global_colours = Irssi::settings_get_bool('neat_global_colors');
+ $retain_colour_time = int( abs( Irssi::settings_get_time('neat_color_reassign_time') ) / 1000 );
+ my $old_ignore = $ignore_setting // '';
+ $ignore_setting = Irssi::settings_get_str('neat_ignorechars');
+ if ($old_ignore ne $ignore_setting) {
+ local $@;
+ eval { $ignore_re = qr/$ignore_setting/ };
+ if ($@) {
+ $@ =~ /^(.*)/;
+ print '%_neat_ignorechars%_ did not compile: '.$1;
+ }
+ }
+ my $old_colours = "@colours";
+ my %scolours = map { ($base_map{$_} // $_) => undef } Irssi::settings_get_str('neat_colors') =~ /(?|x(..)|(.))/ig;
+ @colours = grep { exists $scolours{$_} } @colour_list;
+
+ if ($old_colours ne "@colours") {
+ my $time = time;
+ for my $netch (sort keys %last_time) {
+ for my $nick (sort keys %{ $last_time{$netch} }) {
+ if (exists $has_colour{$netch} && exists $has_colour{$netch}{$nick}) {
+ if ($last_time{$netch}{$nick} + $retain_colour_time > $time
+ || ($last_time{$netch}{$nick} == 0 && $session_load_time + $retain_colour_time > $time)) {
+ $last_time{$netch}{$nick} = 0;
+ }
+ else {
+ delete $last_time{$netch}{$nick};
+ }
+ }
+ }
+ $session_load_time = $time;
+ }
+ }
+}
+
+sub internals {
+ +{
+ set => \%set_colour,
+ avoid => \%avoid_colour,
+ has => \%has_colour,
+ time => \%last_time,
+ hist => \%netchan_hist,
+ colours => \@colours
+ }
+}
+
+sub init_nickcolour {
+ setup_changed();
+ load_colours();
+}
+
+Irssi::settings_add_str('misc', 'neat_colors', 'rRgGybBmMcCX42X3AX5EX4NX3HX3CX32');
+Irssi::settings_add_str('misc', 'neat_ignorechars', '');
+Irssi::settings_add_time('misc', 'neat_color_reassign_time', '30min');
+Irssi::settings_add_bool('misc', 'neat_global_colors', 0);
+init_nickcolour();
+
+Irssi::expando_create('nickcolor', \&expando_neatcolour, {
+ 'message public' => 'none',
+ 'message own_public' => 'none',
+ (map { ("message $_ action" => 'none',
+ "message $_ own_action" => 'none')
+ } @action_protos),
+ });
+
+Irssi::signal_add({
+ 'message public' => 'msg_line_tag',
+ 'message own_public' => 'msg_line_clear',
+ (map { ("message $_ action" => 'msg_line_tag',
+ "message $_ own_action" => 'msg_line_clear')
+ } qw(irc silc)),
+ "message xmpp action" => 'msg_line_tag_xmppaction',
+ "message xmpp own_action" => 'msg_line_clear',
+ 'print text' => 'prnt_clear_public',
+ 'nicklist changed' => 'nicklist_changed',
+ 'gui exit' => 'exit_save',
+});
+Irssi::command_bind({
+ 'help' => sub { &cmd_help_neatcolor if $_[0] =~ /^neatcolor\s*$/i;},
+ 'neatcolor' => 'cmd_neatcolor',
+ 'neatcolor save' => 'cmd_neatcolor_save',
+ 'neatcolor set' => 'cmd_neatcolor_set',
+ 'neatcolor get' => 'cmd_neatcolor_get',
+ 'neatcolor reset' => 'cmd_neatcolor_reset',
+ 'neatcolor re' => 'cmd_neatcolor_re',
+ 'neatcolor colors' => 'cmd_neatcolor_colors',
+ 'neatcolor colors add' => 'cmd_neatcolor_colors_add',
+ 'neatcolor colors remove' => 'cmd_neatcolor_colors_remove',
+ });
+
+Irssi::signal_add_last('setup changed' => 'setup_changed');
+
+
+# Changelog
+# =========
+# 0.3.7
+# - fix crash if xmpp action signal is not registered (just ignore it)
+# 0.3.6
+# - also look up ignorechars in set colours
+# 0.3.5
+# - bug fix release
+# 0.3.4
+# - re/set/reset-colouring was affected by the global colour
+# - set colour score too weak
+# 0.3.3
+# - fix error with get / reported by Meicceli
+# - now possible to reset global colour
+# - check for invalid colours
+# 0.3.2
+# - add global colour option
+# - respect save settings setting
+# - add action handling
+# 0.3.1
+# - regression: reset colours after removing colour
+# 0.3.0
+# - save some more colours
+# 0.2.9
+# - fix incorrect calculation of used colours
+# - add some sanity checks to set/get command
+# - avoid random colour changes
diff --git a/scripts/nickcolor_gay.pl b/scripts/nickcolor_gay.pl
new file mode 100644
index 0000000..61f84d8
--- /dev/null
+++ b/scripts/nickcolor_gay.pl
@@ -0,0 +1,83 @@
+use strict;
+use warnings;
+
+our $VERSION = '0.1'; # fc5429f1bbac061
+our %IRSSI = (
+ name => 'nickcolor_gay',
+ description => 'colourise nicks',
+ license => 'ISC',
+ );
+
+use Hash::Util qw(lock_keys);
+use Irssi;
+
+
+{ package Irssi::Nick }
+
+my @action_protos = qw(irc silc xmpp);
+my $lastnick;
+
+sub msg_line_tag {
+ my ($srv, $msg, $nick, $addr, $targ) = @_;
+ my $obj = $srv->channel_find($targ);
+ clear_ref(), return unless $obj;
+ my $nickobj = $obj->nick_find($nick);
+ $lastnick = $nickobj ? $nickobj->{nick} : undef;
+}
+
+sub msg_line_tag_xmppaction {
+ clear_ref(), return unless @_;
+ my ($srv, $msg, $nick, $targ) = @_;
+ msg_line_tag($srv, $msg, $nick, undef, $targ);
+}
+
+sub msg_line_clear {
+ clear_ref();
+}
+
+{my %m; my $i = 16; for my $l (
+qw(E T A O I N S H R D L C U M W F G Y P B V K J X Q Z),
+qw(0 1 2 3 4 5 6 7 8 9),
+qw(e t a o i n s h r d l c u m w f g y p b v k j x q z),
+qw(_ - [ ] \\ ` ^ { } ~),
+) {
+ $m{$l}=$i++;
+}
+
+sub rainbow {
+ my $nick = shift;
+ $nick =~ s/(.)/exists $m{$1} ? sprintf "\cC%02d%s", $m{$1}, $1 : $1/ge;
+ $nick
+}
+}
+
+sub prnt_clear_public {
+ return unless defined $lastnick;
+ my ($dest, $txt) = @_;
+ if ($dest->{level} & MSGLEVEL_PUBLIC) {
+ my @nick_reg;
+ unshift @nick_reg, quotemeta substr $lastnick, 0, $_ for 1 .. length $lastnick;
+ for my $nick_reg (@nick_reg) {
+ if ($txt =~ s/($nick_reg)/rainbow($1)/e) {
+ Irssi::signal_continue($dest, $txt, $_[2]);
+ last;
+ }
+ }
+ clear_ref();
+ }
+}
+
+sub clear_ref {
+ $lastnick = undef;
+}
+
+Irssi::signal_add({
+ 'message public' => 'msg_line_tag',
+ 'message own_public' => 'msg_line_clear',
+ (map { ("message $_ action" => 'msg_line_tag',
+ "message $_ own_action" => 'msg_line_clear')
+ } qw(irc silc)),
+ "message xmpp action" => 'msg_line_tag_xmppaction',
+ "message xmpp own_action" => 'msg_line_clear',
+ 'print text' => 'prnt_clear_public',
+});
diff --git a/scripts/nm2.pl b/scripts/nm2.pl
new file mode 100644
index 0000000..08ec53d
--- /dev/null
+++ b/scripts/nm2.pl
@@ -0,0 +1,560 @@
+use Irssi;
+use strict;
+use v5.14;
+use List::Util qw(min max);
+use Hash::Util qw(lock_keys);
+
+our $VERSION = '2.0-dev'; # cb10e88bcd58d0c
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'nm2',
+ description => 'right aligned nicks depending on longest nick',
+ license => 'GPL v2',
+);
+
+# based on bc-bd's original nm.pl
+#
+# use a ** nickcolor_expando ** script for nick colors!
+#
+# why is there no right_mode? you can do that in your theme!
+
+# Options
+# =======
+# /set neat_dynamic <ON|OFF>
+# * whether the width should be dynamically chosen on each incoming
+# message
+#
+# /set neat_shrink <ON|OFF>
+# * whether shrinking of the width is allowed, or only growing
+#
+# /set neat_staircase_shrink <ON|OFF>
+# * whether shrinking should be done one character at a time
+#
+# The following styles decide if the nick is left/right aligned and
+# where the colour/mode goes, they're a bit complex...
+# put the desired indicator(s) between the appropriate "," and the
+# default format of the public messages or actions will be rewritten
+# appropriately.
+# This can be used to align the nick left or right, before or after
+# the nick brackets and before or between the nickmode (by using the
+# pad on the correct place). To change the mode from left of the nick
+# to right of the nick, you need to modify the abstracts in your theme
+# however.
+# By placing the colour at the end, you can even colour the message
+# text in the nick colour, however it might be broken if there are
+# other colour codes used inside the message or by scripts.
+#
+# /format neat_style , , , , , , , ,
+# î î î î î î î î î
+# p: pad | | | | | | | | `before message
+# c: colour | | | | | | | `-after msgchannel
+# t: truncate indicator | | | | | | `-before msgchannel
+# | | | | | `-after nick
+# | | | | `-before nick
+# | | | `-after mode
+# | | `-before mode
+# | `-before msgnick
+# `-none
+#
+# /format neat_action_style , , , ,
+# î î î î î
+# p: pad | | | | `-before message
+# c: colour | | | `-after nick
+# t: truncate indicator | | `-before nick
+# | `-before action
+# `-none
+#
+# /format neat_pad_char <char>
+# * the character(s) used for padding
+#
+# /format neat_truncate_char
+# * the format or character to indicate that nick was truncated
+#
+# /format neat_notruncate_char
+# * the format or character to indicate that nick NOT was truncated
+#
+# /format neat_customize_modes @@ | ++ | ?
+# * a |-separated mapping of mode prefixes and their rendition, can be
+# used to replace or colourise them
+#
+# /set neat_color_hinick <ON|OFF>
+# * whether to use colours in hilighted messages
+#
+# /set neat_color_menick <ON|OFF>
+# * whether to use colours in hilight_nick_matches
+#
+# /set neat_truncate_nick <ON|OFF>
+# * whether to truncate overlong nicks
+#
+# /set neat_custom_modes <ON|OFF>
+# * whether to enable the use of neat_customize_modes format
+#
+# /set neat_maxlength <number>
+# * number : (maximum) length to use for nick padding
+#
+# /set neat_melength <number>
+# * number : width to substract from maxlength for /me padding
+#
+# /set neat_history <number>
+# * number : number of formatted lines to remember for dynamic mode
+#
+
+my @action_protos = qw(irc silc xmpp);
+my (%histories, %S, @style, @astyle, %format_ok, %cmmap);
+
+my $align_expando = '';
+my $trunc_expando = '';
+my $cumode_expando = '';
+
+my $format_re = qr/ %(?=[}%{])
+ | %[04261537kbgcrmywKBGCRMYWU9_8I:|FnN>#pP[]
+ | %[Zz][[:xdigit:]]{6}
+ | %[Xx](?i:0[a-f]|[1-6][0-9a-z]|7[a-x]) /x;
+
+sub update_expando {
+ my ($mode, $server, $target, $nick, $space) = @_;
+ my $t_add;
+ my $nl = length $nick;
+ my $pad_len = max(0, $space - $nl);
+ if ($S{truncate_nick}) {
+ if (($mode >= 4 && $S{trunc_in_anick})
+ || ($mode < 4 && $S{trunc_in_nick})) {
+ $t_add = $S{tnolen};
+ }
+ if ($nl + $t_add > $space) {
+ $trunc_expando = format_expand($S{tyes_char});
+ $t_add = $S{tyeslen} if defined $t_add;
+ }
+ else {
+ $trunc_expando = format_expand($S{tno_char});
+ }
+ $pad_len = max(0, $pad_len - $t_add) if $t_add;
+ }
+ else {
+ $trunc_expando = '';
+ }
+ if ($pad_len) {
+ my @subs = split /($format_re)/, $S{pad_char} x $pad_len;
+ $align_expando = '';
+ my $clen = 0;
+ while (@subs) {
+ my ($tx, $fmt) = splice @subs, 0, 2;
+ my $txlen = length $tx // 0;
+ $align_expando .= substr $tx, 0, ($pad_len - $clen) if defined $tx;
+ $clen += $txlen;
+ $align_expando .= $fmt if defined $fmt;
+ last if $clen >= $pad_len;
+ }
+ $align_expando = format_expand($align_expando.'%n');
+ }
+ else {
+ $align_expando = '';
+ }
+ return $t_add;
+}
+
+sub prnt_clear_levels {
+ my ($dest) = @_;
+ clear_ref() if $dest->{level}
+ & (MSGLEVEL_PUBLIC|MSGLEVEL_MSGS|MSGLEVEL_ACTIONS|MSGLEVEL_DCCMSGS|MSGLEVEL_NOTICES);
+}
+
+sub clear_ref {
+ $trunc_expando = $align_expando = $cumode_expando = '';
+}
+
+sub expando_nickalign { $align_expando }
+sub expando_nicktrunc { $trunc_expando }
+sub expando_nickcumode { $cumode_expando }
+
+Irssi::expando_create('nickalign', \&expando_nickalign, {
+ 'message public' => 'none',
+ 'message own_public' => 'none',
+ 'message private' => 'none',
+ 'message own_private' => 'none',
+ (map { ("message $_ action" => 'none',
+ "message $_ own_action" => 'none')
+ } @action_protos),
+ });
+Irssi::expando_create('nicktrunc', \&expando_nicktrunc, {
+ 'message public' => 'none',
+ 'message own_public' => 'none',
+ 'message private' => 'none',
+ 'message own_private' => 'none',
+ (map { ("message $_ action" => 'none',
+ "message $_ own_action" => 'none')
+ } @action_protos),
+ });
+Irssi::expando_create('nickcumode', \&expando_nickcumode, {
+ 'message public' => 'none',
+ 'message own_public' => 'none',
+ 'message private' => 'none',
+ 'message own_private' => 'none',
+ (map { ("message $_ action" => 'none',
+ "message $_ own_action" => 'none')
+ } @action_protos),
+ });
+
+sub init_hist {
+ my ($server, $target) = @_;
+ if (my $ch = $server->channel_find($target)) {
+ [ max map { length } map { $_->{nick} } $ch->nicks ]
+ }
+ else {
+ [ max map { length } $server->{nick}, $target ]
+ }
+}
+
+my %em = (
+ p => '$nickalign',
+ c => '$nickcolor',
+ t => '$nicktrunc',
+ m => '$nickcumode',
+ );
+
+my %formats = (
+ own_action => [5, '{ownaction ', '$0','}','$1' ],
+ action_public => [4, '{pubaction ', '$0','}','$1' ],
+ action_private => [4, '{pvtaction ', '$0','}','$2' ],
+ action_private_query => [4, '{pvtaction_query ','$0','}','$2' ],
+ # * * * # * *
+
+ own_msg_private_query => [3, '{ownprivmsgnick ', '' ,'{ownprivnick ','$2','}','' ,'}','$1' ],
+ msg_private_query => [2, '{privmsgnick ' ,'' ,'' ,'$0','' ,'' ,'}','$2' ],
+ own_msg => [1, '{ownmsgnick ' ,'$2',' {ownnick ' ,'$0','}','' ,'}','$1' ],
+ own_msg_channel => [1, '{ownmsgnick ' ,'$3',' {ownnick ' ,'$0','}','{msgchannel $1}','}','$2' ],
+ pubmsg_me => [0, '{pubmsgmenick ' ,'$2',' {menick ' ,'$0','}','' ,'}','$1' ],
+ pubmsg_me_channel => [0, '{pubmsgmenick ' ,'$3',' {menick ' ,'$0','}','{msgchannel $1}','}','$2' ],
+ pubmsg_hilight => [0, '{pubmsghinick $0 ','$3',' ' ,'$1', '','', ,'}','$2' ],
+ pubmsg_hilight_channel => [0, '{pubmsghinick $0 ','$4',' ' ,'$1', '','{msgchannel $2}','}','$3' ],
+ pubmsg => [0, '{pubmsgnick ' ,'$2',' {pubnick ' ,'$0','}','' ,'}','$1' ],
+ pubmsg_channel => [0, '{pubmsgnick ' ,'$3',' {pubnick ' ,'$0','}','{msgchannel $1}','}','$2' ],
+ # * * * * * # * * * *
+ );
+
+sub reformat_format {
+ Irssi::signal_remove('command format', 'update_formats');
+ Irssi::signal_remove('theme changed' => 'update_formats');
+ %format_ok = () unless @_;
+ my ($mode, $server, $target, $nick, $size) = @_;
+ for my $fmt (keys %formats) {
+ next if defined $mode && $formats{$fmt}[0] != $mode;
+
+ my @fs = @{ $formats{$fmt} };
+
+ my $ls;
+ if (defined $mode) {
+ $ls = $size;
+ }
+ else {
+ $ls = $fs[0] < 4 ? $S{max} : max(0, $S{max} - $S{melength});
+ }
+ next if exists $format_ok{$fmt} && $format_ok{$fmt} == $ls;
+
+ if ($S{truncate_nick} && $ls) {
+ $fs[ $fs[0] < 4 ? 4 : 2 ] =~ s/\$/\$[.$ls]/;
+ }
+ if ($S{custom_modes} && $fs[0] < 4) {
+ $fs[2] =~ s/\$\K\d/nickcumode/;
+ }
+ my $s;
+ local $em{c} = ''
+ if ($fs[1] =~ /menick/ && !$S{color_menick})
+ || ($fs[1] =~ /hinick/ && !$S{color_hinick});
+ my $sr = $fs[0] >= 4 ? \@astyle : \@style;
+ for my $i (1..$#fs) {
+ $s .= ($sr->[$i] =~ s/(.)/$em{$1}/gr) if defined $sr->[$i];
+ $s .= $fs[$i];
+ }
+ Irssi::command("^format $fmt $s");
+ $format_ok{$fmt} = $ls;
+ }
+ Irssi::signal_add_last({
+ 'theme changed' => 'update_formats',
+ 'command format' => 'update_formats',
+ });
+}
+
+sub update_nm {
+ my ($mode, $server, $target, $nick) = @_;
+ my $tg = $server->{tag};
+ if (my $ch = $server->channel_find($target)) {
+ $target = $ch->{name};
+ my $nickobj = $ch->nick_find($nick);
+ if ($nickobj) {
+ $nick = $nickobj->{nick};
+ my $mode = substr $nickobj->{prefixes}.' ', 0, 1;
+ $cumode_expando = exists $cmmap{$mode} ? format_expand($cmmap{$mode}) : $mode;
+ }
+ else {
+ $cumode_expando = '';
+ }
+ }
+ elsif (my $q = $server->query_find($target)) {
+ $target = $q->{name};
+ }
+
+ my $longest;
+ if ($S{dynamic}) {
+ my $hist = $histories{"$tg/$target"} ||= init_hist($server, $target);
+ my $last = $histories{"$tg/$target/last"} || 1;
+ unshift @$hist, length $nick;
+ if (@$hist > 2*$S{history}) {
+ splice @$hist, $S{history};
+ }
+ my @add;
+ unless ($S{shrink}) {
+ push @add, $last;
+ }
+ if ($S{staircase}) {
+ push @add, $last - 1
+ }
+ $longest = $histories{"$tg/$target/last"} = max(@$hist, @add);
+
+ if ($S{max} && ($S{max} < $longest || !$S{shrink})) {
+ $longest = $S{max};
+ }
+ }
+ else {
+ $longest = $S{max};
+ }
+
+ my $size = $mode < 4 ? $longest : max(0, $longest - $S{melength});
+ my $t_add = update_expando($mode, $server, $target, $nick, $size);
+ $size = max(0, $size - $t_add) if defined $t_add;
+ if ($S{dynamic}) {
+ reformat_format($mode, $server, $target, $nick, $size);
+ }
+}
+
+sub sig_setup {
+ my %old_S = %S;
+ $S{history} = Irssi::settings_get_int('neat_history');
+ $S{max} = Irssi::settings_get_int('neat_maxlength');
+ $S{melength} = Irssi::settings_get_int('neat_melength');
+
+ $S{dynamic} = Irssi::settings_get_bool('neat_dynamic');
+ $S{shrink} = Irssi::settings_get_bool('neat_shrink');
+ $S{staircase} = Irssi::settings_get_bool('neat_staircase_shrink');
+
+ $S{color_hinick} = Irssi::settings_get_bool('neat_color_hinick');
+ $S{color_menick} = Irssi::settings_get_bool('neat_color_menick');
+ $S{truncate_nick} = Irssi::settings_get_bool('neat_truncate_nick');
+ $S{custom_modes} = Irssi::settings_get_bool('neat_custom_modes');
+
+ if (!defined $old_S{dynamic} || $old_S{dynamic} != $S{dynamic}) {
+ %histories = ();
+ reformat_format();
+ }
+ elsif ($old_S{max} != $S{max} || $old_S{melength} != $S{melength}
+ || $old_S{color_hinick} != $S{color_hinick} || $old_S{color_menick} != $S{color_menick}
+ || $old_S{truncate_nick} != $S{truncate_nick} || $old_S{custom_modes} != $S{custom_modes}) {
+ reformat_format();
+ }
+}
+
+sub update_formats {
+ my $was_style = "@style";
+ $S{style} = Irssi::current_theme->get_format(__PACKAGE__, 'neat_style');
+ my $was_action_style = "@astyle";
+ $S{action_style} = Irssi::current_theme->get_format(__PACKAGE__, 'neat_action_style');
+ $S{pad_char} = Irssi::current_theme->get_format(__PACKAGE__, 'neat_pad_char');
+ $S{tno_char} = Irssi::current_theme->get_format(__PACKAGE__, 'neat_notruncate_char');
+ $S{tnolen} = length($S{tno_char} =~ s/$format_re//gr);
+ $S{tyeslen} = length($S{tyes_char} =~ s/$format_re//gr);
+ $S{tyes_char} = Irssi::current_theme->get_format(__PACKAGE__, 'neat_truncate_char');
+ @style = map { y/pct//cd; $_ } split /,/, $S{style};
+ @astyle = map { y/pctm//cd; $_ } split /,/, $S{action_style};
+ $S{trunc_in_nick} = grep { /t/ } @style[2..min($#style, 6)];
+ $S{trunc_in_anick} = grep { /t/ } @astyle[2..min($#astyle, 3)];
+ my $custom_modes = Irssi::current_theme->get_format(__PACKAGE__, 'neat_custom_modes');
+ %cmmap = map { (substr $_, 0, 1), (substr $_, 1) } $custom_modes =~ /(?:^\s?|\G\s?\|\s?)((?!\s\|)(?:[^\\|[:space:]]|\\.|\s(?!\||$))*)/sg;
+ if ($was_style ne "@style" || $was_action_style ne "@astyle") {
+ reformat_format();
+ }
+}
+
+{
+ my %format2control = (
+ 'F' => "\cDa", '_' => "\cDc", '|' => "\cDe", '#' => "\cDi", "n" => "\cDg", "N" => "\cDg",
+ 'U' => "\c_", '8' => "\cV", 'I' => "\cDf",
+ );
+ my %bg_base = (
+ '0' => '0', '4' => '1', '2' => '2', '6' => '3', '1' => '4', '5' => '5', '3' => '6', '7' => '7',
+ 'x08' => '8', 'x09' => '9', 'x0a' => ':', 'x0b' => ';', 'x0c' => '<', 'x0d' => '=', 'x0e' => '>', 'x0f' => '?',
+ );
+ my %fg_base = (
+ 'k' => '0', 'b' => '1', 'g' => '2', 'c' => '3', 'r' => '4', 'm' => '5', 'p' => '5', 'y' => '6', 'w' => '7',
+ 'K' => '8', 'B' => '9', 'G' => ':', 'C' => ';', 'R' => '<', 'M' => '=', 'P' => '=', 'Y' => '>', 'W' => '?',
+ );
+ my @ext_colour_off = (
+ '.', '-', ',',
+ '+', "'", '&',
+ );
+ sub format_expand {
+ $_[0] =~ s{%(Z.{6}|z.{6}|X..|x..|.)}{
+ my $c = $1;
+ if (exists $format2control{$c}) {
+ $format2control{$c}
+ }
+ elsif (exists $bg_base{$c}) {
+ "\cD/$bg_base{$c}"
+ }
+ elsif (exists $fg_base{$c}) {
+ "\cD$fg_base{$c}/"
+ }
+ elsif ($c =~ /^[{}%]$/) {
+ $c
+ }
+ elsif ($c =~ /^(z|Z)([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})$/) {
+ my $bg = $1 eq 'z';
+ my (@rgb) = map { hex $_ } $2, $3, $4;
+ my $x = $bg ? 0x1 : 0;
+ my $out = "\cD" . (chr -13 + ord '0');
+ for (my $i = 0; $i < 3; ++$i) {
+ if ($rgb[$i] > 0x20) {
+ $out .= chr $rgb[$i];
+ }
+ else {
+ $x |= 0x10 << $i; $out .= chr 0x20 + $rgb[$i];
+ }
+ }
+ $out .= chr 0x20 + $x;
+ $out
+ }
+ elsif ($c =~ /^(x)(?:0([[:xdigit:]])|([1-6])(?:([0-9])|([a-z]))|7([a-x]))$/i) {
+ my $bg = $1 eq 'x';
+ my $col = defined $2 ? hex $2
+ : defined $6 ? 232 + (ord lc $6) - (ord 'a')
+ : 16 + 36 * ($3 - 1) + (defined $4 ? $4 : 10 + (ord lc $5) - (ord 'a'));
+ if ($col < 0x10) {
+ my $chr = chr $col + ord '0';
+ "\cD" . ($bg ? "/$chr" : "$chr/")
+ }
+ else {
+ "\cD" . $ext_colour_off[($col - 0x10) / 0x50 + $bg * 3] . chr (($col - 0x10) % 0x50 - 1 + ord '0')
+ }
+ }
+ else {
+ "%$c"
+ }
+ }ger;
+ }
+}
+
+sub init {
+ update_formats();
+ sig_setup();
+ lock_keys(%S);
+ print "nm2 experimental version, please report issues. thanks!"
+}
+
+Irssi::settings_add_bool('misc', 'neat_dynamic', 1);
+Irssi::settings_add_bool('misc', 'neat_shrink', 1);
+Irssi::settings_add_bool('misc', 'neat_staircase_shrink', 0);
+
+Irssi::settings_add_bool('misc', 'neat_color_hinick', 0);
+Irssi::settings_add_bool('misc', 'neat_color_menick', 0);
+Irssi::settings_add_bool('misc', 'neat_truncate_nick', 1);
+Irssi::settings_add_bool('misc', 'neat_custom_modes', 0);
+
+Irssi::settings_add_int('misc', 'neat_maxlength', 0);
+Irssi::settings_add_int('misc', 'neat_melength', 2);
+Irssi::settings_add_int('misc', 'neat_history', 50);
+
+Irssi::signal_add('setup changed' => 'sig_setup');
+Irssi::signal_add_last({
+ 'setup reread' => 'sig_setup',
+ 'theme changed' => 'update_formats',
+ 'command format' => 'update_formats',
+ });
+
+Irssi::theme_register([
+ 'neat_style' => ' , , p , , c , t , , , ',
+ 'neat_action_style' => ' , p , , t , ',
+ 'neat_pad_char' => '%K.',
+ 'neat_truncate_char' => '%m+',
+ 'neat_notruncate_char' => '',
+ 'neat_custom_modes' => '&%B&%n | @%g@%n | +%y+%n',
+ ]);
+
+Irssi::signal_add_first({
+ 'message public' => sub {
+ my ($server, $msg, $nick, $address, $target) = @_;
+ update_nm(0, $server, $target, $nick);
+ },
+ 'message private' => sub {
+ my ($server, $msg, $nick, $address) = @_;
+ update_nm(2, $server, $nick, $nick);
+ },
+ (map { ("message $_ action" => sub {
+ my ($server, $msg, $nick, $address, $target) = @_;
+ update_nm(4, $server, $target, $nick);
+ }) } qw(irc silc)),
+ 'message xmpp action' => sub {
+ return unless @_;
+ my ($server, $msg, $nick, $target) = @_;
+ update_nm(4, $server, $target, $nick);
+ },
+ });
+
+sub channel_nick {
+ my ($server, $target) = @_;
+ ($server->channel_find($target)||+{ownnick=>$server})->{ownnick}{nick}
+}
+
+Irssi::signal_add_first({
+ 'message own_public' => sub {
+ my ($server, $msg, $target) = @_;
+ update_nm(1, $server, $target, channel_nick($server, $target));
+ },
+ 'message own_private' => sub {
+ my ($server, $msg, $target) = @_;
+ update_nm(3, $server, $target, $server->{nick});
+ },
+ (map { ("message $_ own_action" => sub {
+ my ($server, $msg, $target) = @_;
+ update_nm(5, $server, $target, $server->{nick});
+ }) } qw(irc silc)),
+ 'message xmpp own_action' => sub {
+ return unless @_;
+ my ($server, $msg, $target) = @_;
+ update_nm(5, $server, $target, channel_nick($server, $target));
+ },
+ });
+Irssi::signal_add_last({
+ 'channel destroyed' => sub {
+ my ($channel) = @_;
+ delete $histories{ $channel->{server}{tag} . '/' . $channel->{name} };
+ delete $histories{ $channel->{server}{tag} . '/' . $channel->{name} . '/last' };
+ },
+ 'query destroyed' => sub {
+ my ($query) = @_;
+ delete $histories{ $query->{server}{tag} . '/' . $query->{name} };
+ delete $histories{ $query->{server}{tag} . '/' . $query->{name} . '/last' };
+ },
+ 'query nick changed' => sub {
+ my ($query, $old_nick) = @_;
+ delete $histories{ $query->{server}{tag} . '/' . $old_nick };
+ delete $histories{ $query->{server}{tag} . '/' . $old_nick . '/last' };
+ },
+ 'query server changed' => sub {
+ my ($query, $old_server) = @_;
+ delete $histories{ $old_server->{tag} . '/' . $query->{name} };
+ delete $histories{ $old_server->{tag} . '/' . $query->{name} . '/last' };
+ }
+ });
+Irssi::signal_add({
+ 'print text' => 'prnt_clear_levels',
+});
+
+init();
+
+# Changelog
+# =========
+# 2.0-dev
+# - fix crash if xmpp action signal is not registered (just ignore it)
+# - do not grow either when using no-shrink with maxlength
+# - hopefully fix alignment in xmpp muc
diff --git a/scripts/recentdepart.pl b/scripts/recentdepart.pl
new file mode 100644
index 0000000..f0cc121
--- /dev/null
+++ b/scripts/recentdepart.pl
@@ -0,0 +1,332 @@
+#!/usr/bin/perl -w
+#
+# recentdepart.pl
+#
+# irssi script
+#
+# This script, when loaded into irssi, will filter quit and parted messages
+# for channels listed in recdep_channels for any nick whos last message was
+# more then a specified time ago.
+#
+# It also filters join messages showing only join messages for nicks who recently
+# parted.
+#
+# [settings]
+# recdep_channels
+# - Should contain a list of chatnet and channel names that recentdepart
+# should monitor. Its format is a spcae delimited list of chatnet/#channel
+# pairs. Either chatnet or channel are optional but adding a / makes it
+# explicitly recognized as a chatnet or a channel name. A special case is just a
+# "*" which turns it on globally.
+#
+# "#irrsi #perl" - enables filtering on the #irssi and #perl
+# channels on all chatnets.
+#
+# "freenode IRCNet/#irssi" - enables filtering for all channels on frenode
+# and only the #irssi channel on IRCNet.
+#
+# "freenode/" - force "freenode" to be interpreted as the chatnet
+# name by adding a / to the end.
+#
+# "/freenode" - forces "freenode" to be interpreted as the channel
+# by prefixing it with the / delimiter.
+#
+# "*" - globally enables filtering.
+#
+# recdep_period
+# - specifies the window of time, after a nick last spoke, for which quit/part
+# notices will be let through the filter.
+#
+# recdep_rejoin
+# - specifies a time period durring which a join notice for someone rejoining will
+# be shown. Join messages are filtered if the nicks part/quit message was filtered
+# or if the nick is gone longer then the rejoin period.
+# Set to 0 to turn off filtering of join notices.
+#
+# recdep_nickperiod
+# - specifies a window of time like recdep_period that is used to filter nick change
+# notices. Set to 0 to turn off filtering of nick changes.
+#
+# recdep_use_hideshow
+# - whether to use hideshow script instead of ignoring
+#
+
+use strict;
+use warnings;
+use Irssi;
+use Irssi::Irc;
+
+our $VERSION = "0.6";
+our %IRSSI = (
+ authors => 'Matthew Sytsma',
+ contact => 'spiderpigy@yahoo.com',
+ name => 'Recently Departed',
+ description => 'Filters quit/part/join/nick notices based on time since last message. (Similar to weechat\'s smartfilter).',
+ license => 'GNU GPLv2 or later',
+ url => '',
+);
+
+# store a hash of configure selected servers/channels
+my %chanlist;
+# Track recent times by server/nick/channel
+# (it is more optimal to go nick/channel then channel/nick because some quit signals are by server not by channel.
+# We will only have to loop through open channels that a nick has spoken in which should be less then looping
+# through all the monitored channels looking for the nick.
+my %nickhash=();
+# Track recent times for parted nicks by server/channel/nick
+my %joinwatch=();
+my $use_hide;
+
+sub on_setup_changed {
+ my %old_chanlist = %chanlist;
+ %chanlist = ();
+ my @pairs = split(/ /, Irssi::settings_get_str("recdep_channels"));
+
+ $use_hide = Irssi::settings_get_bool("recdep_use_hideshow");
+ foreach (@pairs)
+ {
+ my ($net, $chan, $more) = split(/\//);
+ if ($more)
+ {
+ /\/(.+)/;
+ $chan = $1;
+ }
+# Irssi::active_win()->print("Initial Net: $net Chan: $chan");
+ if (!$net)
+ {
+ $net = '*';
+ }
+
+ if ($net =~ /^[#!@&]/ && !$chan)
+ {
+ $chan = $net;
+ $net = "*";
+ }
+
+ if (!$chan)
+ {
+ $chan = "*";
+ }
+
+ $chanlist{$net}{$chan} = 1;
+ }
+
+ # empty the storage in case theres a channel or server we are no longer filtering
+ %nickhash=();
+ %joinwatch=();
+}
+
+sub check_channel
+{
+ my ($server, $channel) = @_;
+
+ # quits dont have a channel so we need to see if this server possibly contains this channel
+ if (!$channel || $channel eq '*')
+ {
+ # see if any non chatnet channel listings are open on this server
+ if (keys %{ $chanlist{'*'} })
+ {
+ foreach my $chan (keys %{ $chanlist{'*'} })
+ {
+ if ($chan eq '*' || $server->channel_find($chan))
+ {
+ return 1;
+ }
+ }
+ }
+
+ # see if there were any channels listed for this chatnet
+ if (keys %{ $chanlist{$server->{'chatnet'}} })
+ { return 1; }
+ else
+ { return 0; }
+ }
+
+ # check for global channel matches and pair matches
+ return (($chanlist{'*'}{'*'}) ||
+ ($chanlist{'*'}{$channel}) ||
+ ($chanlist{$server->{'chatnet'}}{'*'}) ||
+ ($chanlist{$server->{'chatnet'}}{$channel}));
+}
+
+# Hook for quitting
+sub on_quit
+{
+ my ($server, $nick, $address, $reason) = @_;
+
+ if ($server->{'nick'} eq $nick)
+ { return; }
+
+ if (check_channel($server, '*'))
+ {
+ my $recent = 0;
+ foreach my $chan (keys %{ $nickhash{$server->{'tag'}}{lc($nick)} })
+ {
+ if (time() - $nickhash{$server->{'tag'}}{lc($nick)}{$chan} < Irssi::settings_get_int("recdep_period"))
+ {
+ $recent = 1;
+
+ if (Irssi::settings_get_int("recdep_rejoin") > 0)
+ {
+ $joinwatch{$server->{'tag'}}{$chan}{lc($nick)} = time();
+ }
+ }
+ }
+
+ delete $nickhash{$server->{'tag'}}{lc($nick)};
+
+ if (!$recent)
+ {
+ $use_hide ? $Irssi::scripts::hideshow::hide_next = 1
+ : Irssi::signal_stop();
+ }
+ }
+}
+
+# Hook for parting
+sub on_part
+{
+ my ($server, $channel, $nick, $address, $reason) = @_;
+
+ # cleanup if its you who left a channel
+ if ($server->{'nick'} eq $nick)
+ {
+ # slightly painfull cleanup but we shouldn't hit this as often
+ foreach my $nickd (keys %{ $nickhash{$server->{'tag'}} })
+ {
+ delete $nickhash{$server->{'tag'}}{$nickd}{$channel};
+ if (!keys(%{ $nickhash{$server->{'tag'}}{$nickd} }))
+ {
+ delete $nickhash{$server->{'tag'}}{$nickd};
+ }
+ }
+ delete $joinwatch{$server->{'tag'}}{$channel};
+ }
+ elsif (check_channel($server, $channel))
+ {
+ if (time() - $nickhash{$server->{'tag'}}{lc($nick)}{$channel} > Irssi::settings_get_int("recdep_period"))
+ {
+ $use_hide ? $Irssi::scripts::hideshow::hide_next = 1
+ : Irssi::signal_stop();
+ }
+ elsif (Irssi::settings_get_int("recdep_rejoin") > 0)
+ {
+ $joinwatch{$server->{'tag'}}{$channel}{lc($nick)} = time();
+ }
+
+ delete $nickhash{$server->{'tag'}}{lc($nick)}{$channel};
+ if (!keys(%{ $nickhash{$server->{'tag'}}{lc($nick)} }))
+ {
+ delete $nickhash{$server->{'tag'}}{lc($nick)};
+ }
+ }
+}
+
+# Hook for public messages.
+sub on_public
+{
+ my ($server, $msg, $nick, $addr, $target) = @_;
+
+ if (!$target) { return; }
+ if ($nick =~ /^#/) { return; }
+
+ if ($server->{'nick'} eq $nick) { return; }
+
+ if (check_channel($server, $target))
+ {
+ $nickhash{$server->{'tag'}}{lc($nick)}{$target} = time();
+ }
+}
+
+# Hook for people joining
+sub on_join
+{
+ my ($server, $channel, $nick, $address) = @_;
+
+ if ($server->{'nick'} eq $nick)
+ { return; }
+
+ if (Irssi::settings_get_int("recdep_rejoin") == 0)
+ { return; }
+
+ if (check_channel($server, $channel))
+ {
+ if (time() - $joinwatch{$server->{'tag'}}{$channel}{lc($nick)} > Irssi::settings_get_int("recdep_rejoin"))
+ {
+ $use_hide ? $Irssi::scripts::hideshow::hide_next = 1
+ : Irssi::signal_stop();
+ }
+ }
+
+ # loop through and delete all old nicks from the rejoin hash
+ # this should be a small loop because it will only inlude nicks who recently left channel and who
+ # passed the part message filter
+ foreach my $nickd (keys %{ $joinwatch{$server->{'tag'}}{$channel} })
+ {
+ if (time() - $joinwatch{$server->{'tag'}}{$channel}{lc($nickd)} < Irssi::settings_get_int("recdep_rejoin"))
+ { next; }
+
+ delete $joinwatch{$server->{'tag'}}{$channel}{lc($nickd)};
+ }
+ if (!keys(%{ $joinwatch{$server->{'tag'}}{$channel} }))
+ {
+ delete $joinwatch{$server->{'tag'}}{$channel};
+ }
+}
+
+# Hook for nick changes
+sub on_nick
+{
+ my ($server, $new, $old, $address) = @_;
+
+ if ($server->{'nick'} eq $old || $server->{'nick'} eq $new)
+ { return; }
+
+ if (check_channel($server, '*'))
+ {
+ my $recent = 0;
+ foreach my $chan (keys %{ $nickhash{$server->{'tag'}}{lc($old)} })
+ {
+ if (time() - $nickhash{$server->{'tag'}}{lc($old)}{$chan} < Irssi::settings_get_int("recdep_nickperiod"))
+ {
+ $recent = 1;
+ }
+ }
+
+ if (!$recent && Irssi::settings_get_int("recdep_nickperiod") > 0)
+ {
+ $use_hide ? $Irssi::scripts::hideshow::hide_next = 1
+ : Irssi::signal_stop();
+ }
+
+ delete $nickhash{$server->{'tag'}}{lc($old)};
+ }
+}
+
+
+# Hook for cleanup on server quit
+sub on_serverquit
+{
+ my ($server, $msg) = @_;
+
+ delete $nickhash{$server->{'tag'}};
+ delete $joinwatch{$server->{'tag'}};
+}
+
+# Setup hooks on events
+Irssi::signal_add_last("message public", "on_public");
+Irssi::signal_add_last("message part", "on_part");
+Irssi::signal_add_last("message quit", "on_quit");
+Irssi::signal_add_last("message nick", "on_nick");
+Irssi::signal_add_last("message join", "on_join");
+Irssi::signal_add_last("server disconnected", "on_serverquit");
+Irssi::signal_add_last("server quit", "on_serverquit");
+Irssi::signal_add('setup changed', "on_setup_changed");
+
+# Add settings
+Irssi::settings_add_str("recdentpepart", "recdep_channels", '*');
+Irssi::settings_add_int("recdentpepart", "recdep_period", 600);
+Irssi::settings_add_int("recdentpepart", "recdep_rejoin", 120);
+Irssi::settings_add_int("recdentpepart", "recdep_nickperiod", 600);
+Irssi::settings_add_bool("recdentpepart", "recdep_use_hideshow", 0);
+on_setup_changed();
diff --git a/scripts/sb_position.pl b/scripts/sb_position.pl
new file mode 100644
index 0000000..a578094
--- /dev/null
+++ b/scripts/sb_position.pl
@@ -0,0 +1,112 @@
+
+# Display current position in scrollback in a statusbar item named 'position'.
+
+# Copyright (C) 2010 Simon Ruderich & Tom Feist
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+use strict;
+use warnings;
+
+use Irssi;
+use Irssi::TextUI;
+use POSIX qw(ceil);
+
+{ package Irssi::Nick }
+
+our $VERSION = '0.1'; # ca1d079b9abed12
+our %IRSSI = (
+ authors => 'Simon Ruderich, Tom Feist',
+ contact => 'simon@ruderich.org, shabble+irssi@metavore.org',
+ name => 'sb_position',
+ description => 'Displays current position in scrollback.',
+ license => 'GPLv3 or later',
+ changed => '2010-12-02'
+);
+
+my ($buf, $size, $pos, $height, $empty);
+my ($pages, $cur_page, $percent);
+
+
+init();
+
+sub init {
+
+ # (re-)register it so we can access the WIN_REC object directly.
+ Irssi::signal_register({'gui page scrolled' => [qw/Irssi::UI::Window/]});
+ # primary update signal.
+ Irssi::signal_add('gui page scrolled', \&update_position);
+ Irssi::statusbar_item_register('position', 0, 'position_statusbar');
+
+ Irssi::signal_add("window changed", \&update_position);
+ Irssi::signal_add_last("command clear", \&update_cmd_shim);
+ Irssi::signal_add_last("command scrollback", \&update_cmd_shim);
+ # Irssi::signal_add_last("gui print text finished", sig_statusbar_more_updated);
+
+ update_position(Irssi::active_win());
+}
+
+sub update_cmd_shim {
+ my ($cmd, $server, $witem) = @_;
+
+ my $win = $witem ? $witem->window : Irssi::active_win;
+
+ update_position($win);
+}
+
+sub update_position {
+
+ my $win = shift;
+ return unless $win;
+
+ my $view = $win->view;
+
+ $pos = $view->{ypos};
+ $buf = $view->{buffer};
+ $height = $view->{height};
+ $size = $buf->{lines_count};
+
+ $empty = $view->{empty_linecount};
+ $empty = 0 unless $empty;
+
+
+ $pages = ceil($size / $height);
+ $pages = 1 unless $pages;
+
+ my $buf_pos_cache = $size - $pos + ($height - $empty) - 1;
+
+ if ($pos == -1 or $size < $height) {
+ $cur_page = $pages;
+ $percent = 100;
+ } elsif ($pos > ($size - $height)) {
+ $cur_page = 1;
+ $percent = 0;
+ } else {
+ $cur_page = ceil($buf_pos_cache / $height);
+ $percent = ceil($buf_pos_cache / $size * 100);
+ }
+
+ Irssi::statusbar_items_redraw('position');
+}
+
+sub position_statusbar {
+ my ($statusbar_item, $get_size_only) = @_;
+
+ # Alternate view.
+ #my $sb = "p=$pos, s=$size, h=$height, pp:$cur_page/$pages $percent%%";
+ my $sb = "Page: $cur_page/$pages $percent%%";
+
+ $statusbar_item->default_handler($get_size_only, "{sb $sb}", 0, 1);
+}
diff --git a/scripts/sbclearmatch.pl b/scripts/sbclearmatch.pl
new file mode 100644
index 0000000..c2fb87f
--- /dev/null
+++ b/scripts/sbclearmatch.pl
@@ -0,0 +1,93 @@
+use strict;
+use warnings;
+use Irssi;
+use Irssi::TextUI;
+
+our $VERSION = '0.2'; # 6c39400282189a0
+our %IRSSI = (
+ authors => 'Nei',
+ contact => 'Nei @ anti@conference.jabber.teamidiot.de',
+ url => "http://anti.teamidiot.de/",
+ name => 'sbclearmatch',
+ description => 'clear matching lines in scrollback',
+ license => 'GPLv2 or later',
+);
+
+sub cmd_help {
+ my ($args) = @_;
+ if ($args =~ /^scrollback *$/i) {
+ print CLIENTCRAP <<HELP
+
+SCROLLBACK CLEARMATCH [-level <level>] [-regexp] [-case] [-word] [-all] [<pattern>]
+
+ CLEARMATCH: Clears the screen and the buffer of matching text.
+
+ -regexp: The given text pattern is a regular expression.
+ -case: Performs a case-sensitive matching.
+ -word: The text must match full words.
+HELP
+
+ }
+}
+
+
+sub cmd_sb_clearmatch {
+ my ($args, $server, $witem) = @_;
+ my ($options, $pattern) = Irssi::command_parse_options('scrollback clearmatch', $args);
+
+ my $level;
+ if (defined $options->{level}) {
+ $level = $options->{level};
+ $level =~ y/,/ /;
+ $level = Irssi::combine_level(0, $level);
+ }
+ else {
+ return unless length $pattern;
+ $level = MSGLEVEL_ALL;
+ }
+
+ my $regex;
+ if (length $pattern) {
+ my $flags = defined $options->{case} ? '' : '(?i)';
+ my $b = defined $options->{word} ? '\b' : '';
+ if (defined $options->{regexp}) {
+ local $@;
+ eval { $regex = qr/$flags$b$pattern$b/; 1 }
+ or do {
+ print CLIENTERROR "Pattern did not compile: " . do { $@ =~ /(.*) at / && $1 };
+ return;
+ };
+ }
+ else {
+ $regex = qr/$flags$b\Q$pattern\E$b/;
+ }
+ }
+
+ my $current_win = ref $witem ? $witem->window : Irssi::active_win;
+
+ for my $win (defined $options->{all} ? Irssi::windows : $current_win) {
+ my $view = $win->view;
+ my $line = $view->get_lines;
+ my $need_redraw;
+ my $bottom = $view->{bottom};
+
+ while ($line) {
+ my $line_level = $line->{info}{level};
+ my $next = $line->next;
+ if ($line_level & $level && $line->get_text(0) =~ $regex) {
+ $view->remove_line($line);
+ $need_redraw = 1;
+ }
+ $line = $next;
+ }
+
+ if ($need_redraw) {
+ $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
+ $view->redraw;
+ }
+ }
+}
+
+Irssi::command_bind 'scrollback clearmatch' => 'cmd_sb_clearmatch';
+Irssi::command_set_options 'scrollback clearmatch' => '-level regexp case word all';
+Irssi::command_bind_last 'help' => 'cmd_help';
diff --git a/scripts/tmux-nicklist-portable.pl b/scripts/tmux-nicklist-portable.pl
new file mode 100644
index 0000000..d4da3dc
--- /dev/null
+++ b/scripts/tmux-nicklist-portable.pl
@@ -0,0 +1,390 @@
+# based on the nicklist.pl script
+################################################################################
+# tmux_nicklist.pl
+# This script integrates tmux and irssi to display a list of nicks in a
+# vertical right pane with 20% width. Right now theres no configuration
+# or setup, simply initialize the script with irssi and by default you
+# will get the nicklist for every channel(customize by altering
+# the regex in /set nicklist_channel_re)
+#
+# /set nicklist_channel_re <regex>
+# * only show on channels matching this regular expression
+#
+# /set nicklist_max_users <num>
+# * only show when the channel has so many users or less (0 = always)
+#
+# /set nicklist_smallest_main <num>
+# * only show when main window is larger than this (0 = always)
+#
+# /set nicklist_pane_width <num>
+# * width of the nicklist pane
+#
+# /set nicklist_color <ON|OFF>
+# * colourise the nicks in the nicklist (required nickcolor script
+# with get_nick_color2 and debug_ansicolour functions)
+#
+#
+# It supports mouse scrolling and the following keys:
+# k/up arrow: up one line
+# j/down arrow: down one line
+# pageup: up 20 lines
+# pagedown: down 20 lines
+# gg: go to top
+# G: go to bottom
+#
+# For better integration, unrecognized sequences will be sent to irssi and
+# its pane will be focused.
+################################################################################
+
+use strict;
+use warnings;
+use IO::Handle;
+use IO::Select;
+use POSIX;
+use File::Temp qw/ :mktemp /;
+use File::Basename;
+our $VERSION = '0.1.3'; # 82c05732e50bb62
+our %IRSSI = (
+ authors => 'Thiago de Arruda',
+ contact => 'tpadilha84@gmail.com',
+ name => 'tmux-nicklist',
+ description => 'displays a list of nicks in a separate tmux pane',
+ license => 'WTFPL',
+);
+
+# "other" prefixes by danielg4 <daniel@gimpelevich.san-francisco.ca.us>
+
+{ package Irssi::Nick }
+
+if ($#ARGV == -1) {
+require Irssi;
+
+my $enabled = 0;
+my $script_path = __FILE__;
+my $tmpdir;
+my $fifo_path;
+my $fifo;
+my $just_launched;
+my $resize_timer;
+
+sub enable_nicklist {
+ return if ($enabled);
+ $tmpdir = mkdtemp Irssi::get_irssi_dir()."/nicklist-XXXXXXXX";
+ $fifo_path = "$tmpdir/fifo";
+ POSIX::mkfifo($fifo_path, 0600) or die "can't mkfifo $fifo_path: $!";
+ my $cmd = "perl $script_path $fifo_path $ENV{TMUX_PANE}";
+ my $width = Irssi::settings_get_int('nicklist_pane_width');
+ system('tmux', 'split-window', '-dh', '-l', $width, '-t', $ENV{TMUX_PANE}, $cmd);
+ open_fifo();
+ Irssi::timeout_remove($just_launched) if defined $just_launched;
+ $just_launched = Irssi::timeout_add_once(300, sub { $just_launched = undef; }, '');
+}
+
+sub open_fifo {
+ # The next system call will block until the other pane has opened the pipe
+ # for reading, so synchronization is not an issue here.
+ open $fifo, ">", $fifo_path or do {
+ if ($! == 4) {
+ Irssi::timeout_add_once(300, \&open_fifo, '');
+ $enabled = -1 unless $enabled;
+ return;
+ }
+ die "can't open $fifo_path: $!";
+ };
+ $fifo->autoflush(1);
+ if ($enabled < -1) {
+ $enabled = 1;
+ disable_nicklist();
+ } elsif ($enabled == -1) {
+ $enabled = 1;
+ reset_nicklist();
+ } else {
+ $enabled = 1;
+ }
+}
+
+sub disable_nicklist {
+ return unless ($enabled);
+ if ($enabled > 0) {
+ print $fifo "EXIT\n";
+ close $fifo;
+ $fifo = undef;
+ unlink $fifo_path;
+ rmdir $tmpdir;
+ }
+ $enabled--;
+}
+
+sub reset_nicklist {
+ my $active = Irssi::active_win();
+ my $channel = $active->{active};
+ my ($colourer, $ansifier);
+ if (Irssi::settings_get_bool('nicklist_color')) {
+ for my $script (sort map { my $z = $_; $z =~ s/::$//; $z } grep { /^nickcolor|nm/ } keys %Irssi::Script::) {
+ if ($colourer = "Irssi::Script::$script"->can('get_nick_color2')) {
+ $ansifier = "Irssi::Script::$script"->can('debug_ansicolour');
+ last;
+ }
+ }
+ }
+ my $channel_pattern = Irssi::settings_get_str('nicklist_channel_re');
+ { local $@;
+ $channel_pattern = eval { qr/$channel_pattern/ };
+ $channel_pattern = qr/(?!)/ if $@;
+ }
+
+ if (!$channel || !ref($channel)
+ || !$channel->isa('Irssi::Channel')
+ || !$channel->{'names_got'}
+ || $channel->{'name'} !~ $channel_pattern) {
+ disable_nicklist;
+ } else {
+ my %colour;
+ my @nicks = $channel->nicks();
+ my $max_nicks = Irssi::settings_get_int('nicklist_max_users');
+ if ($max_nicks && @nicks > $max_nicks) {
+ disable_nicklist;
+ } else {
+ enable_nicklist;
+ return unless $enabled > 0;
+ foreach my $nick (sort { $a->{_irssi} <=> $b->{_irssi} } @nicks) {
+ $colour{$nick->{nick}} = ($ansifier && $colourer) ? $ansifier->($colourer->($active->{active}{server}{tag}, $channel->{name}, $nick->{nick}, 0)) : '';
+ }
+ print($fifo "BEGIN\n");
+ foreach my $nick (sort {(($a->{'op'}?'1':$a->{'halfop'}?'2':$a->{'voice'}?'3':$a->{'other'}>32?'0':'4').lc($a->{'nick'}))
+ cmp (($b->{'op'}?'1':$b->{'halfop'}?'2':$b->{'voice'}?'3':$b->{'other'}>32?'0':'4').lc($b->{'nick'}))} @nicks) {
+ my $colour = $colour{$nick->{nick}} || "\e[39m";
+ $colour = "\e[37m" if $nick->{'gone'};
+ print($fifo "NICK");
+ if ($nick->{'op'}) {
+ print($fifo "\e[32m\@$colour$nick->{'nick'}\e[39m");
+ } elsif ($nick->{'halfop'}) {
+ print($fifo "\e[34m%$colour$nick->{'nick'}\e[39m");
+ } elsif ($nick->{'voice'}) {
+ print($fifo "\e[33m+$colour$nick->{'nick'}\e[39m");
+ } elsif ($nick->{'other'}>32) {
+ print($fifo "\e[31m".(chr $nick->{'other'})."$colour$nick->{'nick'}\e[39m");
+ } else {
+ print($fifo " $colour$nick->{'nick'}\e[39m");
+ }
+ print($fifo "\n");
+ }
+ print($fifo "END\n");
+ }
+ }
+}
+
+sub switch_channel {
+ print $fifo "SWITCH_CHANNEL\n" if $fifo;
+ reset_nicklist;
+}
+
+sub resized_timed {
+ Irssi::timeout_remove($resize_timer) if defined $resize_timer;
+ return if defined $just_launched;
+ $resize_timer = Irssi::timeout_add_once(1100, \&resized, '');
+ #resized();
+}
+sub resized {
+ $resize_timer = undef;
+ return if defined $just_launched;
+ return unless $enabled > 0;
+ disable_nicklist;
+ Irssi::timeout_add_once(200, \&reset_nicklist, '');
+}
+sub UNLOAD {
+ disable_nicklist;
+}
+
+Irssi::settings_add_str('tmux_nicklist', 'nicklist_channel_re', '.*');
+Irssi::settings_add_int('tmux_nicklist', 'nicklist_max_users', 0);
+Irssi::settings_add_int('tmux_nicklist', 'nicklist_smallest_main', 0);
+Irssi::settings_add_int('tmux_nicklist', 'nicklist_pane_width', 13);
+Irssi::settings_add_bool('tmux_nicklist', 'nicklist_color', 1);
+Irssi::signal_add_last('window item changed', \&switch_channel);
+Irssi::signal_add_last('window changed', \&switch_channel);
+Irssi::signal_add_last('channel joined', \&switch_channel);
+Irssi::signal_add('nicklist new', \&reset_nicklist);
+Irssi::signal_add('nicklist remove', \&reset_nicklist);
+Irssi::signal_add('nicklist changed', \&reset_nicklist);
+Irssi::signal_add_first('nick mode changed', \&reset_nicklist);
+Irssi::signal_add('gui exit', \&disable_nicklist);
+Irssi::signal_add_last('terminal resized', \&resized_timed);
+
+} else {
+my $fifo_path = $ARGV[0];
+my $irssi_pane = $ARGV[1];
+# array to store the current channel nicknames
+my @nicknames = ();
+
+# helper functions for manipulating the terminal
+# escape sequences taken from
+# http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
+sub enable_mouse { print "\e[?1000h"; }
+# recognized sequences
+my $MOUSE_SCROLL_DOWN="\e[Ma";
+my $MOUSE_SCROLL_UP="\e[M`";
+my $ARROW_DOWN="\e[B";
+my $ARROW_UP="\e[A";
+my $UP="k";
+my $DOWN="j";
+my $PAGE_DOWN="\e[6~";
+my $PAGE_UP="\e[5~";
+my $GO_TOP="gg";
+my $GO_BOTTOM="G";
+
+my $current_line = 0;
+my $sequence = '';
+my ($rows, $cols);
+
+sub term_size {
+ split ' ', `stty size`;
+}
+
+sub redraw {
+ my $last_nick_idx = @nicknames;
+ my $last_idx = $current_line + $rows;
+ # normalize last visible index
+ if ($last_idx > ($last_nick_idx)) {
+ $last_idx = $last_nick_idx;
+ }
+ # redraw visible nicks
+ for my $i (reverse 1..$rows) {
+ print "\e[$i;1H\e[K";
+ my $idx = $current_line + $i - 1;
+ if ($idx < $last_idx) {
+ my $z = 0; my $col = $cols;
+ for (split /(\e\[(?:\d|;|:|\?|\s)*.)/, $nicknames[$idx]) {
+ if ($z ^= 1) {
+ print +(substr $_, 0, $col) if $col > 0;
+ $col -= length;
+ } else {
+ print
+ }
+ }
+ }
+ }
+}
+
+sub move_down {
+ $sequence = '';
+ my $count = int $_[0];
+ my $nickcount = $#nicknames;
+ return if ($nickcount <= $rows);
+ if ($count == -1) {
+ $current_line = $nickcount - $rows - 1;
+ redraw;
+ return;
+ }
+ my $visible = $nickcount - $current_line - $count;
+ if ($visible > $rows) {
+ $current_line += $count;
+ redraw;
+ } elsif (($visible + $count) > $rows) {
+ # scroll the maximum we can
+ $current_line = $nickcount - $rows - 1;
+ redraw;
+ }
+}
+
+sub move_up {
+ $sequence = '';
+ my $count = int $_[0];
+ if ($count == -1) {
+ $current_line = 0;
+ redraw;
+ return;
+ }
+ return if ($current_line == 0);
+ $count = 1 if $count == 0;
+ $current_line -= $count;
+ $current_line = 0 if $current_line < 0;
+ redraw;
+}
+
+$SIG{INT} = 'IGNORE';
+
+STDOUT->autoflush(1);
+# setup terminal so we can listen for individual key presses without echo
+`stty -icanon -echo`;
+
+# open named pipe and setup the 'select' wrapper object for listening on both
+# fds(fifo and sdtin)
+open my $fifo, "<", $fifo_path or die "can't open $fifo_path: $!";
+my $select = IO::Select->new();
+my @ready;
+$select->add($fifo);
+$select->add(\*STDIN);
+
+enable_mouse;
+system('tput', 'smcup');
+print "\e[?7l"; #system('tput', 'rmam');
+system('tput', 'civis');
+MAIN: {
+ while (@ready = $select->can_read) {
+ foreach my $fd (@ready) {
+ ($rows, $cols) = term_size;
+ if ($fd == $fifo) {
+ while (<$fifo>) {
+ my $line = $_;
+ if ($line =~ /^BEGIN/) {
+ @nicknames = ();
+ } elsif ($line =~ /^SWITCH_CHANNEL/) {
+ $current_line = 0;
+ } elsif ($line =~ /^NICK(.+)$/) {
+ push @nicknames, $1;
+ } elsif ($line =~ /^END$/) {
+ redraw;
+ last;
+ } elsif ($line =~ /^EXIT$/) {
+ last MAIN;
+ }
+ }
+ } else {
+ my $key = '';
+ sysread(STDIN, $key, 1);
+ $sequence .= $key;
+ if ($MOUSE_SCROLL_DOWN =~ /^\Q$sequence\E/) {
+ if ($MOUSE_SCROLL_DOWN eq $sequence) {
+ move_down 3;
+ # mouse scroll has two more bytes that I dont use here
+ # so consume them now to avoid sending unwanted bytes to
+ # irssi
+ sysread(STDIN, $key, 2);
+ }
+ } elsif ($MOUSE_SCROLL_UP =~ /^\Q$sequence\E/) {
+ if ($MOUSE_SCROLL_UP eq $sequence) {
+ move_up 3;
+ sysread(STDIN, $key, 2);
+ }
+ } elsif ($ARROW_DOWN =~ /^\Q$sequence\E/) {
+ move_down 1 if ($ARROW_DOWN eq $sequence);
+ } elsif ($ARROW_UP =~ /^\Q$sequence\E/) {
+ move_up 1 if ($ARROW_UP eq $sequence);
+ } elsif ($DOWN =~ /^\Q$sequence\E/) {
+ move_down 1 if ($DOWN eq $sequence);
+ } elsif ($UP =~ /^\Q$sequence\E/) {
+ move_up 1 if ($UP eq $sequence);
+ } elsif ($PAGE_DOWN =~ /^\Q$sequence\E/) {
+ move_down $rows/2 if ($PAGE_DOWN eq $sequence);
+ } elsif ($PAGE_UP =~ /^\Q$sequence\E/) {
+ move_up $rows/2 if ($PAGE_UP eq $sequence);
+ } elsif ($GO_BOTTOM =~ /^\Q$sequence\E/) {
+ move_down -1 if ($GO_BOTTOM eq $sequence);
+ } elsif ($GO_TOP =~ /^\Q$sequence\E/) {
+ move_up -1 if ($GO_TOP eq $sequence);
+ } else {
+ # Unrecognized sequences will be send to irssi and its pane
+ # will be focused
+ system('tmux', 'send-keys', '-t', $irssi_pane, $sequence);
+ system('tmux', 'select-pane', '-t', $irssi_pane);
+ $sequence = '';
+ }
+ }
+ }
+ }
+}
+
+close $fifo;
+
+}
diff --git a/scripts/trackbar22.pl b/scripts/trackbar22.pl
new file mode 100644
index 0000000..30015a2
--- /dev/null
+++ b/scripts/trackbar22.pl
@@ -0,0 +1,500 @@
+# trackbar.pl
+#
+# This little script will do just one thing: it will draw a line each time you
+# switch away from a window. This way, you always know just upto where you've
+# been reading that window :) It also removes the previous drawn line, so you
+# don't see double lines.
+#
+# redraw trackbar only works on irssi 0.8.17 or higher.
+#
+#
+# Usage:
+#
+# The script works right out of the box, but if you want you can change
+# the working by /set'ing the following variables:
+#
+# Setting: trackbar_style
+# Description: This setting will be the color of your trackbar line.
+# By default the value will be '%K', only Irssi color
+# formats are allowed. If you don't know the color formats
+# by heart, you can take a look at the formats documentation.
+# You will find the proper docs on http://www.irssi.org/docs.
+#
+# Setting: trackbar_string
+# Description: This is the string that your line will display. This can
+# be multiple characters or just one. For example: '~-~-'
+# The default setting is '-'.
+#
+# Setting: trackbar_use_status_window
+# Description: If this setting is set to OFF, Irssi won't print a trackbar
+# in the statuswindow
+#
+# Setting: trackbar_ignore_windows
+# Description: A list of windows where no trackbar should be printed
+#
+# Setting: trackbar_print_timestamp
+# Description: If this setting is set to ON, Irssi will print the formatted
+# timestamp in front of the trackbar.
+#
+# Setting: trackbar_require_seen
+# Description: Only clear the trackbar if it has been scrolled to.
+#
+# /mark is a command that will redraw the line at the bottom.
+#
+# Command: /trackbar, /trackbar goto
+# Description: Jump to where the trackbar is, to pick up reading
+#
+# Command: /trackbar keep
+# Description: Keep this window's trackbar where it is the next time
+# you switch windows (then this flag is cleared again)
+#
+# Command: /mark, /trackbar mark
+# Description: Remove the old trackbar and mark the bottom of this
+# window with a new trackbar
+#
+# Command: /trackbar markvisible
+# Description: Like mark for all visible windows
+#
+# Command: /trackbar markall
+# Description: Like mark for all windows
+#
+# Command: /trackbar remove
+# Description: Remove this window's trackbar
+#
+# Command: /trackbar removeall
+# Description: Remove all windows' trackbars
+#
+# Command: /trackbar redraw
+# Description: Force redraw of trackbars
+#
+
+
+# For bugreports and other improvements contact one of the authors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this script; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+##
+
+use strict;
+use warnings;
+use Irssi;
+use Irssi::TextUI;
+use Encode;
+
+use POSIX qw(strftime);
+use vars qw($VERSION %IRSSI);
+
+$VERSION = "2.2"; # cb3189a33c8e5f9
+
+%IRSSI = (
+ authors => 'Peter Leurs and Geert Hauwaerts',
+ contact => 'peter@pfoe.be',
+ patchers => 'Johan Kiviniemi (UTF-8), Uwe Dudenhoeffer (on-upgrade-remove-line)',
+ name => 'trackbar',
+ description => 'Shows a bar where you have last read a window.',
+ license => 'GNU General Public License',
+ url => 'http://www.pfoe.be/~peter/trackbar/',
+ changed => 'Fri Jan 23 23:59:11 2004',
+ commands => 'trackbar',
+);
+
+## Comments and remarks.
+#
+# This script uses settings.
+# Use /SET to change the value or /TOGGLE to switch it on or off.
+#
+#
+# Tip: The command 'trackbar' is very usefull if you bind that to a key,
+# so you can easily jump to the trackbar. Please see 'help bind' for
+# more information about keybindings in Irssi.
+#
+# Command: /BIND meta2-P key F1
+# /BIND F1 command trackbar
+#
+##
+
+## Bugfixes and new items in this rewrite.
+#
+# * Remove all the trackbars before upgrading.
+# * New setting trackbar_use_status_window to control the statuswindow trackbar.
+# * New setting trackbar_print_timestamp to print a timestamp or not.
+# * New command 'trackbar' to scroll up to the trackbar.
+# * When resizing your terminal, Irssi will update all the trackbars to the new size.
+# * When changing trackbar settings, change all the trackbars to the new settings.
+# * New command 'trackbar mark' to draw a new trackbar (The old '/mark').
+# * New command 'trackbar markall' to draw a new trackbar in each window.
+# * New command 'trackbar remove' to remove the trackbar from the current window.
+# * New command 'trackbar removeall' to remove all the trackbars.
+# * Don't draw a trackbar in empty windows.
+# * Added a version check to prevent Irssi redraw errors.
+# * Fixed a bookmark NULL versus 0 bug.
+# * Fixed a remove-line bug in Uwe Dudenhoeffer his patch.
+# * New command 'help trackbar' to display the trackbar commands.
+# * Fixed an Irssi startup bug, now processing each auto-created window.
+#
+##
+
+## Known bugs and the todolist.
+#
+# Todo: * Instead of drawing a line, invert the line.
+#
+##
+
+sub cmd_help {
+ my ($args) = @_;
+ if ($args =~ /^trackbar *$/i) {
+ print CLIENTCRAP <<HELP
+%9Syntax:%9
+
+TRACKBAR
+TRACKBAR GOTO
+TRACKBAR KEEP
+TRACKBAR MARK
+TRACKBAR MARKVISIBLE
+TRACKBAR MARKALL
+TRACKBAR REMOVE
+TRACKBAR REMOVEALL
+TRACKBAR REDRAW
+
+%9Parameters:%9
+
+ GOTO: Jump to where the trackbar is, to pick up reading
+ KEEP: Keep this window's trackbar where it is the next time
+ you switch windows (then this flag is cleared again)
+ MARK: Remove the old trackbar and mark the bottom of this
+ window with a new trackbar
+ MARKVISIBLE: Like mark for all visible windows
+ MARKALL: Like mark for all windows
+ REMOVE: Remove this window's trackbar
+ REMOVEALL: Remove all windows' trackbars
+ REDRAW: Force redraw of trackbars
+
+%9Description:%9
+
+ Manage a trackbar. Without arguments, it will scroll up to the trackbar.
+
+%9Examples:%9
+
+ /TRACKBAR MARK
+ /TRACKBAR REMOVE
+HELP
+ }
+}
+
+Irssi::theme_register([
+ 'trackbar_loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.',
+ 'trackbar_wrong_version', '%R>>%n %_Trackbar:%_ Please upgrade your client to 0.8.17 or above if you would like to use this feature of trackbar.',
+ 'trackbar_all_removed', '%R>>%n %_Trackbar:%_ All the trackbars have been removed.',
+ 'trackbar_not_found', '%R>>%n %_Trackbar:%_ No trackbar found in this window.',
+]);
+
+my $old_irssi = Irssi::version < 20140701;
+sub check_version {
+ if ($old_irssi) {
+ Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_wrong_version');
+ return;
+ } else {
+ return 1;
+ }
+}
+
+sub is_utf8 {
+ lc Irssi::settings_get_str('term_charset') eq 'utf-8'
+}
+
+my (%config, %keep_trackbar, %unseen_trackbar);
+
+sub remove_one_trackbar {
+ my $win = shift;
+ my $view = shift || $win->view;
+ my $line = $view->get_bookmark('trackbar');
+ if (defined $line) {
+ my $bottom = $view->{bottom};
+ $view->remove_line($line);
+ $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
+ $view->redraw;
+ }
+}
+
+sub add_one_trackbar {
+ my $win = shift;
+ my $view = shift || $win->view;
+ $win->print(line($win->{width}), MSGLEVEL_NEVER);
+ $view->set_bookmark_bottom('trackbar');
+ $unseen_trackbar{ $win->{_irssi} } = 1;
+ $view->redraw;
+}
+
+sub update_one_trackbar {
+ my $win = shift;
+ my $view = shift || $win->view;
+ my $force = shift;
+ my $ignored = win_ignored($win, $view);
+ remove_one_trackbar($win, $view)
+ if $force || !defined $force || !$ignored;
+ add_one_trackbar($win, $view)
+ if $force || !$ignored;
+}
+
+sub win_ignored {
+ my $win = shift;
+ my $view = shift || $win->view;
+ return 1 unless $view->{buffer}{lines_count};
+ return 1 if $win->{name} eq '(status)' && !$config{use_status_window};
+ return 1 if grep { $win->{name} eq $_ || $win->{refnum} eq $_
+ || $win->get_active_name eq $_ } @{ $config{ignore_windows} };
+ return 0;
+}
+
+sub sig_window_changed {
+ my ($newwindow, $oldwindow) = @_;
+ return unless $oldwindow;
+ trackbar_update_seen($newwindow);
+ return if delete $keep_trackbar{ $oldwindow->{_irssi} };
+ trackbar_update_seen($oldwindow);
+ return if $config{require_seen} && $unseen_trackbar{ $oldwindow->{_irssi } };
+ update_one_trackbar($oldwindow, undef, 0);
+}
+
+sub trackbar_update_seen {
+ my $win = shift;
+ return unless $win;
+ my $view = $win->view;
+ my $line = $view->get_bookmark('trackbar');
+ unless ($line) {
+ delete $unseen_trackbar{ $win->{_irssi} };
+ return;
+ }
+ my $startline = $view->{startline};
+ return unless $startline;
+
+ if ($startline->{info}{time} < $line->{info}{time}
+ || $startline->{_irssi} == $line->{_irssi}) {
+ delete $unseen_trackbar{ $win->{_irssi} };
+ }
+}
+
+sub screen_length;
+{ local $@;
+ eval { require Text::CharWidth; };
+ unless ($@) {
+ *screen_length = sub { Text::CharWidth::mbswidth($_[0]) };
+ }
+ else {
+ *screen_length = sub {
+ my $temp = shift;
+ if (is_utf8()) {
+ Encode::_utf8_on($temp);
+ }
+ length($temp)
+ };
+ }
+}
+
+{ my %strip_table = (
+ (map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8I:|FnN>#[' . 'pP')),
+ (map { $_ => $_ } (split //, '{}%')),
+ );
+ sub c_length {
+ my $o = Irssi::strip_codes($_[0]);
+ $o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} :
+ $2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex;
+ screen_length($o)
+ }
+}
+
+sub line {
+ my ($width, $time) = @_;
+ my $string = $config{string};
+ $string = ' ' unless length $string;
+ $time ||= time;
+
+ Encode::_utf8_on($string);
+ my $length = c_length($string);
+
+ my $format = '';
+ if ($config{print_timestamp}) {
+ $format = $config{timestamp_str};
+ $format =~ y/%/\01/;
+ $format =~ s/\01\01/%/g;
+ $format = strftime($format, localtime $time);
+ $format =~ y/\01/%/;
+ }
+
+ my $times = $width / $length;
+ $times += 1 if $times != int $times;
+ $format .= $config{style};
+ $width -= c_length($format);
+ $string x= $times;
+ chop $string while length $string && c_length($string) > $width;
+ return $format . $string;
+}
+
+sub remove_all_trackbars {
+ for my $window (Irssi::windows) {
+ next unless ref $window;
+ remove_one_trackbar($window);
+ }
+}
+
+sub UNLOAD {
+ remove_all_trackbars();
+}
+
+sub redraw_trackbars {
+ return unless check_version();
+ for my $win (Irssi::windows) {
+ next unless ref $win;
+ my $view = $win->view;
+ my $line = $view->get_bookmark('trackbar');
+ next unless $line;
+ my $bottom = $view->{bottom};
+ $win->print_after($line, MSGLEVEL_NEVER, line($win->{width}, $line->{info}{time}),
+ $line->{info}{time});
+ $view->set_bookmark('trackbar', $win->last_line_insert);
+ $view->redraw;
+ $view->remove_line($line);
+ $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
+ $view->redraw;
+ }
+}
+
+sub goto_trackbar {
+ my $win = Irssi::active_win;
+ my $line = $win->view->get_bookmark('trackbar');
+
+ if ($line) {
+ $win->command("scrollback goto ". strftime("%d %H:%M:%S", localtime($line->{info}{time})));
+ } else {
+ $win->printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_not_found');
+ }
+}
+
+sub cmd_mark {
+ update_one_trackbar(Irssi::active_win, undef, 1);
+}
+
+sub cmd_markall {
+ for my $window (Irssi::windows) {
+ next unless ref $window;
+ update_one_trackbar($window);
+ }
+}
+
+sub signal_stop {
+ Irssi::signal_stop;
+}
+
+sub cmd_markvisible {
+ my @wins = Irssi::windows;
+ my $awin =
+ my $bwin = Irssi::active_win;
+ my $awin_counter = 0;
+ Irssi::signal_add_priority('window changed' => 'signal_stop', -99);
+ do {
+ Irssi::active_win->command('window up');
+ $awin = Irssi::active_win;
+ update_one_trackbar($awin);
+ ++$awin_counter;
+ } until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins);
+ Irssi::signal_remove('window changed' => 'signal_stop');
+}
+
+sub cmd_trackbar_remove_one {
+ remove_one_trackbar(Irssi::active_win);
+}
+
+sub cmd_remove_all_trackbars {
+ remove_all_trackbars();
+ Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_all_removed');
+}
+
+sub cmd_keep_once {
+ $keep_trackbar{ Irssi::active_win->{_irssi} } = 1;
+}
+
+sub trackbar_runsub {
+ my ($data, $server, $item) = @_;
+ $data =~ s/\s+$//g;
+
+ if ($data) {
+ Irssi::command_runsub('trackbar', $data, $server, $item);
+ } else {
+ goto_trackbar();
+ }
+}
+
+sub update_config {
+ my $was_status_window = $config{use_status_window};
+ $config{style} = Irssi::settings_get_str('trackbar_style');
+ $config{string} = Irssi::settings_get_str('trackbar_string');
+ $config{require_seen} = Irssi::settings_get_bool('trackbar_require_seen');
+ $config{ignore_windows} = [ split /[,\s]+/, Irssi::settings_get_str('trackbar_ignore_windows') ];
+ $config{use_status_window} = Irssi::settings_get_bool('trackbar_use_status_window');
+ $config{print_timestamp} = Irssi::settings_get_bool('trackbar_print_timestamp');
+ if (defined $was_status_window && $was_status_window != $config{use_status_window}) {
+ if (my $swin = Irssi::window_find_name('(status)')) {
+ if ($config{use_status_window}) {
+ update_one_trackbar($swin);
+ }
+ else {
+ remove_one_trackbar($swin);
+ }
+ }
+ }
+ if ($config{print_timestamp}) {
+ my $ts_format = Irssi::settings_get_str('timestamp_format');
+ my $ts_theme = Irssi::current_theme->get_format('fe-common/core', 'timestamp');
+ my $render_str = Irssi::current_theme->format_expand($ts_theme);
+ (my $ts_escaped = $ts_format) =~ s/([%\$])/$1$1/g;
+ $render_str =~ s/(?|\$(.)(?!\w)|\$\{(\w+)\})/$1 eq 'Z' ? $ts_escaped : $1/ge;
+ $config{timestamp_str} = $render_str;
+ }
+ redraw_trackbars() unless $old_irssi;
+}
+
+Irssi::settings_add_str('trackbar', 'trackbar_string', is_utf8() ? "\x{2500}" : '-');
+Irssi::settings_add_str('trackbar', 'trackbar_style', '%K');
+Irssi::settings_add_str('trackbar', 'trackbar_ignore_windows', '');
+Irssi::settings_add_bool('trackbar', 'trackbar_use_status_window', 1);
+Irssi::settings_add_bool('trackbar', 'trackbar_print_timestamp', 0);
+Irssi::settings_add_bool('trackbar', 'trackbar_require_seen', 0);
+
+update_config();
+
+Irssi::signal_add_last( 'mainwindow resized' => 'redraw_trackbars')
+ unless $old_irssi;
+
+Irssi::signal_register({'gui page scrolled' => [qw/Irssi::UI::Window/]});
+Irssi::signal_add_last('gui page scrolled' => 'trackbar_update_seen');
+
+Irssi::signal_add('setup changed' => 'update_config');
+Irssi::signal_add_priority('session save' => 'remove_all_trackbars', Irssi::SIGNAL_PRIORITY_HIGH-1);
+
+Irssi::signal_add('window changed' => 'sig_window_changed');
+
+Irssi::command_bind('trackbar goto' => 'goto_trackbar');
+Irssi::command_bind('trackbar keep' => 'cmd_keep_once');
+Irssi::command_bind('trackbar mark' => 'cmd_mark');
+Irssi::command_bind('trackbar markvisible' => 'cmd_markvisible');
+Irssi::command_bind('trackbar markall' => 'cmd_markall');
+Irssi::command_bind('trackbar remove' => 'cmd_trackbar_remove_one');
+Irssi::command_bind('trackbar removeall' => 'cmd_remove_all_trackbars');
+Irssi::command_bind('trackbar redraw' => 'redraw_trackbars');
+Irssi::command_bind('trackbar' => 'trackbar_runsub');
+Irssi::command_bind('mark' => 'cmd_mark');
+Irssi::command_bind_last('help' => 'cmd_help');
+
+Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_loaded', $IRSSI{name}, $VERSION, $IRSSI{authors});
diff --git a/scripts/typofix.pl b/scripts/typofix.pl
new file mode 100644
index 0000000..49b312a
--- /dev/null
+++ b/scripts/typofix.pl
@@ -0,0 +1,165 @@
+# typofix.pl - when someone uses s/foo/bar typofixing, this script really
+# goes and modifies the original text on screen.
+
+use strict;
+use warnings;
+use Irssi qw(
+ settings_get_str settings_get_bool
+ settings_add_str settings_add_bool
+ signal_add signal_stop
+);
+use Irssi 20140701;
+use Irssi::TextUI;
+use Algorithm::Diff 'sdiff';
+
+our $VERSION = '1.12'; # 4be3787e5717715
+our %IRSSI = (
+ authors => 'Juerd (first version: Timo Sirainen, additions by: Qrczak)',
+ contact => 'tss@iki.fi, juerd@juerd.nl, qrczak@knm.org.pl',
+ name => 'Typofix',
+ description => 'When someone uses s/foo/bar/, this really modifies the text',
+ license => 'Same as Irssi',
+ url => 'http://juerd.nl/irssi/',
+ changed => 'Sat Jun 28 16:24:26 CEST 2014',
+ upgrade_info => '/set typofix_modify_string %wold%gnew%n',
+ NOTE1 => 'you need irssi 0.8.17'
+);
+
+# /SET typofix_modify_string [fixed] - append string after replaced text
+# /SET typofix_hide_replace NO - hide the s/foo/bar/ line
+# (J) /SET typofix_format - format with "old" and "new" in it
+
+
+my $chars = '/|;:\'"_=+*&^%$#@!~,.?-';
+my $regex = qq{(?x-sm: # "s/foo/bar/i # oops"
+ \\s* # Optional whitespace
+ s # Substitution operator s
+ ([$chars]) # Delimiter /
+ ( (?: \\\\. | (?!\\1). )* ) # Pattern foo
+ # Backslash plus any char, or a single non-delimiter char
+ \\1 # Delimiter /
+ ( (?: \\\\. | (?!\\1). )* ) # Replacementstring bar
+ \\1? # Optional delimiter /
+ ([a-z]*) # Modifiers i
+ \\s* # Optional whitespace
+ (.?) # Don't hide if there's more # oops
+)};
+my $irssi_mumbo = qr/\cD[`-i]|\cD[&-@\xff]./;
+my $irssi_mumbo_no_partial = qr/(?<!\cD)(?<!\cD[&-@\xff])/;
+
+sub replace {
+ my ($window, $nick, $from, $to, $opt, $screen) = @_;
+
+ my $view = $window->view();
+ my $line = $screen ? $view->{bottom_startline} : $view->{startline};
+
+ my $last_line;
+ (my $copy = $from) =~ s/\^|^/^.*\\b$nick\\b.*?\\s.*?/;
+ while ($line) {
+ my $text = $line->get_text(0);
+ eval {
+ $last_line = $line
+ if ($line->{info}{level} & (MSGLEVEL_PUBLIC | MSGLEVEL_MSGS)) &&
+ $text !~ /$regex/o && $text =~ /$copy/;
+ 1
+ } or return;
+ $line = $line->next();
+ }
+ return 0 if (!$last_line);
+ my $text = $last_line->get_text(1);
+
+ # variables and case insensitivity
+ $from = "(?i:$from)" if $opt =~ /i/;
+ $to = quotemeta $to;
+ $to =~ s{\\\\\\(.)|\\(.)([1-9])?}{
+ if (defined $1) {
+ "\\$1"
+ } elsif (defined $3 && ($2 eq "\\" || $2 eq "\$")) {
+ "\$$3"
+ } else {
+ "\\$2".($3//"")
+ } }ge;
+
+ # text replacing
+ $text =~ s/(.*(?:\b|$irssi_mumbo)$irssi_mumbo_no_partial$nick(?:\b|$irssi_mumbo).*?\s)//;
+ my $pre = $1;
+ $text =~ s/$irssi_mumbo//g;
+ my $format = settings_get_str('typofix_format');
+ $format =~ s/old/\0\cA/;
+ $format =~ s/new/\0\cB/;
+ $format =~ s/%/\0\cC/g;
+
+ my $old = $text;
+ eval " \$text =~ s/\$from/$to/".($opt =~ /g/ ? "g" : "")." ; 1 "
+ or Irssi::print "Typofix warning: $@", return 0;
+ my $new = '';
+ my $diff = Algorithm::Diff->new([split//,$old],[split//,$text]);
+ while ($diff->Next()) {
+ local $" = '';
+ if (my @it = $diff->Same()) {
+ $new .= "@it";
+ }
+ else {
+ my %r = ("\cA" => [ $diff->Items(1) ],
+ "\cB" => [ $diff->Items(2) ]);
+ my $format_st = $format;
+ $format_st =~ s/\0([\cA\cB])/@{$r{$1}}/g;
+ $new .= $format_st;
+ }
+ }
+ s/%/%%/g for $pre, $new;
+ s/\0\cC/%/g for $new;
+ $text = $pre . $new . settings_get_str('typofix_modify_string');
+
+ my $bottom = $view->{bottom};
+ my $info = $last_line->{info};
+ $window->print_after($last_line, $info->{level}, $text, $info->{time});
+ $view->remove_line($last_line);
+ $window->command('^scrollback end') if $bottom && !$window->view->{bottom};
+ $view->redraw();
+
+ return 1;
+}
+
+sub event_privmsg {
+ my ($server, $data, $nick, $address) = @_;
+ my ($target, $text) = $data =~ /^(\S*)\s:(.*)/ or return;
+
+ return unless $text =~ /^$regex/o;
+ my ($from, $to, $opt, $extra) = ($2, $3, $4, $5);
+
+ my $hide = settings_get_bool('typofix_hide_replace') && !$extra;
+
+ my $ischannel = $server->ischannel($target);
+ my $level = $ischannel ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS;
+
+ $target = $nick unless $ischannel;
+ my $window = $server->window_find_closest($target, $level);
+
+ signal_stop() if (replace($window, $nick, $from, $to, $opt, 0) && $hide);
+}
+
+sub event_own_public {
+ my ($server, $text, $target) = @_;
+
+ return unless $text =~ /^$regex/o;
+ my ($from, $to, $opt, $extra) = ($2, $3, $4, $5);
+
+ my $hide = settings_get_bool('typofix_hide_replace') && !$extra;
+ $hide = 0 if settings_get_bool('typofix_own_no_hide');
+
+ my $level = $server->ischannel($target) ? MSGLEVEL_MSGS : MSGLEVEL_PUBLIC;
+ my $window = $server->window_find_closest($target, $level);
+
+ signal_stop() if (replace($window, $server->{nick}, $from, $to, $opt, 0) && $hide);
+}
+
+settings_add_str ('typofix', 'typofix_modify_string', ' [fixed]');
+settings_add_str ('typofix', 'typofix_format', '%rold%gnew%n');
+settings_add_bool('typofix', 'typofix_hide_replace', 0);
+settings_add_bool('typofix', 'typofix_own_no_hide', 0);
+
+signal_add {
+ 'event privmsg' => \&event_privmsg,
+ 'message own_public' => \&event_own_public
+};
diff --git a/scripts/uberprompt.pl b/scripts/uberprompt.pl
new file mode 100644
index 0000000..c4b8cc9
--- /dev/null
+++ b/scripts/uberprompt.pl
@@ -0,0 +1,787 @@
+=pod
+
+=head1 NAME
+
+uberprompt.pl
+
+=head1 DESCRIPTION
+
+This script replaces the default prompt status-bar item with one capable of
+displaying additional information, under either user control or via scripts.
+
+=head1 INSTALLATION
+
+Copy into your F<~/.irssi/scripts/> directory and load with
+C</SCRIPT LOAD F<filename>>.
+
+It is recommended that you make it autoload in one of the
+L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>.
+
+=head1 SETUP
+
+If you have a custom prompt format, you may need to copy it to the
+uberprompt_format setting. See below for details.
+
+=head1 USAGE
+
+Although the script is designed primarily for other scripts to set
+status information into the prompt, the following commands are available:
+
+=over 4
+
+=item * C</prompt set [-inner|-pre|-post|only] E<lt>msgE<gt>>
+
+Sets the prompt to the given argument. Any use of C<$p> in the argument will
+be replaced by the original prompt content.
+
+A parameter corresponding to the C<UP_*> constants listed below is required, in
+the format C</prompt set -inner Hello!>
+
+=item * C</prompt clear>
+
+Clears the additional data provided to the prompt.
+
+=item * C</prompt on>
+
+Eenables the uberprompt (things may get confused if this is used
+whilst the prompt is already enabled)
+
+=item * C</prompt off>
+
+Restore the original irssi prompt and prompt_empty statusbars. unloading the
+script has the same effect.
+
+=item * C</help prompt>
+
+show help for uberprompt commands
+
+=back
+
+=head1 SETTINGS
+
+=head2 UBERPROMPT FORMAT
+
+C</set uberprompt_format E<lt>formatE<gt>>
+
+The default is C<[$*$uber]>, which is the same as the default provided in
+F<default.theme>.
+
+Changing this setting will update the prompt immediately, unlike editing your theme.
+
+An additional variable available within this format is C<$uber>, which expands to
+the content of prompt data provided with the C<UP_INNER> or C</prompt set -inner>
+placement argument.
+
+For all other placement arguments, it will expand to the empty string.
+
+B<Note:> This setting completely overrides the C<prompt="...";> line in your
+.theme file, and may cause unexpected behaviour if your theme wishes to set a
+different form of prompt. It can be simply copied from the theme file into the
+above setting.
+
+=head2 OTHER SETTINGS
+
+=over 4
+
+=item * C<uberprompt_autostart>
+
+Boolean value, which determines if uberprompt should enable itself automatically
+upon loading. If Off, it must be enabled manually with C</prompt on>. Defaults to On.
+
+=item * C<uberprompt_debug>
+
+Boolean value, which determines if uberprompt should print debugging information.
+Defaults to Off, and should probably be left that way unless requested for bug-tracing
+purposes.
+
+=item * C<uberprompt_format>
+
+String value containing the format-string which uberprompt uses to display the
+prompt. Defaults to "C<[$*$uber] >", where C<$*> is the content the prompt would
+normally display, and C<$uber> is a placeholder variable for dynamic content, as
+described in the section above.
+
+=item * C<uberprompt_load_hook>
+
+String value which can contain one or more commands to be run whenever the uberprompt
+is enabled, either via autostart, or C</prompt on>. Defaults to the empty string, in
+which case no commands are run. Some examples include:
+
+C</set uberprompt_load_hook /echo prompt enabled> or
+
+C</^sbar prompt add -after input vim_mode> for those using vim_mode.pl who want
+the command status indicator on the prompt line.
+
+=item * C<uberprompt_unload_hook>
+
+String value, defaulting to the empty string, which can contain commands which
+are executed when the uberprompt is disabled, either by unloading the script,
+or by the command C</prompt off>.
+
+=item * C<uberprompt_use_replaces>
+
+Boolean value, defaults to Off. If enabled, the format string for the prompt
+will be subject to the I<replaces> section of the theme. The most obvious
+effect of this is that bracket characters C<[ ]> are displayed in a different
+colour, typically quite dark.
+
+=back
+
+B<Note:> For both C<uberprompt_*_hook> settings above, multiple commands can
+be chained together in the form C</eval /^cmd1 ; /^cmd2>. The C<^> prevents
+any output from the commands (such as error messages) being displayed.
+
+=head2 SCRIPTING USAGE
+
+The primary purpose of uberprompt is to be used by other scripts to
+display information in a way that is not possible by printing to the active
+window or using statusbar items.
+
+The content of the prompt can be set from other scripts via the C<"change prompt">
+signal.
+
+For Example:
+
+ signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER;
+
+will set the prompt to include that content, by default 'C<[$* some_string]>'
+
+The possible position arguments are the following strings:
+
+=over 4
+
+=item * C<UP_PRE> - place the provided string before the prompt - C<$string$prompt>
+
+=item * C<UP_INNER> - place the provided string inside the prompt - C<{prompt $* $string}>
+
+=item * C<UP_POST> - place the provided string after the prompt - C<$prompt$string>
+
+=item * C<UP_ONLY> - replace the prompt with the provided string - C<$string>
+
+=back
+
+All strings may use the special variable 'C<$prompt>' to include the prompt
+verbatim at that position in the string. It is probably only useful for
+the C<UP_ONLY> mode however. '$C<prompt_nt>' will include the prompt, minus any
+trailing whitespace.
+
+=head2 CHANGE NOTIFICATIONS
+
+You can also be notified when the prompt changes in response to the previous
+signal or manual C</prompt> commands via:
+
+ signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... };
+
+This callback will occur whenever the contents of the prompt is changed.
+
+
+=head2 NOTES FOR SCRIPT WRITERS:
+
+The following code snippet can be used within your own script as a preamble
+to ensure that uberprompt is loaded before your script to avoid
+any issues with loading order. It first checks if uberprompt is loaded, and
+if not, attempts to load it. If the load fails, the script will die
+with an error message, otherwise it will call your app_init() function.
+
+I<---- start of snippet ---->
+
+ my $DEBUG_ENABLED = 0;
+ sub DEBUG () { $DEBUG_ENABLED }
+
+ # check we have uberprompt loaded.
+
+ sub script_is_loaded {
+ return exists($Irssi::Script::{$_[0] . '::'});
+ }
+
+ if (not script_is_loaded('uberprompt')) {
+
+ print "This script requires 'uberprompt.pl' in order to work. "
+ . "Attempting to load it now...";
+
+ Irssi::signal_add('script error', 'load_uberprompt_failed');
+ Irssi::command("script load uberprompt.pl");
+
+ unless(script_is_loaded('uberprompt')) {
+ load_uberprompt_failed("File does not exist");
+ }
+ app_init();
+ } else {
+ app_init();
+ }
+
+ sub load_uberprompt_failed {
+ Irssi::signal_remove('script error', 'load_uberprompt_failed');
+
+ print "Script could not be loaded. Script cannot continue. "
+ . "Check you have uberprompt.pl installed in your path and "
+ . "try again.";
+
+ die "Script Load Failed: " . join(" ", @_);
+ }
+
+I<---- end of snippet ---->
+
+=head1 AUTHORS
+
+Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>>
+
+=head1 LICENCE
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+=head1 BUGS
+
+=over 4
+
+=item *
+
+Resizing the terminal rapidly whilst using this script in debug mode may cause
+irssi to crash. See bug report at http://bugs.irssi.org/index.php?do=details&task_id=772 for details.
+
+=back
+
+=head1 TODO
+
+=over 4
+
+=item * report failure (somehow) to clients if hte prompt is disabled.
+
+=item * fix issue at autorun startup with sbar item doesn't exist.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use Irssi;
+use Irssi::TextUI;
+use Data::Dumper;
+
+{ package Irssi::Nick } # magic.
+
+our $VERSION = "0.2"; # 255b35bb44161e0
+our %IRSSI =
+ (
+ authors => "shabble",
+ contact => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode',
+ name => "uberprompt",
+ description => "Helper script for dynamically adding text "
+ . "into the input-bar prompt.",
+ license => "MIT",
+ changed => "24/7/2010"
+ );
+
+
+my $DEBUG_ENABLED = 0;
+sub DEBUG { $DEBUG_ENABLED }
+
+my $prompt_data = '';
+my $prompt_data_pos = 'UP_INNER';
+
+my $prompt_last = '';
+my $prompt_format = '';
+my $prompt_format_empty = '';
+
+# flag to indicate whether rendering of hte prompt should allow the replaces
+# theme formats to be applied to the content.
+my $use_replaces = 0;
+my $trim_data = 0;
+
+my $emit_request = 0;
+
+my $expando_refresh_timer;
+my $expando_vars = {};
+
+my $init_callbacks = {load => '', unload => ''};
+
+pre_init();
+
+sub pre_init {
+ `stty -ixon`;
+ init();
+}
+
+sub prompt_subcmd_handler {
+ my ($data, $server, $item) = @_;
+ #$data =~ s/\s+$//g; # strip trailing whitespace.
+ Irssi::command_runsub('prompt', $data, $server, $item);
+}
+
+sub _error($) {
+ my ($msg) = @_;
+ Irssi::active_win->print($msg, MSGLEVEL_CLIENTERROR);
+}
+
+sub _debug_print($) {
+ return unless DEBUG;
+ my ($msg) = @_;
+ Irssi::active_win->print($msg, MSGLEVEL_CLIENTCRAP);
+}
+
+sub _print_help {
+ my ($args) = @_;
+ if ($args =~ m/^\s*prompt/i) {
+ my @help_lines =
+ (
+ "",
+ "PROMPT ON",
+ "PROMPT OFF",
+ "PROMPT CLEAR",
+ "PROMPT SET { -pre | -post | -only | -inner } <content>",
+ "",
+ "Commands for manipulating the UberPrompt.",
+ "",
+ "/PROMPT ON enables uberprompt, replacing the existing prompt ",
+ " statusbar-item",
+ "/PROMPT OFF disables it, and restores the original prompt item",
+ "/PROMPT CLEAR resets the value of any additional data set by /PROMPT SET",
+ " or a script",
+ "/PROMPT SET changes the contents of the prompt, according to the mode",
+ " and content provided.",
+ " { -inner sets the value of the \$uber psuedo-variable in the",
+ " /set uberprompt_format setting.",
+ " | -pre places the content before the current prompt string",
+ " | -post places the content after the prompt string",
+ " | -only replaces the entire prompt contents with the given string }",
+ "",
+ "See Also:",
+ '',
+ '/SET uberprompt_format -- defaults to "[$*$uber] "',
+ '/SET uberprompt_format_empty -- defaults to "[$*] "',
+ "/SET uberprompt_autostart -- determines whether /PROMPT ON is run",
+ " automatically when the script loads",
+ "/SET uberprompt_use_replaces -- toggles the use of the current theme",
+ " \"replaces\" setting. Especially",
+ " noticeable on brackets \"[ ]\" ",
+ "/SET uberprompt_trim_data -- defaults to off. Removes whitespace from",
+ " both sides of the \$uber result.",
+ "",
+ );
+
+ Irssi::print($_, MSGLEVEL_CLIENTCRAP) for @help_lines;
+ Irssi::signal_stop;
+ }
+}
+
+sub UNLOAD {
+ deinit();
+}
+
+sub exp_lbrace() { '{' }
+sub exp_rbrace() { '}' }
+
+sub deinit {
+ Irssi::expando_destroy('lbrace');
+ Irssi::expando_destroy('rbrace');
+
+ # remove uberprompt and return the original ones.
+ #print "Removing uberprompt and restoring original";
+ restore_prompt_items();
+}
+
+sub gui_exit {
+ restore_prompt_items();
+}
+
+sub init {
+ Irssi::statusbar_item_register('uberprompt', 0, 'uberprompt_draw');
+
+ # TODO: flags to prevent these from being recomputed?
+ Irssi::expando_create('lbrace', \&exp_lbrace, {});
+ Irssi::expando_create('rbrace', \&exp_rbrace, {});
+
+ Irssi::settings_add_str ('uberprompt', 'uberprompt_format', '[$*$uber] ');
+ Irssi::settings_add_str ('uberprompt', 'uberprompt_format_empty', '[$*] ');
+
+ Irssi::settings_add_str ('uberprompt', 'uberprompt_load_hook', '');
+ Irssi::settings_add_str ('uberprompt', 'uberprompt_unload_hook', '');
+
+ Irssi::settings_add_bool('uberprompt', 'uberprompt_debug', 0);
+ Irssi::settings_add_bool('uberprompt', 'uberprompt_autostart', 1);
+
+ Irssi::settings_add_bool('uberprompt', 'uberprompt_use_replaces', 0);
+ Irssi::settings_add_bool('uberprompt', 'uberprompt_trim_data', 0);
+
+ Irssi::command_bind("prompt", \&prompt_subcmd_handler);
+ Irssi::command_bind('prompt on', \&replace_prompt_items);
+ Irssi::command_bind('prompt off', \&restore_prompt_items);
+ Irssi::command_bind('prompt set', \&cmd_prompt_set);
+ Irssi::command_bind('prompt clear',
+ sub {
+ Irssi::signal_emit 'change prompt', '$p', 'UP_POST';
+ });
+
+ my $prompt_set_args_format = "inner pre post only";
+ Irssi::command_set_options('prompt set', $prompt_set_args_format);
+
+ Irssi::command_bind('help', \&_print_help);
+
+ Irssi::signal_add_first('gui exit', \&gui_exit);
+ Irssi::signal_add('setup changed', \&reload_settings);
+
+ # intialise the prompt format.
+ reload_settings();
+
+ # make sure we redraw when necessary.
+ Irssi::signal_add('window changed', \&uberprompt_refresh);
+ Irssi::signal_add('window name changed', \&uberprompt_refresh);
+ Irssi::signal_add('window changed automatic', \&uberprompt_refresh);
+ Irssi::signal_add('window item changed', \&uberprompt_refresh);
+ Irssi::signal_add('window item server changed', \&uberprompt_refresh);
+ Irssi::signal_add('window server changed', \&uberprompt_refresh);
+ Irssi::signal_add('server nick changed', \&uberprompt_refresh);
+
+ Irssi::signal_add('nick mode changed', \&refresh_if_me);
+
+ # install our statusbars if required.
+ if (Irssi::settings_get_bool('uberprompt_autostart')) {
+ replace_prompt_items();
+ }
+
+ # API signals
+
+ Irssi::signal_register({'change prompt' => [qw/string string/]});
+ Irssi::signal_add('change prompt' => \&change_prompt_handler);
+
+ # other scripts (specifically overlay/visual) can subscribe to
+ # this event to be notified when the prompt changes.
+ # arguments are new contents (string), new length (int)
+ Irssi::signal_register({'prompt changed' => [qw/string int/]});
+ Irssi::signal_register({'prompt length request' => []});
+
+ Irssi::signal_add('prompt length request', \&length_request_handler);
+}
+
+sub cmd_prompt_set {
+ my $args = shift;
+ my @options_list = Irssi::command_parse_options "prompt set", $args;
+ if (@options_list) {
+ my ($options, $rest) = @options_list;
+
+ my @opt_modes = keys %$options;
+ if (@opt_modes != 1) {
+ _error '%_/prompt set%_ must specify exactly one mode of'
+ . ' {-inner, -only, -pre, -post}';
+ return;
+ }
+
+ my $mode = 'UP_' . uc($opt_modes[0]);
+
+ Irssi::signal_emit 'change prompt', $rest, $mode;
+ } else {
+ _error ('%_/prompt set%_ must specify a mode {-inner, -only, -pre, -post}');
+ }
+}
+
+sub refresh_if_me {
+ my ($channel, $nick) = @_;
+
+ return unless $channel and $nick;
+
+ my $server = Irssi::active_server;
+ my $window = Irssi::active_win;
+
+ return unless $server and $window;
+
+ my $my_chan = $window->{active}->{name};
+ my $my_nick = $server->parse_special('$N');
+
+ return unless $my_chan and $my_nick;
+
+ _debug_print "Chan: $channel->{name}, "
+ . "nick: $nick->{nick}, "
+ . "me: $my_nick, chan: $my_chan";
+
+ if ($my_chan eq $channel->{name} and $my_nick eq $nick->{nick}) {
+ uberprompt_refresh();
+ }
+}
+
+sub length_request_handler {
+ $emit_request = 1;
+ uberprompt_render_prompt();
+ $emit_request = 0;
+}
+
+sub reload_settings {
+
+ $use_replaces = Irssi::settings_get_bool('uberprompt_use_replaces');
+ $DEBUG_ENABLED = Irssi::settings_get_bool('uberprompt_debug');
+
+ $init_callbacks = {
+ load => Irssi::settings_get_str('uberprompt_load_hook'),
+ unload => Irssi::settings_get_str('uberprompt_unload_hook'),
+ };
+
+ if (DEBUG) {
+ Irssi::signal_add 'prompt changed', 'debug_prompt_changed';
+ } else {
+ Irssi::signal_remove 'prompt changed', 'debug_prompt_changed';
+ }
+
+ my $new = Irssi::settings_get_str('uberprompt_format');
+
+ if ($prompt_format ne $new) {
+ _debug_print("Updated prompt format");
+ $prompt_format = $new;
+ $prompt_format =~ s/\$uber/\$\$uber/;
+ Irssi::abstracts_register(['uberprompt', $prompt_format]);
+
+ $expando_vars = {};
+
+ # TODO: something clever here to check if we need to schedule
+ # an update timer or something, rather than just refreshing on
+ # every possible activity in init()
+ while ($prompt_format =~ m/(?<!\$)(\$[A-Za-z,.:;][a-z_]*)/g) {
+ _debug_print("Detected Irssi expando variable $1");
+ my $var_name = substr $1, 1; # strip the $
+ $expando_vars->{$var_name} = Irssi::parse_special($1);
+ }
+ }
+
+ $new = Irssi::settings_get_str('uberprompt_format_empty');
+
+ if ($prompt_format_empty ne $new) {
+ _debug_print("Updated prompt format");
+ $prompt_format_empty = $new;
+ $prompt_format_empty =~ s/\$uber/\$\$uber/;
+ Irssi::abstracts_register(['uberprompt_empty', $prompt_format_empty]);
+
+ $expando_vars = {};
+
+ # TODO: something clever here to check if we need to schedule
+ # an update timer or something, rather than just refreshing on
+ # every possible activity in init()
+ while ($prompt_format_empty =~ m/(?<!\$)(\$[A-Za-z,.:;][a-z_]*)/g) {
+ _debug_print("Detected Irssi expando variable $1");
+ my $var_name = substr $1, 1; # strip the $
+ $expando_vars->{$var_name} = Irssi::parse_special($1);
+ }
+ }
+
+ $trim_data = Irssi::settings_get_bool('uberprompt_trim_data');
+}
+
+sub debug_prompt_changed {
+ my ($text, $len) = @_;
+
+ $text =~ s/%/%%/g;
+
+ print "DEBUG_HANDLER: Prompt Changed to: \"$text\", length: $len";
+}
+
+sub change_prompt_handler {
+ my ($text, $pos) = @_;
+
+ # fix for people who used prompt_info and hence the signal won't
+ # pass the second argument.
+ $pos = 'UP_INNER' unless defined $pos;
+ _debug_print("Got prompt change signal with: $text, $pos");
+
+ my ($changed_text, $changed_pos);
+ $changed_text = defined $prompt_data ? $prompt_data ne $text : 1;
+ $changed_pos = defined $prompt_data_pos ? $prompt_data_pos ne $pos : 1;
+
+ $prompt_data = $text;
+ $prompt_data_pos = $pos;
+
+ if ($changed_text || $changed_pos) {
+ _debug_print("Redrawing prompt");
+ uberprompt_refresh();
+ }
+}
+
+sub _escape_prompt_special {
+ my $str = shift;
+ $str =~ s/\$/\$\$/g;
+ $str =~ s/\\/\\\\/g;
+ #$str =~ s/%/%%/g;
+ $str =~ s/{/\${lbrace}/g;
+ $str =~ s/}/\${rbrace}/g;
+
+ return $str;
+}
+
+sub uberprompt_render_prompt {
+
+ my $window = Irssi::active_win;
+ my $prompt_arg = '';
+
+ # default prompt sbar arguments (from config)
+ if (scalar( () = $window->items )) {
+ $prompt_arg = '$[.15]{itemname}';
+ } else {
+ $prompt_arg = '${winname}';
+ }
+
+ my $prompt = ''; # rendered content of the prompt.
+ my $theme = Irssi::current_theme;
+
+ my $arg = $use_replaces ? 0 : Irssi::EXPAND_FLAG_IGNORE_REPLACES;
+
+ if ($prompt_data && (!$trim_data || trim($prompt_data))) {
+ $prompt = $theme->format_expand("{uberprompt $prompt_arg}", $arg);
+ }
+ else {
+ $prompt = $theme->format_expand("{uberprompt_empty $prompt_arg}", $arg);
+ }
+
+ if ($prompt_data_pos eq 'UP_ONLY') {
+ $prompt =~ s/\$\$uber//; # no need for recursive prompting, I hope.
+
+ # TODO: only compute this if necessary?
+ my $prompt_nt = $prompt;
+ $prompt_nt =~ s/\s+$//;
+
+ my $pdata_copy = $prompt_data;
+
+ $pdata_copy =~ s/\$prompt_nt/$prompt_nt/;
+ $pdata_copy =~ s/\$prompt/$prompt/;
+
+ $prompt = $pdata_copy;
+
+ } elsif ($prompt_data_pos eq 'UP_INNER' && defined $prompt_data) {
+
+ my $esc = _escape_prompt_special($prompt_data);
+ $esc = $trim_data ? trim($esc) : $esc;
+ $prompt =~ s/\$\$uber/$esc/;
+
+ } else {
+ # remove the $uber marker
+ $prompt =~ s/\$\$uber//;
+
+ # and add the additional text at the appropriate position.
+ if ($prompt_data_pos eq 'UP_PRE') {
+ $prompt = $prompt_data . $prompt;
+ } elsif ($prompt_data_pos eq 'UP_POST') {
+ $prompt .= $prompt_data;
+ }
+ }
+
+ _debug_print("rendering with: $prompt");
+
+
+ if (($prompt ne $prompt_last) or $emit_request) {
+
+ # _debug_print("Emitting prompt changed signal");
+ # my $exp = Irssi::current_theme()->format_expand($text, 0);
+ my $ps = Irssi::parse_special($prompt);
+
+ Irssi::signal_emit('prompt changed', $ps, length($ps));
+ $prompt_last = $prompt;
+ }
+ return $prompt;
+}
+
+sub uberprompt_draw {
+ my ($sb_item, $get_size_only) = @_;
+
+ my $prompt = uberprompt_render_prompt();
+
+ my $ret = $sb_item->default_handler($get_size_only, $prompt, '', 0);
+ _debug_print("redrawing with: $prompt");
+ return $ret;
+}
+
+sub uberprompt_refresh {
+ Irssi::statusbar_items_redraw('uberprompt');
+}
+
+my $prompt_items_replaced;
+
+sub replace_prompt_items {
+ unless ($prompt_items_replaced) {
+ $prompt_items_replaced = 1;
+
+ # add the new one.
+ _sbar_command('prompt', 'add', 'uberprompt',
+ qw/-alignment left -after prompt_empty -priority '-1'/);
+
+ # remove existing ones.
+ _debug_print("Removing original prompt");
+
+ _sbar_command('prompt', 'remove', 'prompt');
+ _sbar_command('prompt', 'remove', 'prompt_empty');
+
+ }
+
+ my $load_hook = $init_callbacks->{load};
+ if (defined $load_hook and length $load_hook) {
+ eval {
+ Irssi::command($load_hook);
+ };
+ if ($@) {
+ _error("Uberprompt user load-hook command ($load_hook) failed: $@");
+ }
+ }
+
+}
+
+sub restore_prompt_items {
+ if ($prompt_items_replaced) {
+ $prompt_items_replaced = undef;
+
+ _debug_print("Restoring original prompt");
+
+ _sbar_command('prompt', 'add', 'prompt',
+ qw/-alignment left -after uberprompt -priority '-1'/);
+ _sbar_command('prompt', 'add', 'prompt_empty',
+ qw/-alignment left -after prompt -priority '-1'/);
+
+ _sbar_command('prompt', 'remove', 'uberprompt');
+
+ }
+
+ my $unload_hook = $init_callbacks->{unload};
+
+ if (defined $unload_hook and length $unload_hook) {
+ eval {
+ Irssi::command($unload_hook);
+ };
+ if ($@) {
+ _error("Uberprompt user unload-hook command ($unload_hook) failed: $@");
+ }
+ }
+}
+
+sub _sbar_command {
+ my ($bar, $cmd, $item, @args) = @_;
+
+ my $args_str = join ' ', @args;
+
+ $args_str .= ' ' if length $args_str && defined $item;
+
+ my $command = sprintf 'STATUSBAR %s %s %s%s',
+ $bar, $cmd, $args_str, defined $item ? $item : '';
+
+ _debug_print("Running command: $command");
+ Irssi::command($command);
+}
+
+sub trim {
+ my $string = shift;
+
+ $string =~ s/^\s*//;
+ $string =~ s/\s*$//;
+
+ return $string;
+}