aboutsummaryrefslogblamecommitdiffstats
path: root/ido-mode/ido_switcher.pl
blob: e1b684acb69c95aa0c6d200d9a26ba908d336b5e (plain) (tree)















































                                                                               

                                                                               












                                                                                        

                                                  
                                                                 
 
                        
                       
 
                          
 
                        
                        
 
                    
                     

                    
                   
 


                            



                               





                                   

                                  
            

                                
                    

                                             
 
                  
                                




                                                  
                              











                                                             

 

                      
                                                        







                                                         
 
                                                                    


                                                                
                                                
 


                                                      
                      


                            
                                                                   
                                                                 




                                                                  



                                                                  
 
                                                                







                                                                    


                                                                   


 

                                                
                                                 





                                                 

                            
 

                                                               
                        
 
                                                                    

                    
 

                     

 



                                      






                                        




                                                        
                                                         

                         
                                 

                                       



                                                              
                                                      
                                                             

                             
                

                                                                          

         
 
                                                 
                

 
                       



                                                                   
                                                       
 
                                        
                                                                                


                                                                    

 

                           
 

                   

                                              

                                                        
 









                                                            
                                                           






                                                             




                                                                            

                                     
     


                                                             
 
                                       
                                                              
                                   

 





                                 
                    

                                  




                                               
                             




                                                   
            




                                                     






                                
                                       

                


                                
                               
                                               








                        







                                          
                                                    







                                        
                                                    

 

                                         




                       


                                     
                                                 








                                        








                                          







                                        







                                          


                             




                                             

                                 
                                                               

                                              


                             



                              
                                                       
                          

                     
                        



                                                                         
                               


                                                       
                          
                        





                             
                                                
                          







                                                     
                                                                    
         
 

                         






                                                 




                           








                                              

                                 

                         




                             

                            
 


                  
# 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.
#
# USAGE:
#
# * Setup: /bind ^R /history_search_start
#
# * Then type ctrl-R and type what you're searching for
#
# * You can cycle through multiple matches with ^R (older matches), and
#   ^S (newer matches)
#
# 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

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 ido-mode like search interface',
   license     => 'GPLv2 or later',
   url         => 'http://github.com/shabble/irssi-scripts/tree/master/history-search/',
   changed     => '24/7/2010'
  );


# TODO:
# C-g - cancel
# C-spc - narrow
# flex matching (on by default, but optional)
# server/network narrowing
# colourised output (via uberprompt)
# C-r / C-s rotate matches
# toggle queries/channels
# remove inputline content, restore it afterwards.
# tab - display all possibilities in window (clean up afterwards)

my $input_copy     = '';
my $input_pos_copy = 0;

my $ido_switch_active = 0;

my @window_cache   = ();
my @search_matches = ();

my $match_index = 0;
my $search_str  = '';
my $active_only = 0;

my $need_clear = 0;

# /set configurable settings
my $ido_show_count;
my $ido_use_flex;

my $DEBUG_ENABLED = 0;
sub DEBUG () { $DEBUG_ENABLED }

sub MODE_A () { 0 } # all
sub MODE_Q () { 1 } # queries
sub MODE_C () { 2 } # channels
sub MODE_S () { 3 } # select server
sub MODE_W () { 4 } # select window

# 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 {
    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 print_all_matches {
    my $msg = join(", ", map { $_->{name} } @search_matches);
    # $msg =~ s/(.{80}.*?,)/$1\n/g;
    # my @lines = split "\n", $msg;
    # foreach my $line (@lines) {
    #     _print($line);
    # }
    _print($msg);
}

sub script_is_loaded {
    my $name = shift;
    _debug_print "Checking if $name is loaded" if DEBUG;
    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_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');
}


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;

    # 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(", ", @window_cache) if DEBUG;

    _update_cache();

    update_matches();
    update_prompt();
}

sub _update_cache {
    @window_cache = get_all_windows();
}

sub get_all_windows {
    my @ret;

    foreach my $win (Irssi::windows()) {
        my @items = $win->items();

        if ($win->{name} ne '') {
            push @ret, {
                        name   => $win->{name},
                        type   => 'WINDOW',
                        num    => $win->{refnum},
                        server => $win->{active_server},
                        active => $win->{data_level} > 0,
                       };

        } elsif (scalar @items) {
            foreach my $item (@items) {
                push @ret, {
                            name     => $item->{visible_name},
                            type     => $item->{type},
                            server   => $item->{server},
                            num      => $win->{refnum},
                            itemname => $item->{name},
                            active => $win->{data_level} > 0,
                           };
            }
        } else {
            #_debug_print "Error occurred reading info from window: $win";
            #_debug_print Dumper($win);
        }
    }

    @ret = sort { $a->{num} <=> $b->{num} } @ret;
    return @ret;
}

sub ido_switch_select {
    my ($selected, $is_refnum) = @_;

    _debug_print "Selecting window: " . $selected->{name} if DEBUG;

    Irssi::command("WINDOW GOTO " . $selected->{name});

    if ($selected->{type} ne 'WINDOW') {
        _debug_print "Selecting window item: " . $selected->{itemname} if DEBUG;
        Irssi::command("WINDOW ITEM GOTO " . $selected->{itemname});
    }

}

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 update_prompt {

    # take the top $ido_show_count entries and display them.
    my $match_num = scalar @search_matches;
    my $show_num = $ido_show_count;
    my $show_str = '(no matches) ';

    $show_num = $match_num if $match_num < $show_num;

    if ($show_num > 0) {
        _debug_print "Showing: $show_num matches" if DEBUG;

        my @ordered_matches
         = @search_matches[$match_index .. $#search_matches,
                           0            .. $match_index - 1];

        my @show = @ordered_matches[0..$show_num - 1];

        $show[0] = sprintf '%%g%d:%s%%n', $show[0]->{num}, $show[0]->{name};
        @show[1..$#show] = map
          {
              sprintf '%%r%d:%s%%n', $_->{num}, $_->{name};
          } @show[1..$#show];

        $show_str = join ', ', @show;
    }
    my $flex = sprintf(' [%s]', $ido_use_flex ? 'F' : 'E');
    my $search = '';
    $search = ' `' . $search_str . "'" if length $search_str;

    Irssi::signal_emit('change prompt',
                       $flex . $search . ' win: ' . $show_str,
                       'UP_INNER');
}

sub _check_active {
    my ($obj) = @_;
    return 1 unless $active_only;
    return $obj->{active};
}

sub update_matches {

    if ($search_str =~ m/^\d+$/) {
        @search_matches =
          grep {
              _check_active($_)
                and  $_->{num} == 0+$search_str
          } @window_cache;
    } elsif ($ido_use_flex) {
        @search_matches =
          grep {
              _check_active($_) and
                flex_match($search_str, $_->{name})
          } @window_cache;
    } else {
        @search_matches =
          grep {
              _check_active($_)
                and $_->{name} =~ m/\Q$search_str\E/i
          } @window_cache;
    }

}

sub flex_match {
    my ($pattern, $source) = @_;

    my @chars = split '', lc($pattern);
    my $i = -1;
    my $ret = 1;

    my $lc_source = lc($source);

    foreach my $char (@chars) {
        my $pos = index($lc_source, $char, $i);
        if ($pos > -1) {
            $i = $pos;
        } else {
            $ret = 0;
            last;
        }
    }

    return $ret;
}

sub prev_match {

    $match_index++;
    if ($match_index > $#search_matches) {
        $match_index = 0;
    }
    _debug_print "index now: $match_index" if DEBUG;
}

sub next_match {

    $match_index--;
    if ($match_index < 0) {
        $match_index = $#search_matches;
    }
    _debug_print "index now: $match_index" if DEBUG;
}

sub get_window_match {
    return $search_matches[$match_index];
}

sub handle_keypress {
	my ($key) = @_;

    return unless $ido_switch_active;

    if ($key == 0) { # C-SPC?
        _debug_print "\%_Ctrl-space\%_" if DEBUG;

        $search_str = '';
        @window_cache = @search_matches;
        update_prompt();

        Irssi::signal_stop();
        return;
    }

    if ($key == 3) { # C-C
        # $search_str = '';
        # @window_cache = @search_matches;
        # update_prompt();
        _print_clear();
        Irssi::signal_stop();
        return;
    }

    if ($key == 5) { # C-e
        $active_only = not $active_only;
        Irssi::signal_stop();
        update_matches();
        update_prompt();
        return;
    }

    if ($key == 6) { # C-f

        $ido_use_flex = not $ido_use_flex;
        _update_cache();

        update_matches();
        update_prompt();

        Irssi::signal_stop();
        return;
    }
    if ($key == 9) { # TAB
        _debug_print "Tab complete" if DEBUG;
        print_all_matches();
        Irssi::signal_stop();
    }

	if ($key == 10) { # enter
        _debug_print "selecting history and quitting" if DEBUG;
        my $selected_win = get_window_match();
        ido_switch_select($selected_win);

        ido_switch_exit();
        Irssi::signal_stop();
        return;
	}

    if ($key == 18) { # Ctrl-R
        _debug_print "skipping to prev match" if DEBUG;
        #update_matches();
        next_match();

        update_prompt();
        Irssi::signal_stop(); # prevent the bind from being re-triggered.
        return;
    }

    if ($key == 19) {  # Ctrl-S
        _debug_print "skipping to next match" if DEBUG;
        prev_match();

        #update_matches();
        update_prompt();

        Irssi::signal_stop();
        return;
    }

    if ($key == 7) { # Ctrl-G
        _debug_print "aborting search" if DEBUG;
        ido_switch_exit();
        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" if DEBUG;
        }

        update_matches();
        update_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);
        Irssi::signal_stop();

        return;
    }

    if ($key > 32) { # printable
        $search_str .= chr($key);

        update_matches();
        update_prompt();

        Irssi::signal_stop();
        return;
    }

    # ignore all other keys.
    Irssi::signal_stop();
}

ido_switch_init();