From d01a9573ada4b6a20ed506d10f0286754e738b08 Mon Sep 17 00:00:00 2001 From: Tom Feist Date: Thu, 30 Dec 2010 01:08:18 +0000 Subject: ido_switch: general refactor of code for more DRY. Beginning of full listing available with . --- ido-mode/ido_switcher.pl | 755 +++++++++++++++++++++++++++++------------------ 1 file changed, 471 insertions(+), 284 deletions(-) diff --git a/ido-mode/ido_switcher.pl b/ido-mode/ido_switcher.pl index 3a6a99c..eca29ff 100644 --- a/ido-mode/ido_switcher.pl +++ b/ido-mode/ido_switcher.pl @@ -44,7 +44,7 @@ $VERSION = '2.0'; 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', + description => 'Select window[-items] using an ido-mode like search interface', license => 'GPLv2 or later', url => 'http://github.com/shabble/irssi-scripts/tree/master/history-search/', changed => '24/7/2010' @@ -63,11 +63,13 @@ $VERSION = '2.0'; # 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 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 $ido_switch_active = 0; # for intercepting keystrokes my @window_cache = (); my @search_matches = (); @@ -76,6 +78,9 @@ my $match_index = 0; my $search_str = ''; my $active_only = 0; +my $mode_type = 'ALL'; +my @mode_cache; + my $need_clear = 0; my $sort_ordering = "start-asc"; @@ -87,12 +92,15 @@ my $ido_use_flex; my $DEBUG_ENABLED = 0; sub DEBUG () { $DEBUG_ENABLED } -#sub MODE_A () { 0 } # all -#sub MODE_Q () { 1 } # queries + +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 { @@ -117,23 +125,59 @@ sub _print_clear { sub print_all_matches { my $msg = join(", ", map { $_->{name} } @search_matches); - _print($msg); -} + 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; -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'; + 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; - return $retval; + _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..."; + . "Attempting to load it now..."; Irssi::signal_add('script error', 'load_uberprompt_failed'); Irssi::command("script load uberprompt.pl"); @@ -147,7 +191,7 @@ unless (script_is_loaded('uberprompt')) { 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 " + . "Check you have uberprompt.pl installed in your path and " . "try again."; die "Script Load Failed: " . join(" ", @_); } @@ -183,6 +227,7 @@ sub ido_switch_start { $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'); @@ -193,13 +238,44 @@ sub ido_switch_start { _update_cache(); update_matches(); - update_prompt(); + 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; @@ -208,394 +284,505 @@ sub get_all_windows { if ($win->{name} ne '') { _debug_print "Adding window: " . $win->{name}; - push @ret, { - name => $win->{name}, - type => 'WINDOW', - num => $win->{refnum}, - server => $win->{active_server}, - active => $win->{data_level} > 0, - b_pos => -1, - e_pos => -1, - hilight_field => 'name', - }; + push @ret, { _build_win_obj($win, undef) }; } - if (scalar @items) { foreach my $item (@items) { _debug_print "Adding windowitem: " . $item->{visible_name}; - - push @ret, { - name => $item->{visible_name}, - type => $item->{type}, - server => $item->{server}, - num => $win->{refnum}, - itemname => $item->{name}, - active => $win->{data_level} > 0, - b_pos => -1, - e_pos => -1, - hilight_field => 'name', - }; + push @ret, { _build_win_obj($win, $item) }; } } else { - #_debug_print "Error occurred reading info from window: $win"; + _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; + sub _sort_windows { + my $list_ref = shift; + my @ret = @$list_ref; - @ret = sort { $a->{num} <=> $b->{num} } @ret; + @ret = sort { $a->{num} <=> $b->{num} } @ret; - return @ret; -} + return @ret; + } -sub ido_switch_select { - my ($selected, $is_refnum) = @_; + sub ido_switch_select { + my ($selected, $is_refnum) = @_; - _debug_print "Selecting window: " . $selected->{name}; + _debug_print "Selecting window: " . $selected->{name}; - Irssi::command("WINDOW GOTO " . $selected->{name}); + Irssi::command("WINDOW GOTO " . $selected->{name}); + + if ($selected->{type} ne 'WINDOW') { + _debug_print "Selecting window item: " . $selected->{itemname}; + Irssi::command("WINDOW ITEM GOTO " . $selected->{itemname}); + } - if ($selected->{type} ne 'WINDOW') { - _debug_print "Selecting window item: " . $selected->{itemname}; - Irssi::command("WINDOW ITEM GOTO " . $selected->{itemname}); } -} + sub ido_switch_exit { + $ido_switch_active = 0; -sub ido_switch_exit { - $ido_switch_active = 0; + _print_clear(); - _print_clear(); + Irssi::gui_input_set($input_copy); + Irssi::gui_input_set_pos($input_pos_copy); + Irssi::signal_emit('change prompt', '', 'UP_INNER'); + } - 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 _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 %uniq; + # determine which items are non-unique, if any. + foreach my $res (@ordered_matches) { + 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; + } + } -sub update_prompt { + # + my @display = @ordered_matches[0..$show_count - 1]; - #TODO: refactor this beast. + # show the first entry in green - # 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) '; + my $first = shift @display; + my $formatted_first = _format_display_entry($first, '%g'); + unshift @display, $formatted_first; - $show_count = $match_count if $match_count < $show_count; + # and array-slice-map the rest to be red. + # or yellow, if they have unviewed activity - if ($show_count > 0) { - _debug_print "Showing: $show_count matches"; + @display[1..$#display] + = map + { + _format_display_entry($_, $_->{active}?'%y':'%r') - my @ordered_matches = _order_matches(@search_matches); - my @display = @ordered_matches[0..$show_count - 1]; + } @display[1..$#display]; + + # join em all up + $match_string = join ', ', @display; + } - # show the first entry in green + my @indicators; - unshift(@display, _format_display_entry(shift(@display), '%g')); + # 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)); - # and array-slice-map the rest to be red. - @display[1..$#display] - = map { _format_display_entry($_, '%r') } @display[1..$#display]; + my $flex = sprintf(' %%k[%%n%s%%k]%%n ', join ',', @indicators); - # join em all up - $match_string = join ', ', @display; + my $search = ''; + $search = (sprintf '`%s\': ', $search_str) if length $search_str; + + Irssi::signal_emit('change prompt', $flex . $search . $match_string, + 'UP_INNER'); } - # indicator if flex mode is being used (C-f to toggle) - my @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; + sub _format_display_entry { + my ($obj, $colour) = @_; - Irssi::signal_emit('change prompt', $flex . $search . $match_string, - 'UP_INNER'); -} + my $field = $obj->{hilight_field}; + my $hilighted = { name => $obj->{name}, num => $obj->{num} }; + my $show_tag = $obj->{display_net} || 0; -sub _format_display_entry { - my ($obj, $colour) = @_; + 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} + } - my $field = $obj->{hilight_field}; - my $hilighted = { name => $obj->{name}, num => $obj->{num} }; + return sprintf('%s%s:%s%s%%n', + $colour, + $hilighted->{num}, + $show_tag ? _format_display_tag($obj) : '', + $hilighted->{name}); + } - 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} + 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 ''; } - return sprintf('%s%s:%s%%n', $colour, $hilighted->{num}, $hilighted->{name}); -} + sub _check_active { + my ($obj) = @_; + return 1 unless $active_only; + return $obj->{active}; + } -sub _check_active { - my ($obj) = @_; - return 1 unless $active_only; - return $obj->{active}; -} + sub update_matches { -sub update_matches { + _update_cache() unless $search_str; - _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+$/) { + if ($search_str =~ m/^\d+$/) { - @search_matches = - grep { - _check_active($_) and regex_match($_, 'num') - } @window_cache; + @search_matches = + grep { + _check_active($_) and regex_match($_, 'num') + } @window_cache; - } elsif ($ido_use_flex) { + } elsif ($ido_use_flex) { - @search_matches = - grep { - _check_active($_) and flex_match($_) >= 0 - } @window_cache; + @search_matches = + grep { + _check_active($_) and flex_match($_) >= 0 + } @window_cache; - } else { + } else { - @search_matches = - grep { - _check_active($_) and regex_match($_, 'name') - } @window_cache; - } + @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; + 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; } - return 0; -} -sub flex_match { - my ($obj) = @_; + sub flex_match { + my ($obj) = @_; - my $pattern = $search_str; - my $source = $obj->{name}; + my $pattern = $search_str; + my $source = $obj->{name}; - _debug_print "Flex match: $pattern / $source"; + _debug_print "Flex match: $pattern / $source"; - # default to matching everything if we don't have a pattern to compare - # against. + # default to matching everything if we don't have a pattern to compare + # against. - return 0 unless $pattern; + return 0 unless $pattern; - my @chars = split '', lc($pattern); - my $ret = -1; - my $first = 0; + my @chars = split '', lc($pattern); + my $ret = -1; + my $first = 0; - my $lc_source = lc($source); + my $lc_source = lc($source); - $obj->{hilight_field} = 'name'; + $obj->{hilight_field} = 'name'; - foreach my $char (@chars) { - my $pos = index($lc_source, $char, $ret); - if ($pos > -1) { + 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; + # 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; + _debug_print("matched: $char at $pos in $source"); + $ret = $pos + 1; - } else { + } else { - $obj->{b_pos} = $obj->{e_pos} = -1; - _debug_print "Flex returning: -1"; + $obj->{b_pos} = $obj->{e_pos} = -1; + _debug_print "Flex returning: -1"; - return -1; + return -1; + } } - } - _debug_print "Flex returning: $ret"; + _debug_print "Flex returning: $ret"; - #store the end of the match. - $obj->{e_pos} = $ret; + #store the end of the match. + $obj->{e_pos} = $ret; - return $ret; -} + return $ret; + } -sub prev_match { + sub prev_match { - $match_index++; - if ($match_index > $#search_matches) { - $match_index = 0; + $match_index++; + if ($match_index > $#search_matches) { + $match_index = 0; + } + + _debug_print "index now: $match_index"; } - _debug_print "index now: $match_index"; -} + sub next_match { -sub next_match { + $match_index--; + if ($match_index < 0) { + $match_index = $#search_matches; + } + _debug_print "index now: $match_index"; + } - $match_index--; - if ($match_index < 0) { - $match_index = $#search_matches; + sub get_window_match { + return $search_matches[$match_index]; } - _debug_print "index now: $match_index"; -} -sub get_window_match { - return $search_matches[$match_index]; -} + sub handle_keypress { + my ($key) = @_; -sub handle_keypress { - my ($key) = @_; + return unless $ido_switch_active; - return unless $ido_switch_active; + if ($key == 0) { # C-SPC? + _debug_print "\%_Ctrl-space\%_"; - if ($key == 0) { # C-SPC? - _debug_print "\%_Ctrl-space\%_"; + $search_str = ''; + @window_cache = @search_matches; + update_window_select_prompt(); - $search_str = ''; - @window_cache = @search_matches; - update_prompt(); + Irssi::signal_stop(); + return; + } - 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 == 3) { # C-C - _print_clear(); - 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 == 5) { # C-e - $active_only = not $active_only; - Irssi::signal_stop(); - update_matches(); - update_prompt(); - return; - } + if ($key == 6) { # C-f - if ($key == 6) { # C-f + $ido_use_flex = not $ido_use_flex; + _update_cache(); - $ido_use_flex = not $ido_use_flex; - _update_cache(); + update_matches(); + update_window_select_prompt(); - update_matches(); - update_prompt(); + Irssi::signal_stop(); + return; + } + if ($key == 9) { # TAB + _debug_print "Tab complete"; + print_all_matches(); + Irssi::signal_stop(); + } - 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); - 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; + } - ido_switch_exit(); - Irssi::signal_stop(); - return; - } + if ($key == 18) { # Ctrl-R + _debug_print "skipping to prev match"; + #update_matches(); + next_match(); - 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; + } - update_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(); + if ($key == 19) { # Ctrl-s + _debug_print "skipping to next match"; + prev_match(); - #update_matches(); - update_prompt(); + #update_matches(); + update_window_select_prompt(); - Irssi::signal_stop(); - return; - } + Irssi::signal_stop(); + return; + } - if ($key == 7) { # Ctrl-G - _debug_print "aborting search"; - ido_switch_exit(); - Irssi::signal_stop(); - return; - } + if ($key == 7) { # Ctrl-g + _debug_print "aborting search"; + ido_switch_exit(); + Irssi::signal_stop(); + return; + } - if ($key == 21) { # Ctrl-U - $search_str = ''; - update_matches(); - update_prompt(); + if ($key == 21) { # Ctrl-u + $search_str = ''; + update_matches(); + update_window_select_prompt(); - Irssi::signal_stop(); - return; + 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 ($key == 127) { # DEL + update_matches(); + update_window_select_prompt(); - if (length $search_str) { - $search_str = substr($search_str, 0, -1); - _debug_print "Deleting char, now: $search_str"; + Irssi::signal_stop(); + return; } - update_matches(); - update_prompt(); + # 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_window_select_prompt(); + + Irssi::signal_stop(); + return; + } + # ignore all other keys. Irssi::signal_stop(); - return; } - # TODO: handle esc- sequences and arrow-keys? + ido_switch_init(); - if ($key == 27) { # Esc - ido_switch_exit(); - return; - } + sub update_network_select_prompt { - if ($key == 32) { # space - my $selected_win = get_window_match(); - ido_switch_select($selected_win); - Irssi::signal_stop(); + my @servers = map + { + { + name => $_->{tag}, + type => 'SERVER', + active => 0, + e_pos => -1, + b_pos => -1, + hilight_field => 'name', + } + } Irssi::servers(); - return; - } + my $match_count = scalar @servers; + my $show_count = $ido_show_count; + my $match_string = '(no matches) '; - if ($key > 32) { # printable - $search_str .= chr($key); + $show_count = $match_count if $match_count < $show_count; - update_matches(); - update_prompt(); + if ($show_count > 0) { + _debug_print "Showing: $show_count matches"; - Irssi::signal_stop(); - return; - } + my @ordered_matches = _order_matches(@servers); + my @display = @ordered_matches[0..$show_count - 1]; - # ignore all other keys. - Irssi::signal_stop(); -} + # 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') -ido_switch_init(); + } @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'); + + } -- cgit v1.2.3