diff options
| -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; +} | 
