diff options
| author | dequis | 2015-11-16 19:36:52 -0300 |
|---|---|---|
| committer | dequis | 2015-11-23 13:59:24 -0300 |
| commit | 6dec1974e3ac2e71c26e842887a4b8fe2ec9082c (patch) | |
| tree | 95aeae48906c69305ae7d0afd123876ff908050b | |
| parent | 2bc8e3368c0b842f1fe6fad069915a4bb90b1fa2 (diff) | |
| download | scripts.irssi.org-6dec1974e3ac2e71c26e842887a4b8fe2ec9082c.tar.bz2 | |
Add all non-nei scripts from nei's website
Some are mostly nei or nei variants, but not originally authored by nei
If any of the authors doesn't want to have these in scripts.irssi.org,
complain and we'll remove it.
| -rw-r--r-- | scripts/aspell.pl | 725 | ||||
| -rw-r--r-- | scripts/cmpchans.pl | 64 | ||||
| -rw-r--r-- | scripts/hlscroll.pl | 83 | ||||
| -rw-r--r-- | scripts/ido_switcher.pl | 1166 | ||||
| -rw-r--r-- | scripts/ircuwhois.pl | 84 | ||||
| -rw-r--r-- | scripts/messages_bottom.pl | 29 | ||||
| -rw-r--r-- | scripts/mouse-awl.pl | 144 | ||||
| -rw-r--r-- | scripts/mouse_soliton.pl | 146 | ||||
| -rw-r--r-- | scripts/nickcolor_gay.pl | 83 | ||||
| -rw-r--r-- | scripts/recentdepart.pl | 332 | ||||
| -rw-r--r-- | scripts/sb_position.pl | 112 | ||||
| -rw-r--r-- | scripts/tmux-nicklist-portable.pl | 390 | ||||
| -rw-r--r-- | scripts/trackbar22.pl | 500 | ||||
| -rw-r--r-- | scripts/typofix.pl | 165 | ||||
| -rw-r--r-- | scripts/uberprompt.pl | 787 |
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; +} |
