summaryrefslogtreecommitdiffstats
path: root/scripts/ido_switcher.pl
diff options
context:
space:
mode:
authordequis2015-11-16 19:36:52 -0300
committerdequis2015-11-23 13:59:24 -0300
commit6dec1974e3ac2e71c26e842887a4b8fe2ec9082c (patch)
tree95aeae48906c69305ae7d0afd123876ff908050b /scripts/ido_switcher.pl
parent2bc8e3368c0b842f1fe6fad069915a4bb90b1fa2 (diff)
downloadscripts.irssi.org-6dec1974e3ac2e71c26e842887a4b8fe2ec9082c.tar.bz2
Add all non-nei scripts from nei's website
Some are mostly nei or nei variants, but not originally authored by nei If any of the authors doesn't want to have these in scripts.irssi.org, complain and we'll remove it.
Diffstat (limited to 'scripts/ido_switcher.pl')
-rw-r--r--scripts/ido_switcher.pl1166
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');
+
+}