aboutsummaryrefslogblamecommitdiffstats
path: root/ido-mode/ido_switcher.pl
blob: 91da08b0dcbd3aaaaa1bbeb9eb6f98525155b786 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11










                                                                               
        
 
                                     
 
                                                       
 



                                                                               






                                                                               







                                                              
                                           




                                                                         





                                                                                 
 









                                                                                   
















                                                                            






                                                                   










                                                                        
                                 
                                                                                  
                                   
                                                                                  




                             





                                                  
                              



                                                                         


                                                                                     
                                 
 
                        
                       
 
                                                             
 
                        
                        
 
                    
                     

                    

                      
                     
 
                   
 
                                
                          
 


                            



                               


                                


                                    
 

                    

                                  
            

                                
                    

                                             
 
                  
                        
                                




                                                  
                              



                                                         
































                                                                                       

                                                             




















                                                                               
 










                                                       
 


                                            

 









                                                           
                                         
 
                                                                          
                                       

                                                                
                                                
 


                                                      
                      


                            
                                                                   
                                                                 
                                                                    



                                               
                     



                                                                       
 
                                                                







                                                                    



                                                                           


 

                                                
                                                 





                                                 

                            
                               
 

                                                               
                        
 
                                                                              

                    
 
                     
                                  

 



                                      






























                                                        






                                        
                                                          
                                                       
         
                            
                                       
                                                                           
                                                           
             
                





                                                                          
                                       

         


                                
 

 


                             
 



                                                          
 



                                        
     
 
                           
                                  
 

                                                                  
 
                                                          
 
                                         


                                                                           
 
                         

     

                               
 
                       
 



                                                            
 



                                                   
 













                                                                  

                                                               
                                                           



                                        

















                                                                          
 
                                           
 


                                                                      
 

                                                       
 



                                                                   
 




                                                
 
                       
 



                                                              
 
                                                                        
 




                                                                            
     
 
 
 

                                
 


                                                                     
 




                                                                     
 





                                                                  
 







                                             
     
 




                                     
 
                        
 
                                           
 





                                                                                                         
 
                                      
 



                                                              
 
                                 
 



                                                           
 
                
 




                                                               
 
     
 








                                                                
     
 

                       
 

                                   
 
                                                      
 

                                                                              
 
                                 
 


                                           
 
                                    
 
                                       
 


                                                     
 


                                                   
 

                                                                  
 
                    
 

                                                   
 

                          
         
 
                                            
 

                                    
 

                    
 
                    
 





                                               
     
 
                    
 





                                               
 

                                             
     
 

                         
 
                                         
 





                                 

                                            
 


                                            
 


                                 
 









                                           
 






                                            
 
                                     
 

                                              
 

                                          
 







                                        
 



                                                          
 



                                 









                                                           
 



                                                  
 



                                                                             
 











                                             
 


                                                  
 

                                          
 


                                 
 




                                           





                                        
         
 



                                          
 

                                 
 







                                                               
 

                                          
 

                                 
         
 









                                                     



                                          













                                           
 
                                
                             

     
                      
 
                                      
 










                                          
 


                                           
 
                                                                 
 

                                                        
 

                                                               
 








                                                                            
 
                                       
 


















                                                                            
# Search and select windows similar to ido-mode for emacs
#
# INSTALL:
#
# This script requires that you have first installed and loaded 'uberprompt.pl'
# Uberprompt can be downloaded from:
#
# http://github.com/shabble/irssi-scripts/raw/master/prompt_info/uberprompt.pl
#
# and follow the instructions at the top of that file for installation.
#
# SETUP:
#
# * Setup: /bind ^G /ido_switch_start
#
# * Then type ctrl-G and type what you're searching for
#
# USAGE:
#
# C-g (or whatever you've set the above bind to), enters window switching mode.
#
# NB: 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-g, Esc,
# or RET.

# The following key-bindings are available only once the mode has been
# activated:
#
# * C-g   - cancel out of the mode without changing windows.
# * Esc   - cancel out, as above.
# * C-s   - rotate the list of window candidates forward by 1
# * C-r   - rotate the list of window candidates backward by 1
# * C-e   - Toggle 'Active windows only' filter
# * C-f   - Switch between 'Flex' and 'Exact' matching.
# * C-d   - Select a network or server to filter candidates by
# * C-u   - Clear the current search string
# * C-q   - Cycle between showing only queries, channels, or all.
# * C-SPC - Filter candidates by current search string, and then reset
#            the search string
# * RET   - Select the current head of the candidate list (the green one)
# * 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.
# * TAB   - [currently in development] 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.
#
# USAGE NOTES:
#
# * Using C-e (show actives), followed by repeatedly pressing space will cycle
#   through all your currently active windows.
#
# * 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.
#
# Based in part on window_switcher.pl script Copyright 2007 Wouter Coekaerts
# <coekie@irssi.org>
#
# 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

# BUGS:
#
# * Sometimes selecting a channel with the same name on a different
#   network will take you to the wrong channel.
#
#
#
use strict;
use Irssi;
use Irssi::TextUI;
use Data::Dumper;

use vars qw($VERSION %IRSSI);
$VERSION = '2.0';
%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'
  );


# TODO:
# DONE C-g - cancel
# DONE C-spc - narrow
# DONE flex matching (on by default, but optional)
# TODO server/network narrowing
# DONE colourised output (via uberprompt)
# DONE C-r / C-s rotate matches
# DONE toggle queries/channels
# DONE remove inputline content, restore it afterwards.
# TODO tab - display all possibilities in window (clean up afterwards)
#       how exactly will this work?
# DONE sort by recent activity/recently used windows (separate commands?)
# TODO need to be able to switch ordering of active ones (numerical, or most recently
#      active, priority to PMs/hilights, etc?)
# DONE should space auto-move forward to next window for easy stepping through
#      sequential/active windows?

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 $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.

sub _print {
    my $win = Irssi::active_win;
    my $str = join('', @_);
    $need_clear = 1;
    $win->print($str, Irssi::MSGLEVEL_NEVER);
}

sub _debug_print {
    return unless DEBUG;
    my $win = Irssi::active_win;
    my $str = join('', @_);
    $win->print($str, Irssi::MSGLEVEL_CLIENTCRAP);
}

sub _print_clear {
    return unless $need_clear;
    my $win = Irssi::active_win();
    $win->command('/scrollback levelclear -level NEVER');
}

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 \'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%_   - [%_currently non-functional%_] 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 $msg = join(", ", map { $_->{name} } @search_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;

    for (@search_matches) {
        my $len = length($_->{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) {
        my $name = $item->{name};
        push @line, sprintf('%.*s', $col_width, $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");
}

  sub script_is_loaded {
      my $name = shift;
      _debug_print "Checking if $name is loaded";
      no strict 'refs';
      my $retval = defined %{ "Irssi::Script::${name}::" };
      use strict 'refs';

      return $retval;
  }

unless (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");
    }
    ido_switch_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(" ", @_);
}

sub ido_switch_init {
    Irssi::settings_add_bool('ido_switch', 'ido_switch_debug',      0);
    Irssi::settings_add_bool('ido_switch', 'ido_use_flex',          1);
    Irssi::settings_add_bool('ido_switch', 'ido_show_active_first', 1);
    Irssi::settings_add_int ('ido_switch', 'ido_show_count',        5);

    Irssi::command_bind('ido_switch_start', \&ido_switch_start);

    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      = Irssi::settings_get_bool('ido_use_flex');
    $sort_active_first = Irssi::settings_get_bool('ido_show_active_first');
}


sub ido_switch_start {
    # 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('');

    # set startup flags
    $ido_switch_active = 1;
    $search_str        = '';
    $match_index       = 0;
    $mode_type         = 'ALL';

    # refresh in case we toggled it last time.
    $ido_use_flex   = Irssi::settings_get_bool('ido_use_flex');
    $active_only    = 0;

    _debug_print "Win cache: " . join(", ", map { $_->{name} } @window_cache);

    _update_cache();

    update_matches();
    update_window_select_prompt();
}

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,

               )
    } 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) = @_;

        _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};
            Irssi::command("WINDOW ITEM GOTO " . $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 match';

        $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}) {
                    push @{$uniq{$name}}, $res;
                } else {
                    $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 ? 'Flex' : 'Exact';
        push @indicators, 'Active' if $active_only;
        push @indicators, ucfirst(lc($mode_type));

        my $flex = sprintf(' %%k[%%n%s%%k]%%n ', join ',', @indicators);

        my $search = '';
        $search = (sprintf '`%s\': ', $search_str) if length $search_str;

        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 = { 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%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 {

        _update_cache() unless $search_str;

        if ($mode_type ne 'ALL') {
            @mode_cache = @window_cache;
            @window_cache = grep { print "Type: " . $_->{type}; $_->{type} eq $mode_type } @window_cache;
        } else {
            @window_cache = @mode_cache if @mode_cache;
        }

        if ($search_str =~ m/^\d+$/) {

            @search_matches =
              grep {
                  _check_active($_) and regex_match($_, 'num')
              } @window_cache;

        } elsif ($ido_use_flex) {

            @search_matches =
              grep {
                  _check_active($_) and flex_match($_) >= 0
              } @window_cache;

        } else {

            @search_matches =
              grep {
                  _check_active($_) and regex_match($_, 'name')
              } @window_cache;
        }

    }

    sub regex_match {
        my ($obj, $field) = @_;
        if ($obj->{$field} =~ m/^(.*?)\Q$search_str\E(.*?)$/i) {
            $obj->{hilight_field} = $field;
            $obj->{b_pos} = length $1;
            $obj->{e_pos} = $obj->{b_pos} + length($search_str);
            return 1;
        }
        return 0;
    }

    sub flex_match {
        my ($obj) = @_;

        my $pattern = $search_str;
        my $source  = $obj->{name};

        _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} = 'name';

        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 = not $ido_use_flex;
            _update_cache();

            update_matches();
            update_window_select_prompt();

            Irssi::signal_stop();
            return;
        }
        if ($key == 9) {        # TAB
            _debug_print "Tab complete";
            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 ? 'Flex' : 'Exact';
        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;

        Irssi::signal_emit('change prompt', $flex . $search . $match_string,
                           'UP_INNER');

    }