summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/aspell.pl725
-rw-r--r--scripts/cmpchans.pl64
-rw-r--r--scripts/hlscroll.pl83
-rw-r--r--scripts/ido_switcher.pl1166
-rw-r--r--scripts/ircuwhois.pl84
-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_gay.pl83
-rw-r--r--scripts/recentdepart.pl332
-rw-r--r--scripts/sb_position.pl112
-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
15 files changed, 4810 insertions, 0 deletions
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/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/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/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_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/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/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;
+}