diff options
Diffstat (limited to 'scripts/ido_switcher.pl')
| -rw-r--r-- | scripts/ido_switcher.pl | 1166 | 
1 files changed, 1166 insertions, 0 deletions
| 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'); + +} | 
