diff options
| author | Tom Feist <shabble@metavore.org> | 2011-04-22 02:27:12 +0000 | 
|---|---|---|
| committer | Tom Feist <shabble@metavore.org> | 2011-04-22 02:27:12 +0000 | 
| commit | fe6e50a76dba36899782fdda0f97590ee5f02a3c (patch) | |
| tree | a8ae7406048f00807ba54aecb5dfcd9c0dd08944 | |
| parent | removed docs/ from dev branch, since they're all in their own repo (well, wiki) (diff) | |
| parent | feature-tests/key_sig: test to see if there's any useful info in the 'keyboard (diff) | |
| download | irssi-scripts-fe6e50a76dba36899782fdda0f97590ee5f02a3c.tar.gz irssi-scripts-fe6e50a76dba36899782fdda0f97590ee5f02a3c.zip | |
Merge branch 'master' into dev
95 files changed, 9717 insertions, 806 deletions
| @@ -1,2 +1,7 @@  /test/irssi/*  /nightfrog +/modules/*/*.so +/modules/*/*.o +/vim-mode/TAGS +/testing/irssi-test.log +/vim-mode/irssi/scripts/autorun/README.pod diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b351d8e --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +SCRIPT_DIRS = \ +		history-search \ +		ido-mode \ +		prompt_info \ +		quit-notify \ +		sb-position \ +		scrolled-reminder \ +		tinyurl-tabcomplete \ +		vim-mode + +#		act_hide \ +#		auto-server \ +#		colour-popup \ +#		feature-tests \ +#		joinforward \ +#		masshilight \ +#		modules \ +#		no-key-modes \ +#		patches \ +#		testing \ +#		throttled-autojoin \ +#		undo \ +#		url_hilight \ + +SCRIPT_FILES = 	$(foreach FOO, $(SCRIPT_DIRS), $(wildcard $(FOO)/*.pl)) + +$GENERATOR = "./readme_generator" + +.PHONY: all clean rebuild + +all: + +	echo making all: $(SCRIPT_DIRS) +	echo files are: $(SCRIPT_FILES) + +rebuild: clean all + +clean: +	-echo cleaning. + +README.pod: +	-echo stuff @@ -0,0 +1,112 @@ +# Shabble's Irssi Scripts + +This repository contains a collection of scripts I have written or adapted from +others to improve my (and your!) irssi using experience. + +## What's In Here + +I never thought I'd end up writing so many scripts, so rather than give them +each their own repository as might be sensible, they're all stuffed in here. + +The following is a brief list of the interesting scripts, and why you might want +to use them. + +* `auto_server/` -- provides a `/join+` command which allows you to join +  channels on servers to which you're not currently connected, as long as you've +  got everything set up correctly in your `/channels` and `/networks` config. +   + +* `history_search/` -- an improved version of coekie's original history_search, it +  behaves a lot more like the standard Readline incremental +  search over your recent command history. +                     +* `ido_mode/` -- A window switching mode best described as "The bastard lovechild +  of emacs ido-mode and window_switcher.pl".  It supports flexible matching of +  channel names, filtering by active windows, servers, and more. See the script +  comments for details. +   +* `feature_tests/` has a bunch of different little scripts written to test certain +  aspects of Irssi behaviour.  None of them are likely to be particularly useful +  unless you're looking for simple examples of various things on which to build. +  Some of the ones which might actually be useful are: + + * `key_test.pl` - decodes and prints keyboard inputs from the `gui key pressed` +   signal.  Useful for checking what keycode a particular combo generates. + + * `template.pl` - A basic outline of a script to use as a starting point +   for others. + + * `pipes.pl` A barebones template for a script which uses forking and pipes +   to handle async tasks in the background without hanging Irssi. +   +* `modules/` contains a few half-baked attempts at loadable modules for Irssi, +  none of which do very much at the moment.  They might be useful as templates +  for your own modules, however. +   +* `no_key_modes/` provides an additional expando `$M_nopass` which you can +  add to your theme to provide channel modes, but without showing the channel +  key.  This is probably less useful since someone pointed me at `/set +  chanmode_expando_strip` which has much the same effect. +   +* `patches/` contains a few patches for the irssi source, probably out of date +  by now, which do various things. The easiest way to figure out what is to read +  them, unless I get bored and document them sometime. +   +* `prompt_info/` was the original "How can I put dynamic status crap into my +  prompt string" script.  This has since been superceded by the far nicer +  `uberprompt.pl` which also lives in that dir.  Many of my other scripts +  require uberprompt as a way to provide part of their user interface. +   +* `sb_position/` gives you an additional status bar item which indicates +  approximately how far you are scrolled in a given window buffer, in terms +  of pages and percentage.  It is configurable as the comments in the script +  explain. +   +* `scrolled_reminder/` is a script that tries to prevent one of the more +  common accidental IRC screwups -- replying to a conversation that happened +  hours ago because you didn't notice you were scrolled up in the history buffer. +  If you try to send a message whilst scrolled, it'll warn you and check you +  really want to first. +  +* `quit-notify/` is based on a script by vague@#irssi/Freenode.  It is most +  useful when you are ignoring joins/parts/quits from a channel, and hence +  don't realise when the person you are responding to has left.  If they have +  (or if you prefix your message with a nonexistent nick), the script will +  ask you to confirm that you want to send the message, much like +  `scrolled_reminder`. +   +* `undo/` contains 2 scripts, `undo.pl` and `kill-ring.pl`. See their header +  for further documentation. +   +* `testing/` is a Perl framework external to Irssi (ie: not a script) that will +  ultimately allow you to write unit tests for your scripts, and run them in a +  real irssi instance, and check that they behave correctly.  Still undergoing +  heavy development, and not really ready for public usage. +   +* `throttled_autojoin/` doesn't work. It's meant to allow people with huuuuuuge +  numbers of autojoin channels and servers to connect slowly, so as not to time +  themselves out when reconnecting. Mostly dead. +   +* `tinyurl_tabcomplete/` is a script which lets you hit tab following anything +  that looks like a URL to replace it with a tinyurl'd version.  Handy if you +  regularly paste Google-maps links or other massive horrible links. +   +* `url_hilight/` I can't remember what this one does. Suggestions welcome. + +* `vim_mode/` The most exciting (and by far the largest) script here. Gives +  vim-addicted users a way to use most of their finger-memory in Irssi.  Still +  under active development, features and bug-requests welcome.  Also supported +  in #irssi_vim on Freenode. +                      +## Documentation + +If you are here looking for my Irssi scripting documentation, please note that +it has been moved to +[https://github.com/shabble/irssi-docs/wiki](https://github.com/shabble/irssi-docs/wiki) + +## Other Things + +Many of my scripts require [uberprompt](https://github.com/shabble/irssi-scripts/blob/master/prompt_info/uberprompt.pl) as a +dependency.  Those that do will say so in the comments at the top of the file, +and will probably refuse to load without it. + diff --git a/act_hide/act_hide.pl b/act_hide/act_hide.pl new file mode 100644 index 0000000..95c10b9 --- /dev/null +++ b/act_hide/act_hide.pl @@ -0,0 +1,353 @@ +=pod + +=head1 NAME + +act_hide.pl + +=head1 DESCRIPTION + +A minimalist template useful for basing actual scripts on. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<filename>>. + +=head1 USAGE + +None, since it doesn't actually do anything. + +=head1 AUTHORS + +Derived from the L<hide.pl|http://scripts.irssi.org/scripts/hide.pl> script, +Original Copyright E<copy> 2002 Marcus Rueckert C<E<lt>darix@irssi.deE<gt>> + +Modifications Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> + +=head1 LICENCE + +B<TODO:> Is this Public Domain enough? + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=head1 TODO + +=cut + +use strict; +use warnings; + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'Marcus Rueckert, shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => 'act_hide', +              description => 'Improved interface to activity_hide_* settings', +              license     => 'Public Domain', +             ); + +# Globals. + +my $CMD_NAME = "hide"; +my $SUB_CMDS = { +                add  => \&cmd_hide_add, +                del  => \&cmd_hide_del, +                list => \&cmd_hide_list, +               }; + +my $CMD_OPTS = "-target -level"; + + +act_hide_init(); + +sub act_hide_init { + +    Irssi::command_bind($CMD_NAME, \&subcmd_handler); + +    foreach my $subcmd (keys %$SUB_CMDS) { +        my $coderef = $SUB_CMDS->{$subcmd}; +        Irssi::command_bind("$CMD_NAME $subcmd", $coderef); +        Irssi::command_set_options("$CMD_NAME $subcmd", $CMD_OPTS); +    } +} + +sub subcmd_handler { +    my ($data, $server, $w_item) = @_; +    $data =~ s/\s+$//g; # strip trailing whitespace. +    Irssi::command_runsub($CMD_NAME, $data, $server, $w_item); +} + +sub parse_args { +    my ($cmd, $data) = @_; +    my $options = {}; +    my @opts = Irssi::command_parse_options($cmd, $data); + +    if (@opts and ref($opts[0]) eq 'HASH') { +        $options = $opts[0]; +        $options->{__remainder} = $opts[1]; +        print "Options: " . Dumper($options); +    } else { +        $options->{__remainder} = $data; +    } +    return $options; +} + +sub cmd_hide_add { +    my ($args, $server, $w_item) = @_; +    my $opts = parse_args("$CMD_NAME add", $args); + +    if (exists $opts->{target}) { +        my $target = $opts->{__remainder}; +        $target =~ s/^\s*(\S+)\s*$/$1/; + +        if (add_item_to_targets($target)) { +            $w_item->print("added $target"); +        } else { +            $w_item->print("failed to add $target"); +        } +    } elsif (exists $opts->{level}) { + +    } else { +        print "Bah"; +    } + +} + +sub cmd_hide_del { +    my ($args, $server, $w_item) = @_; +    my $opts = parse_args("$CMD_NAME del", $args); +    if (exists $opts->{target}) { +        my $target = $opts->{__remainder}; +        $target =~ s/^\s*(\S+)\s*$/$1/; + +        if (remove_item_from_targets($target)) { +            $w_item->print("removed $target"); +        } else { +            $w_item->print("failed to remove $target"); +        } +    } elsif (exists $opts->{level}) { + +    } else { +        print "Bah"; +    } + +} + +sub cmd_hide_list { +    my ($args, $server, $w_item) = @_; +    my $opts = parse_args("$CMD_NAME list", $args); +    if (exists $opts->{target}) { +        $w_item->print("Targets: " +                       . Irssi::settings_get_str('activity_hide_targets')); +    } elsif (exists $opts->{level}) { +      $w_item->print("Levels: " +                     . Irssi::settings_get_level('activity_hide_level')); +    } else { +        $w_item->print("Bah"); +    } + +} + +sub add_item_to_targets { +    my ($item) = @_; +    my $current = Irssi::settings_get_str('activity_hide_targets'); +    my %map = map { $_ => 1 } split /\s+/, $current; + +    if (not exists $map{$item}) { +        Irssi::settings_set_str('activity_hide_targets', $current . ' ' . $item); +        return 1; +    } else { +        print "Cannot add $item, already exists"; +        return 0; +    } +} + +sub remove_item_from_targets { +    my ($item) = @_; +    my $current = Irssi::settings_get_str('activity_hide_targets'); +    my %map = map { $_ => 1 } split /\s+/, $current; + +    if (exists $map{$item}) { +        my $new_str = join ' ', grep { $_ ne $item } keys %map; +        Irssi::settings_set_str('activity_hide_targets', $new_str); +        return 1; +    } else { +        print "Cannot remove $item, doesn't exist"; +        return 0; +    } +} + + + + +# sub add_item { +#     my ($target_type, $data) = @_; +#     my $target = target_check ($target_type); +#     return 0 unless $target; +#     if ($data =~ /^\s*$/ ) { +#         print (CRAP "\cBNo target specified!\cB"); +#         print (CRAP "\cBUsage:\cB hide $target_type add [$target_type]+"); +#     } +#     else { +#         my $set = settings_get_str($target); +#         for my $item ( split (/\s+/, $data) ) { +#             if ($set =~ m/^\Q$item\E$/i) { +#                 print (CRAP "\cBWarning:\cB $item is already in in $target_type hide list.") +#             } +#             else { +#                 print (CRAP "$item added to $target_type hide list."); +#                 $set = join (' ', $set, $item); +#             } +#         }; +#         settings_set_str ($target, $set); +#         signal_emit('setup changed'); +#     } +#     return 1; +# } + +# sub remove_item { +#     my ($target_type, $data) = @_; +#     my $target = target_check ($target_type); +#     if ( not ( $target )) { return 0 }; +#     if ($data =~ /^\s*$/ ) { +#         print (CRAP "\cBNo target specified!\cB"); +#         print (CRAP "\cBUsage:\cB hide $target_type remove [$target_type]+"); +#     } +#     else { +#         my $set = settings_get_str($target); +#         for my $item ( split (/\s+/, $data) ) { +#             if ($set =~ s/$item//i) { +#                 print (CRAP "$item removed from $target_type hide list.") +#             } +#             else { +#                 print (CRAP "\cBWarning:\cB $item was not in $target_type hide list.") +#             } +#         }; +#         settings_set_str ($target, $set); +#         signal_emit('setup changed'); +#     } +#     return 1; +# } + +# sub target_check { +#     my ($target_type) = @_; +#     my $target = ''; +#     if ($target_type eq 'level') { +#         $target = 'activity_hide_level'; +#     } +#     elsif ($target_type eq 'target') { +#         $target = 'activity_hide_targets'; +#     } +#     else { +#         print (CLIENTERROR "\cBadd_item: no such target_type $target_type\cB"); +#     } +#     return $target; +# } + +# sub print_usage { +#     print (CRAP "\cBUsage:\cB"); +#     print (CRAP "  hide target [add|remove] [targets]+"); +#     print (CRAP "  hide level [add|remove] [levels]+"); +#     print (CRAP "  hide usage"); +#     print (CRAP "  hide print"); +#     print (CRAP "See also: levels"); +# }; + +# sub print_items { +#     my ($target_type) = @_; +#     my $delimiter = settings_get_str('hide_print_delimiter'); +#     my $target = target_check ($target_type); +#     if ( not ( $target )) { return 0 }; +#     print ( CRAP "\cB$target_type hide list:\cB$delimiter", join ( $delimiter, sort ( split ( " ", settings_get_str($target) ) ) ) ); +#     return 1; +# } + +# # +# # targets +# # + +# command_bind 'hide target' => sub { +#     my ($data, $server, $item) = @_; +#     if ($data =~ m/^[(add)|(remove)]/i ) { +#         command_runsub ('hide target', $data, $server, $item); +#     } +#     else { +#         print (CRAP "\cBUsage:\cB hide target [add|remove] [targets]+"); +#     } +# }; + +# command_bind 'hide target add' => sub { +#     my ($data, $server, $item) = @_; +#     add_item ('target', $data); +# }; + +# command_bind 'hide target remove' => sub { +#     my ($data, $server, $item) = @_; +#     remove_item ('target', $data); +# }; + +# # +# # levels +# # +# command_bind 'hide level' => sub { +#     my ($data, $server, $item) = @_; +#     if ($data =~ m/^[(add)|(remove)]/i ) { +#         command_runsub ('hide level', $data, $server, $item); +#     } +#     else { +#         print (CRAP "\cBUsage:\cB hide level [add|remove] [levels]+"); +#         print (CRAP "See also: levels"); +#     } +# }; + +# command_bind 'hide level add' => sub { +#     my ($data, $server, $item) = @_; +#     add_item ('level', $data); +# }; + +# command_bind 'hide level remove' => sub { +#     my ($data, $server, $item) = @_; +#     remove_item ('level', $data); +# }; + +# # +# # general +# # + +# command_bind 'hide' => sub { +#     my ($data, $server, $item) = @_; +#     if ($data =~ m/^[(target)|(level)|(help)|(usage)|(print)]/i ) { +#         command_runsub ('hide', $data, $server, $item); +#     } +#     else { +#         print_usage(); +#     } +# }; + +# command_bind 'hide print' => sub { +#     print_items ('level'); +#     print_items ('target'); +# }; + +# command_bind  'hide usage' => sub {  print_usage (); }; +# command_bind  'hide help' => sub {  print_usage (); }; + +# # +# # settings +# # + +# settings_add_str ( 'script', 'hide_print_delimiter',  "\n - "); diff --git a/auto-server/auto_server.pl b/auto-server/auto_server.pl index 7054664..447a131 100644 --- a/auto-server/auto_server.pl +++ b/auto-server/auto_server.pl @@ -55,7 +55,7 @@ our %IRSSI = (                license     => 'Public Domain',               ); -my $channel_map; +my $channel_map = {};  my @hack_channels;  my $pending_joins; @@ -104,6 +104,7 @@ sub haxy_print_hook {  sub parse_channel_map {      #my $data = Irssi::settings_get_str('joinplus_server_maps'); +    unbind_completion();      my $data = retrieve_channels();      my @items = split /\s+/, $data;      if (@items % 2 == 0) { @@ -113,6 +114,19 @@ sub parse_channel_map {          $channel_map = {};      }      _debug_print Dumper($channel_map); +    bind_completion(); +} + +sub bind_completion { +    foreach(%$channel_map) { +        Irssi::command_bind("join+ $_", \&join_plus); +    } +} + +sub unbind_completion { +    foreach(%$channel_map) { +        Irssi::command_unbind("join+ $_", \&join_plus); +    }  }  sub join_plus { @@ -121,7 +135,7 @@ sub join_plus {      # parse out channel name from args:      my $channel; -    if ($args =~ m/^(#?[#a-zA-Z0-9]+)/) { +    if ($args =~ m/^(#?[#a-zA-Z0-9-]+)/) {          $channel = $1;          _debug_print ("Channel is: $channel");      } diff --git a/feature-tests/augment_inputline.pl b/feature-tests/augment_inputline.pl index 577f756..97a129e 100644 --- a/feature-tests/augment_inputline.pl +++ b/feature-tests/augment_inputline.pl @@ -1,3 +1,12 @@ +=pod + +=head1 NAME + +test + +=cut + +  use strict;  use Irssi;  use Irssi::TextUI; # for sbar_items_redraw diff --git a/feature-tests/bindings.pl b/feature-tests/bindings.pl new file mode 100644 index 0000000..ece220d --- /dev/null +++ b/feature-tests/bindings.pl @@ -0,0 +1,114 @@ +use strict; +use warnings; + + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => '', +              description => '', +              licence     => q(GNU GPLv2 or later), + +             ); + +# code taken from adv_windowlist + +my $keymap; + +sub STATE_HEADER () { 0 } +sub STATE_BODY   () { 1 } +sub STATE_END    () { 2 } +my $parse_state = STATE_HEADER; + +my $binding_formats = {}; + +init(); + +sub init { + +    $keymap = {}; + +    Irssi::command_bind('showbinds', 'cmd_showbinds'); +	Irssi::signal_add('command bind' => 'watch_keymap'); + +    $binding_formats = get_binding_formats(); + +    capture_bind_data(); +} + +sub get_binding_formats { +    my $theme = Irssi::current_theme(); +    my @keys = qw/bind_header bind_list bind_command_list +                  bind_footer bind_unknown_id/; + +    my $ret = {}; +    foreach my $key (@keys) { +        my $tmp = $theme->get_format('fe-common/core', $key); +        #$tmp =~ s/%/%%/g; # escape colour codes? +        $ret->{$key} = $tmp; +    } +    return $ret; +} + +sub cmd_showbinds { +    my ($args, @rest) = @_; + +    my $win = Irssi::active_win(); +    $win->print("Change window bindings:", Irssi::MSGLEVEL_CLIENTCRAP); +    for my $w (sort keys %$keymap) { +        my $x = $keymap->{$w}; +        $win->print("$w ==> $x", Irssi::MSGLEVEL_CLIENTCRAP); +    } +    $win->print("Done showing window bindings:", Irssi::MSGLEVEL_CLIENTCRAP); + +} + +sub sig_print_text { +	my ($text_dest, $str, $str_stripped) = @_; + +    return unless $text_dest->{level} == Irssi::MSGLEVEL_CLIENTCRAP; +    return unless $text_dest->{target} eq ''; +    return unless not defined $text_dest->{server}; + +    # if ($parse_state = STATE_HEADER) { +    #     if ($str =~ m/\Q$binding_formats->{bind_header}\E/) { +    #         $parse_state = STATE_BODY; +    #     } +    # } elsif ($parse_state = STATE_BODY) { +    print "Data is: $str_stripped"; +    if ($str_stripped =~ m/^.*?(\S{,20})\s+(\S+)\s+(\S+)/) { +        $keymap->{$1} = "$2, $3"; +        print "Parsed $1 as $2, $3"; +    } +    Irssi::signal_stop(); +    #     } elsif ($str =~ m/$binding_formats->{bind_footer}\E/) { +    #         $parse_state = STATE_END; +    #     } +    # } +} + + +sub capture_bind_data { +	Irssi::signal_remove('command bind' => 'watch_keymap'); +	Irssi::signal_add_first('print text' => 'sig_print_text'); +	Irssi::command('bind'); # stolen from grep +	Irssi::signal_remove('print text' => 'sig_print_text'); + +} + + +# watch keymap changes +sub watch_keymap { +	Irssi::timeout_add_once(1000, 'capture_bind_data', undef); +} + + + diff --git a/feature-tests/easy_exec.pl b/feature-tests/easy_exec.pl index 669a00b..d342516 100644 --- a/feature-tests/easy_exec.pl +++ b/feature-tests/easy_exec.pl @@ -2,7 +2,7 @@ use strict;  use warnings;  # export everything. -use Irssi; #(@Irssi::EXPORT_OK); +use Irssi;  use Irssi::Irc;  use Irssi::TextUI; @@ -18,33 +18,38 @@ our %IRSSI = (                license     => 'Public Domain',               ); -#Irssi::signal_add_first 'command script exec', \&better_exec; -Irssi::command_bind('script exec', \&better_exec); - -sub better_exec { -    my ($args, $serv, $witem) = @_; -    # todo: handle permanent arg? -    my $perm = 0; -    print "Args: $args"; -    if ($args =~ s/^\s*-permanent\s*(.*)$/$1/) { -        $perm = 1; -    } -    print "Args now: $args"; - -#    eval $args; -    my $str = "//script exec " . -     ($perm ? '-permanent' : '') -     . 'use Irssi (@Irssi::EXPORT_OK); ' . $args; -     print "Running: $str"; - -#    Irssi::command($str); -    Irssi::signal_continue($str, @_[1..$#_]); -} +# TODO: make this more tab-complete friendly +init(); + +sub init { +    Irssi::command('/alias se script exec use Data::Dumper\;' +                   .' use Irssi (@Irssi::EXPORT_OK)\; $0-'); +    Irssi::command('/alias sep script exec -permanent ' +                   . 'use Data::Dumper\; use Irssi (@Irssi::EXPORT_OK)\; $0-'); -sub Dump { -    print Dumper(\@_); +    Irssi::signal_add_last ('complete word', 'sig_complete_word');  } -sub test() { -    print "This is a test"; +sub sig_complete_word { +    my ($strings, $window, $word, $linestart, $want_space) = @_; +    # only provide these completions if the input line is otherwise empty. +    my $cmdchars = Irssi::settings_get_str('cmdchars'); +    my $quoted = quotemeta($cmdchars); +    #print "Linestart: $linestart"; +    return unless ($linestart =~ /^${quoted}(?:se|sep)/); +     +    my $clean_word = $word; +    $clean_word =~ s/^"//g; +    $clean_word =~ s/"$//g; +    $clean_word =~ s/->$//g; + + + +    my @expansions = @Irssi::EXPORT_OK; +    push @$strings,  grep { $_ =~ m/^\Q$clean_word\E/ } @expansions; +    print "Sebug: " . join(", ", @$strings); +    $$want_space = 0; + + +    Irssi::signal_stop() if (@$strings);  } diff --git a/feature-tests/exec.pl b/feature-tests/exec.pl new file mode 100644 index 0000000..f6d6377 --- /dev/null +++ b/feature-tests/exec.pl @@ -0,0 +1,442 @@ +# exec.pl +# a (currently stupid) alternative to the built-in /exec, because it's broken +# on OSX. This thing stll needs a whole bunch of actual features, but for now, +# you can actually run commands. + +# Obviously, that's pretty dangerous.  Use at your own risk. + +# EXEC [-] [-nosh] [-out | -msg <target> | -notice <target>] [-name <name>] <cmd line> +# EXEC -out | -window | -msg <target> | -notice <target> | -close | -<signal> %<id> +# EXEC -in %<id> <text to send to process> +# +#      -: Don't print "process terminated ..." message +# +#      -nosh: Don't start command through /bin/sh +# +#      -out: Send output to active channel/query +# +#      -msg: Send output to specified nick/channel +# +#      -notice: Send output to specified nick/channel as notices +# +#      -name: Name the process so it could be accessed easier +# +#      -window: Move the output of specified process to active window +# +#      -close: Forcibly close (or "forget") a process that doesn't die. +#              This only removes all information from irssi concerning the +#              process, it doesn't send SIGKILL or any other signal +#              to the process. +# +#      -<signal>: Send a signal to process. <signal> can be either numeric +#                 or one of the few most common ones (hup, term, kill, ...) +# +#      -in: Send text to standard input of the specified process +# +#      -interactive: Creates a query-like window item. Text written to it is +#                    sent to executed process, like /EXEC -in. +# +# Execute specified command in background. Output of process is printed to +# active window by default, but can be also sent as messages or notices to +# specified nick or channel. +# +# Processes can be accessed either by their ID or name if you named it. Process +# identifier must always begin with '%' character, like %0 or %name. +# +# Once the process is started, its output can still be redirected elsewhere with +# the -window, -msg, etc. options. You can send text to standard input of the +# process with -in option. +# +# -close option shouldn't probably be used if there's a better way to kill the +# process. It is meant to remove the processes that don't die even with +# SIGKILL. This option just closes the pipes used to communicate with the +# process and frees all memory it used. +# +# EXEC without any arguments displays the list of started processes. +# + + + +use 5.010;    # 5.10 or above, necessary to get the return value from a command. + +use strict; +use warnings; +use English '-no_match_vars'; + +use Irssi; +use POSIX; +use Time::HiRes qw/sleep/; +use IO::Handle; +use IO::Pipe; +use IPC::Open3; +use Symbol 'geniosym'; + +use Data::Dumper; + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => 'exec.pl', +              description => '', +              license     => 'Public Domain', +             ); + +my @processes = (); +sub get_processes { return @processes } + +# the /exec command, nothing to do with the actual command being run. +my $command; +my $command_options; + +sub get_new_id { +    my $i = 1; +    foreach my $proc (@processes) { +        if ($proc->{id} != $i) { +            next; +        } +        $i++; +    } +    return $i; +} + +sub add_process { +    #my ($pid) = @_; +    my $id = get_new_id(); + +    my $new = { +               id      => $id, +               pid     => 0, +               in_tag  => 0, +               out_tag => 0, +               err_tag => 0, +               s_in    => geniosym(), #IO::Handle->new, +               s_err   => geniosym(), #IO::Handle->new, +               s_out   => geniosym(), #IO::Handle->new, +               cmd     => '', +               opts    => {}, +              }; + +    # $new->{s_in}->autoflush(1); +    # $new->{s_out}->autoflush(1); +    # $new->{s_err}->autoflush(1); + +    push @processes, $new; + +    _msg("New process item created: $id"); +    return $new; +} + +sub find_process_by_id { +    my ($id) = @_; +    my @matches =  grep { $_->{id} == $id } @processes; +    _error("wtf, multiple id matches for $id. BUG") if @matches > 1; + +    return $matches[0]; + +} +sub find_process_by_pid { +    my ($pid) = @_; +    my @matches =  grep { $_->{pid} == $pid } @processes; +    _error("wtf, multiple pid matches for $pid. BUG") if @matches > 1; + +    return $matches[0]; +} + +sub remove_process { +    my ($id, $verbose) = @_; +    my $del_index = 0; +    foreach my $proc (@processes) { +        if ($id == $proc->{id}) { +            last; +        } +        $del_index++; +    } +    print "remove: del index: $del_index"; +    if ($del_index <= $#processes) { +        my $dead = splice(@processes, $del_index, 1, ()); +        #_msg("removing " . Dumper($dead)); + +        Irssi::input_remove($dead->{err_tag}); +        Irssi::input_remove($dead->{out_tag}); + +        close $dead->{s_out}; +        close $dead->{s_in}; +        close $dead->{s_err}; + +    } else { +        $verbose = 1; +        if ($verbose) { +            print "remove: No such process with ID $id"; +        } +    } +} + +sub show_current_processes { +    if (@processes == 0) { +        print "No processes running"; +        return; +    } +    foreach my $p (@processes) { +        printf("ID: %d, PID: %d, Command: %s", $p->{id}, $p->{pid}, $p->{cmd}); +    } +} + +sub parse_options { +    my ($args) = @_; +    my @options = Irssi::command_parse_options($command, $args); +    if (@options) { +        my $opt_hash = $options[0]; +        my $rest     = $options[1]; + +        $rest =~ s/^\s*(.*?)\s*$/$1/; # trim surrounding space. + +        #print Dumper([$opt_hash, $rest]); +        if (length $rest) { +            return ($opt_hash, $rest); +        } else { +            show_current_processes(); +            return (); +        } +    } else { +        _error("Error parsing $command options"); +        return (); +    } +} + +sub schedule_cleanup { +    my $fd = shift; +    Irssi::timeout_add_once(100, sub { $_[0]->close }, $fd); +} + +sub do_fork_and_exec { +    my ($rec) = @_; + +    #Irssi::timeout_add_once(100, sub { die }, {}); + +    return unless exists $rec->{cmd}; +    drop_privs(); + +    _msg("Executing command " . join(", ", @{ $rec->{cmd} })); +    my $c = join(" ", @{ $rec->{cmd} }); +    my $pid = open3($rec->{s_sin}, $rec->{s_out}, $rec->{s_err}, $c); + +    _msg("PID is $pid"); +    $rec->{pid} = $pid; + +    # _msg("Pid %s, in: %s, out: %s, err: %s, cmd: %s", +    #      $pid, $sin, $sout, $serr, $cmd); + +    # _msg("filenos, Pid %s, in: %s, out: %s, err: %s", +    #      $pid, $sin->fileno, $sout->fileno, $serr->fileno); + +    if (not defined $pid) { + +        _error("open3 failed: $! Aborting"); + +        close($_) for ($rec->{s_in}, $rec->{s_err}, $rec->{s_out}); +        undef($_) for ($rec->{s_in}, $rec->{s_err}, $rec->{s_out}); + +        return; +    } + +    # parent +    if ($pid) { + +#    eval { +        print "fileno is " .  fileno($rec->{s_out}); +        $rec->{out_tag} = Irssi::input_add( fileno($rec->{s_out}), +                                            Irssi::INPUT_READ, +                                            \&child_output, +                                            $rec); +        #die unless $rec->{out_tag}; + +        $rec->{err_tag} = Irssi::input_add(fileno($rec->{s_err}), +                                           Irssi::INPUT_READ, +                                           \&child_error, +                                           $rec); +        #die unless $rec->{err_tag}; + + #   }; + + +        Irssi::pidwait_add($pid); +        die "input_add failed to initialise: $@" if $@; +    } +} + +sub drop_privs { +    my @temp = ($EUID, $EGID); +    my $orig_uid = $UID; +    my $orig_gid = $GID; +    $EUID = $UID; +    $EGID = $GID; +    # Drop privileges +    $UID = $orig_uid; +    $GID = $orig_gid; +    # Make sure privs are really gone +    ($EUID, $EGID) = @temp; +    die "Can't drop privileges" +      unless $UID == $EUID && $GID eq $EGID; +} + +sub child_error { +    my $rec = shift; + +    my $err_fh = $rec->{s_err}; + +    my $done = 0; + +    while (not $done) { +        my $data = ''; +        _msg("Stderr: starting sysread"); +        my $bytes_read = sysread($err_fh, $data, 256); +        if (not defined $bytes_read) { +            _error("stderr: sysread failed:: $!"); +            $done = 1; +        } elsif ($bytes_read == 0) { +            _msg("stderr: sysread got EOF"); +            $done = 1; +        } elsif ($bytes_read < 256) { +            # that's all, folks. +            _msg("%%_stderr:%%_ read %d bytes: %s", $bytes_read, $data); +        } else { +            # we maybe need to read some more +            _msg("%%_stderr:%%_ read %d bytes: %s, maybe more", $bytes_read, $data); +        } +    } + +    _msg('removing input stderr tag'); +    Irssi::input_remove($rec->{err_tag}); + +} + +sub sig_pidwait { +    my ($pidwait, $status) = @_; +    my @matches = grep { $_->{pid} == $pidwait } @processes; +    foreach my $m (@matches) { +        _msg("PID %d has terminated. Status %d (or maybe %d .... %d)", +             $pidwait, $status, $?, ${^CHILD_ERROR_NATIVE} ); + +        remove_process($m->{id}); +    } +} + +sub child_output { +    my $rec = shift; +    my $out_fh = $rec->{s_out}; + +    my $done = 0; + +    while (not $done) { +        my $data = ''; +        _msg("Stdout: starting sysread"); +        my $bytes_read = sysread($out_fh, $data, 256); +        if (not defined $bytes_read) { +            _error("stdout: sysread failed:: $!"); +            $done = 1; +        } elsif ($bytes_read == 0) { +            _msg("stdout: sysread got EOF"); +            $done = 1; +        } elsif ($bytes_read < 256) { +            # that's all, folks. +            _msg("%%_stdout:%%_ read %d bytes: %s", $bytes_read, $data); +        } else { +            # we maybe need to read some more +            _msg("%%_stdout:%%_ read %d bytes: %s, maybe more", $bytes_read, $data); +        } +    } + +    _msg('removing input stdout tag'); +    Irssi::input_remove($rec->{out_tag}); + +    #schedule_cleanup($stdout_reader); +    #$stdout_reader->close; +} + +sub _error { +    my ($msg, @params) = @_; +    my $win = Irssi::active_win(); +    my $str = sprintf($msg, @params); +    $win->print($str, Irssi::MSGLEVEL_CLIENTERROR); +} + +sub _msg { +    my ($msg, @params) = @_; +    my $win = Irssi::active_win(); +    my $str = sprintf($msg, @params); +    $win->print($str, Irssi::MSGLEVEL_CLIENTCRAP); +} + +sub cmd_exec { + +    my ($args, $server, $witem) = @_; +    Irssi::signal_stop; +    my @options = parse_options($args); + +    if (@options) { +        my $rec = add_process(); +        my ($options, $cmd) = @options; + +        $cmd = [split ' ', $cmd]; + +        if (not exists $options->{nosh}) { +            unshift @$cmd, ("/bin/sh -c"); +        } + +        $rec->{opts} = $options; +        $rec->{cmd}  = $cmd; + +        do_fork_and_exec($rec) +    } + +} + +sub cmd_input { +    my ($args) = @_; +    my $rec = $processes[0];    # HACK, make them specify. +    if ($rec->{pid}) { +        print "INput writing to $rec->{pid}"; +        my $fh = $rec->{s_in}; + +        my $ret = syswrite($fh, "$args\n"); +        if (not defined $ret) { +            print "Error writing to process $rec->{pid}: $!"; +        } else { +            print "Wrote $ret bytes to $rec->{pid}"; +        } + +    } else { +        _error("no execs are running to accept input"); +    } +} + +sub exec_init { +    $command = "exec"; +    $command_options = join ' ', +      ( +       '!-', 'interactive', 'nosh', '+name', '+msg', +       '+notice', 'window', 'close', '+level', 'quiet' +      ); + +    Irssi::command_bind($command, \&cmd_exec); +    Irssi::command_set_options($command, $command_options); +    Irssi::command_bind('input', \&cmd_input); + +    Irssi::signal_add('pidwait', \&sig_pidwait); +} + +  exec_init(); + +package Irssi::UI; + +{ +    no warnings 'redefine'; + +    sub processes() { +        return Irssi::Script::exec::get_processes(); +    } + +} + +1; diff --git a/feature-tests/format-test.pl b/feature-tests/format-test.pl index d8be412..7a38d9f 100644 --- a/feature-tests/format-test.pl +++ b/feature-tests/format-test.pl @@ -3,7 +3,7 @@ use warnings;  use Irssi; - +use Data::Dumper;  our $VERSION = '0.1';  our %IRSSI = ( @@ -14,13 +14,26 @@ our %IRSSI = (                license     => 'Public Domain',               ); +sub actually_printformat { +    my ($win, $level, $module, $format, @args) = @_; +    my $ret = ''; +    { +        # deeeeeeep black magic. +        local *CORE::GLOBAL::caller = sub { $module }; +        $win->printformat($level, $format, @args); + +        $ret = Irssi::current_theme()->get_format($module, $format); +    } +    return $ret; +} +  init();  sub init { -    Irssi::command_bind('ft', \&format_test); -} +    my $win = Irssi::active_win(); +    my $moo = actually_printformat($win, Irssi::MSGLEVEL_CLIENTCRAP, 'fe-common/irc', +                                   "kill_server", "foo", "bar", "horse", "cake"); +    print Dumper($moo); -sub format_test { -    my ($args, $win, $server) = @_;  } diff --git a/feature-tests/key_sig.pl b/feature-tests/key_sig.pl new file mode 100644 index 0000000..ef69d45 --- /dev/null +++ b/feature-tests/key_sig.pl @@ -0,0 +1,51 @@ +use strict; +use warnings 'all'; + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => '', +              description => '', +              license     => 'Public Domain', +             ); + +my $bacon = 10; + +Irssi::signal_register({'key created' => [qw/Irssi::UI::Key/ ] }); + +Irssi::signal_add('key created', \&sig_key_created); +Irssi::signal_register({'key command' => [qw/string/]}); +Irssi::signal_add_first('key command' => \&sig_key_cmd); + +Irssi::signal_register({'key nothing' => [qw/string/]}); +Irssi::signal_add_first('key nothing' => \&sig_key_cmd); + +Irssi::signal_register({'keyboard created' => [qw/Irssi::UI::Keyboard/]}); +Irssi::signal_add_first('keyboard created' => \&sig_keyboard); + +sub sig_keyboard { +    my ($data) = @_; +    print "keyboard: " . Dumper($data); +} + +sub sig_key_cmd { +    my ($data) = @_; +    print "key cmd: " . Dumper($data); + +} + +sub sig_key_created { +    my @args = @_; + +    print "Key Created, Args: " . Dumper(\@args); +} + +Irssi::command("bind meta-q /echo moo"); diff --git a/feature-tests/local_input_capture.pl b/feature-tests/local_input_capture.pl new file mode 100644 index 0000000..847ff07 --- /dev/null +++ b/feature-tests/local_input_capture.pl @@ -0,0 +1,50 @@ +use strict; +use warnings; + + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => '', +              description => '', +              license     => 'Public Domain', +             ); + +my $buffer = ''; +init(); + +sub init { + +    Irssi::signal_add_first 'print text', 'sig_print_text'; +    Irssi::command 'echo Hello there'; +    Irssi::signal_remove 'print text', 'sig_print_text'; +    Irssi::command_bind 'showbuf', 'cmd_showbuf'; +} + +sub cmd_showbuf { +    my ($args, $server, $win_item) = @_; +    my $win; +    if (defined $win_item) { +        $win = $win_item->window(); +    } else { +        $win = Irssi::active_win(); +    } + +    $win->print("buffer is: $buffer"); +    $buffer = ''; +} + +sub sig_print_text { +    my ($text_dest, $str, $stripped_str) = @_; + +    $buffer .= $stripped_str; +    Irssi::signal_stop; +} diff --git a/feature-tests/pipes.pl b/feature-tests/pipes.pl new file mode 100644 index 0000000..50bff53 --- /dev/null +++ b/feature-tests/pipes.pl @@ -0,0 +1,87 @@ +use strict; +use warnings; + +use Irssi; +use POSIX; +use Time::HiRes qw/sleep/; + +my $forked = 0; + +sub pipe_and_fork { +    my ($read_handle, $write_handle); + +    pipe($read_handle, $write_handle); + +    my $oldfh = select($write_handle); +    $| = 1; +    select $oldfh; + +    return if $forked; + +    my $pid = fork(); + +    if (not defined $pid) { +        _error("Can't fork: Aborting"); +        close($read_handle); +        close($write_handle); +        return; +    } + +    $forked = 1; + +    if ($pid > 0) { # this is the parent (Irssi) +        close ($write_handle); +        Irssi::pidwait_add($pid); +        my $job = $pid; +        my $tag; +        my @args = ($read_handle, \$tag, $job); +        $tag = Irssi::input_add(fileno($read_handle), +                                      Irssi::INPUT_READ, +                                      \&child_input, +                                      \@args); + +    } else { # child +        # make up some data - block if we like. +        for (1..10) { +            sleep rand 1; +            print $write_handle "Some data: $_\n"; +        } +        print $write_handle "__DONE__\n"; +        close $write_handle; + +        POSIX::_exit(1); +    } +} + +sub child_input { +    my $args = shift; +    my ($read_handle, $input_tag_ref, $job) = @$args; + +    my $data = <$read_handle>; + +    if ($data =~ m/__DONE__/) { +        close($read_handle); +        Irssi::input_remove($$input_tag_ref); +        _msg("child finished"); + +        $forked = 0; + +    } else { +        _msg("Received from child: $data"); +    } + +} + +sub _error { +    my ($msg) = @_; +    my $win = Irssi::active_win(); +    $win->print($msg, Irssi::MSGLEVEL_CLIENTERROR); +} + +sub _msg { +    my ($msg) = @_; +    my $win = Irssi::active_win(); +    $win->print($msg, Irssi::MSGLEVEL_CLIENTCRAP); +} + +Irssi::command_bind("start_pipes", \&pipe_and_fork); diff --git a/feature-tests/redir-input.pl b/feature-tests/redir-input.pl index 94ca523..ed77d3c 100644 --- a/feature-tests/redir-input.pl +++ b/feature-tests/redir-input.pl @@ -20,11 +20,14 @@ our %IRSSI = (  Irssi::command_bind("ri", \&cmd_ri); - +Irssi::signal_register({ 'gui entry redirect' => [ qw/string string intptr intptr/]});  sub cmd_ri {      my ($args, $server, $witem) = @_;      my $win = Irssi::active_win(); - +    my ($x, $y) = (0, 0); +    Irssi::signal_emit('gui entry redirect', 'sub_blah', "bacon", $x, $y);      #my $ref = Irssi::windows_refnum_last -    $win->format_create_dest(Irssi::MSGLEVEL_ClIENTCRAP()); +#    $win->format_create_dest(Irssi::MSGLEVEL_ClIENTCRAP());  } + +sub sub_blah { print "Moo" } diff --git a/feature-tests/sig_unbind.pl b/feature-tests/sig_unbind.pl new file mode 100644 index 0000000..3123182 --- /dev/null +++ b/feature-tests/sig_unbind.pl @@ -0,0 +1,58 @@ +use strict; +use warnings; + + +use Irssi (@Irssi::EXPORT_OK); +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => '', +              description => '', +              license     => 'Public Domain', +             ); + +command_bind("dosig_r", +             sub { +                 my $ref = \&cmd_oink; +                 _print("binding oink to $ref"); +                 signal_add("command oink", $ref); +             }); + +command_bind("undosig_r", +             sub { +                 my $ref = \&cmd_oink; + +                 _print("unbinding oink from $ref"); + +                 signal_remove("command oink", $ref); +                 }); + +command_bind("dosig_s", +             sub { +                 signal_add("command oink", 'cmd_oink'); +             }); + +command_bind("undosig_s", +             sub { +                 signal_remove("command oink", 'cmd_oink'); +                 }); + +sub cmd_oink { +    Irssi::active_win()->print("Oink:"); +} + +sub _print  { +    Irssi::active_win()->print($_[0]); +} + +command("dosig_r"); +command("oink"); +command("undosig_r"); +command("oink"); diff --git a/feature-tests/signal_logger.pl b/feature-tests/signal_logger.pl new file mode 100644 index 0000000..3b3b9ad --- /dev/null +++ b/feature-tests/signal_logger.pl @@ -0,0 +1,170 @@ +use strict; +use warnings; + + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; +use Time::HiRes qw/time/; + +use Data::Dumper; + + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => '', +              description => '', +              license     => 'Public Domain', +             ); + +my $enabled = 0; +my $depth = 0; +my $handlers = { }; +my @log = (); +my @signals = +( +'send text', +'send command', +#'print text', +#'gui print text', +'beep', +#'complete word', +#'gui key pressed', +'window changed', + "server add fill", + "server connect copy", + "server connect failed", + "server connected", + "server connecting", + "server disconnected", + "server event", + "server incoming", + "server lag disconnect", + "server lag", + "server looking", + "server nick changed", + "server quit", + "server reconnect not found", + "server reconnect remove", + "server reconnect save status", + "server sendmsg", + "server setup fill chatnet", + "server setup fill connect", + "server setup fill reconn", + "server setup read", + "server setup saved", + "default event", +#'gui print text finished', + +); + +init(); + +sub init { + +    @log = (); +    $handlers = {}; + +    Irssi::command_bind('siglog_on',    \&cmd_register_all_signals); +    Irssi::command_bind('siglog_off',   \&cmd_unregister_all_signals); +    Irssi::command_bind('siglog_dump',  \&cmd_log_dump); +    Irssi::command_bind('siglog_stats', \&cmd_log_stats); +} + +sub cmd_register_all_signals { + + +    Irssi::active_win->print("Starting to log all signals"); +    $enabled = 1; + +    foreach my $sig_name (@signals) { + +        my $first_func = build_init_func($sig_name); +        my $last_func  = build_end_func($sig_name); + +        $handlers->{$sig_name} = [ $first_func, $last_func ]; + +        Irssi::signal_add_first($sig_name, $first_func); +        Irssi::signal_add_last($sig_name,  $last_func); +    } +} + +sub cmd_unregister_all_signals { + +    foreach my $sig_name (@signals) { + +        my ($first_func, $last_func) = @{ $handlers->{$sig_name} }; + +        Irssi::signal_remove($sig_name, $first_func); +        Irssi::signal_remove($sig_name,  $last_func); +    } +    $enabled = 0; +    Irssi::active_win->print("Signal logging disabled"); + +} + +sub cmd_log_dump { + +    my $win = Irssi::active_win(); +    if ($enabled) { +        cmd_unregister_all_signals(); +        $win->print("Disabled logging"); +    } +    foreach my $lref (@log) { +        my ($line, $indent) = @$lref; +        my $xx = " " x $indent; +        $win->print($xx . $line); +    } +} + +sub cmd_log_stats { + +    my $win = Irssi::active_win(); +    if ($enabled) { +        cmd_unregister_all_signals(); +        $win->print("Disabled logging"); +    } +} + +sub build_init_func { +    my ($sig_name) = @_; + +    return sub { +        my @args = @_; +        my $args_str = ''; +        my $n = 0; + +        foreach my $arg (@args) { +            $args_str .= "[$n] "; + +            if (not defined $arg) { +                $args_str .= "undef, "; +                next; +            } + +            if (ref $arg) { +                $args_str .= ref($arg) . ", " +            } else { +                $arg =~ s/^(.{20})/$1/; +                $args_str .= "$arg, "; +            } +            $n++; +        } +        my $msg = sprintf("%f: %s - First %s", time(), $sig_name, $args_str); +        push @log, [$msg, $depth]; +        $depth++; +    } +} + +sub build_end_func { +    my ($sig_name) = @_; + +    return sub { +        my $msg = sprintf("%f: %s - End", time(), $sig_name); +        push @log, [$msg, $depth]; +        $depth--; +    } +} + diff --git a/feature-tests/signal_redir.pl b/feature-tests/signal_redir.pl new file mode 100644 index 0000000..89da34c --- /dev/null +++ b/feature-tests/signal_redir.pl @@ -0,0 +1,116 @@ +# mangled from cmpusers.pl (unpublished, afaik) by Bazerka <bazerka@quakenet.org>. +# He is not to blame for any problems, contact me instead. + +use strict; +use warnings; + +use Irssi; + +our $VERSION = "0.1"; +our %IRSSI = +  ( +   authors     => "shabble, Bazerka", +   contact     => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode,' +                . 'bazerka@quakenet.org', +   name        => "signal_redir", +   description => "Demonstration showing how to redirect a remote WHOIS"  +                . "command so the results can be captured by a script.", +   license     => "BSD", +   url         => "https://github.com/shabble/irssi-scripts/", +   changed     => "Fri Apr 1 00:05:39 2011" +  ); + +my $running = 0; # flag to prevent overlapping requests. +my $debug = 1; +sub redir_init { +    # set up event to handler mappings +    Irssi::signal_add +        ({ +          'redir test_redir_whois_user'       => 'event_whois_user', +          'redir test_redir_whois_channels'   => 'event_whois_channels', +          'redir test_redir_whois_end'        => 'event_whois_end', +          'redir test_redir_whois_nosuchnick' => 'event_whois_nosuchnick', +          'redir test_redir_whois_timeout'    => 'event_whois_timeout', +         }); +} + +sub request_whois { +    my ($server, $nick) = @_; + +    $server->redirect_event +      ( +       'whois', 1, $nick, 0,             # command, remote, arg, timeout +       'redir test_redir_whois_timeout', # error handler +       { +        'event 311' => 'redir test_redir_whois_user', # event mappings +        'event 318' => 'redir test_redir_whois_end', +        'event 319' => 'redir test_redir_whois_channels', +        'event 401' => 'redir test_redir_whois_nosuchnick', +        ''          => 'event empty', +       } +      ); +    Irssi::print("Sending Command: WHOIS $nick", MSGLEVEL_CLIENTCRAP) if $debug; +    # send the actual command directly to the server, rather than +    # with $server->command() +    $server->send_raw("WHOIS $nick"); +} + +sub event_whois_user { +    my ($server, $data) = @_; +    my ($nick, $user, $host) = ( split / +/, $data, 6 )[ 1, 2, 3 ]; +    Irssi::print("test_redir whois_user: $nick!$user\@$host", MSGLEVEL_CLIENTCRAP); +} + +sub event_whois_channels { +    my ($server, $data) = @_; +    my ($nick, $channels) = ( split / +/, $data, 3 )[ 1, 2 ]; +    my $prefix = 'cowu.be'; # server name +    my $args = "shabble";   # match criteria +    my $event = 'event 319'; # triggering event +    my $sig = $server->redirect_get_signal($prefix, $event, $args); +    Irssi::print("test_redir whois_channels: $nick, $channels", MSGLEVEL_CLIENTCRAP); +    Irssi::print("test_redir get_signal: $sig", MSGLEVEL_CLIENTCRAP); + +} + +sub event_whois_end { +    my ($server, $data) = @_; +    my ($nick) = ( split / +/, $data, 3 )[1]; +    Irssi::print("test_redir whois_end: $nick", MSGLEVEL_CLIENTCRAP); + +    return if $running == 0; # catch 318 -> 401 (nosuchnick followed by endofwhois) +    $running = 0; +} + +sub event_whois_nosuchnick { +    my ($server, $data) = @_; +    my $nick = ( split / +/, $data, 4)[1]; +    Irssi::active_win->print("test_redir error: no such nick $nick - aborting.", +                             MSGLEVEL_CLIENTCRAP); +    $running = 0; +} + +sub event_whois_timeout { +    my ($server, $data) = @_; +    Irssi::print("test_redir whois_timeout", MSGLEVEL_CLIENTCRAP); +    $running = 0; +} + +sub cmd_test_redir { +    my ($args, $server, $witem) = @_; +    $args = lc $args; +    my @nicks = split /\s+/, $args; + +    if ($running) { +        Irssi::active_win->print +            ("test_redir error: a request is currently being processed " +             . "- please try again shortly.", MSGLEVEL_CLIENTCRAP); +        return; +    } +    $running = 1; +    request_whois($server, $nicks[0]); +} + +redir_init(); +Irssi::command_bind("test_redir", \&cmd_test_redir); + diff --git a/feature-tests/template.pl b/feature-tests/template.pl index f3ae68a..a49d742 100644 --- a/feature-tests/template.pl +++ b/feature-tests/template.pl @@ -1,19 +1,69 @@ +=pod + +=head1 NAME + +template.pl + +=head1 DESCRIPTION + +A minimalist template useful for basing actual scripts on. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<filename>>. + +=head1 USAGE + +None, since it doesn't actually do anything. + +=head1 AUTHORS + +Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=head1 TODO + +Use this template to make an actual script. + +=cut +  use strict;  use warnings; -  use Irssi;  use Irssi::Irc;  use Irssi::TextUI;  use Data::Dumper; -  our $VERSION = '0.1';  our %IRSSI = (                authors     => 'shabble',                contact     => 'shabble+irssi@metavore.org',                name        => '',                description => '', -              license     => 'Public Domain', +              license     => 'MIT', +              updated     => '$DATE'               ); diff --git a/history-search/README.pod b/history-search/README.pod new file mode 100644 index 0000000..ce0a42f --- /dev/null +++ b/history-search/README.pod @@ -0,0 +1,135 @@ +=pod + +=head1 NAME + +rl_history_search.pl + +=head1 DESCRIPTION + +Search within your typed history as you type (something like ctrl-R in bash or other +readline-type applications.) + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<filename>>. + +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. + +=head1 SETUP + +C</bind ^R /history_search_start> + +Where C<^R> is a key of your choice. + +=head1 USAGE + +Type C<ctrl-R> followed by your search string.  The prompt line will show +you the most recent command to match the string you've typed. + +B<Tip:> You can cycle through multiple matches with C<Ctrl-R> (to select older +matches), and <Ctrl-S> (to select newer matches).  Cycling off the end of the +history list returns you to the other end again. + +B<NOTE:> C<Ctrl-S> may not work if you have software flow control configured for +your terminal. It may appear to freeze irssi entirely. If this happens, it can +be restored with C<Ctrl-Q>, but you will be unable to use the C<Ctrl-S> binding. +You can disable flow control by running the command C<stty -ixon> in your +terminal, or setting C<defflow off> in your F<~/.screenrc>if using GNU Screen. + +=head2 COMMANDS + +=over 4 + +=item C<Enter> selects a match and terminates search mode.  B<It will also run +the selected command.> + +=item C<Ctrl-G> exits search mode without selecting. + +=item C<E<lt>TABE<gt>> will open a new split window, showing all matching +  completions.  C<E<lt>EscE<gt>> will close the window again, as will any other +  action that exits history search mode.  Possible candidates can be cycled +  through as normal using C<C-r> and <C-s>. + +=item Any other ctrl- or meta- key binding will terminate search mode, leaving +  the selected item in the input line. + +=back + +=head1 AUTHORS + +Original script L<history_search.pl|https://github.com/coekie/irssi-scripts/blob/master/history_search.pl> Copyright E<copy> 2007 Wouter Coekaerts <coekie@irssi.org> + +Copyright E<copy> 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 + +faff + + +=head1 TODO + +=over 1 + +=item * DONE document tab behaviour + +=item * add keys (C-n/C-p) to scroll history list if necessary. + +=item * DONE if list is bigger than split size, centre it so selected item is visible + +=item * INPROG allow a mechanism to select by number from list + +=item * steal more of the code from ido_switcher to hilight match positions. + +=item * make flex matching optional (key or setting) + +=item * add some online help (? or C-h triggered, maybe?) + +=item * make temp_split stuff more generic (to be used by help, etc) + +=item * figure out why sometimes the split list doesn't update correctly (eg: no matches) + +=item * consider tracking history manually (via send command/send text) + +=item + +=over 4 + +=item * Pro: we could timestamp it. + +=item * Con: Would involve catching up/down and all the other history selection mechanisms. + +=item * Compromise - tag history as it comes it (match it with data via sig handlers?) + +=back + +=item * Possibility of saving/restoring history over sessions? + +=back + diff --git a/history-search/rl_history_search.pl b/history-search/rl_history_search.pl index ca45fe5..7561386 100644 --- a/history-search/rl_history_search.pl +++ b/history-search/rl_history_search.pl @@ -1,79 +1,185 @@ -# Search within your typed history as you type (like ctrl-R in bash) -# -# 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) -# -# NOTE: Ctrl-S may not work if you have software flow control configured for -# your terminal. It may appear to freeze irssi entirely. If this happens, it can -# be restored with Ctrl-Q, but you will be unable to use the Ctrl-S binding. -# You can disable flow control by running the command `stty -ixon' in your -# terminal, or setting `defflow off' in your ~/.screenrc if using GNU Screen. -# -# * Hitting enter selects a match and terminates search mode. -# -# * You can use ^G to exit search mode without selecting. -# -# * Any other ctrl- or meta- key binding will terminate search mode, leaving the -#   selected item in the input line. -# -# Original script Copyright 2007  Wouter Coekaerts <coekie@irssi.org> -# Heavy modifications by Shabble <shabble+irssi@metavore.org>, 2010. -# -# 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 +=pod + +=head1 NAME + +rl_history_search.pl + +=head1 DESCRIPTION + +Search within your typed history as you type (something like ctrl-R in bash or other +readline-type applications.) + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<filename>>. + +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. + +=head1 SETUP + +C</bind ^R /history_search_start> + +Where C<^R> is a key of your choice. + +=head1 USAGE + +Type C<ctrl-R> followed by your search string.  The prompt line will show +you the most recent command to match the string you've typed. + +B<Tip:> You can cycle through multiple matches with C<Ctrl-R> (to select older +matches), and <Ctrl-S> (to select newer matches).  Cycling off the end of the +history list returns you to the other end again. + +B<NOTE:> C<Ctrl-S> may not work if you have software flow control configured for +your terminal. It may appear to freeze irssi entirely. If this happens, it can +be restored with C<Ctrl-Q>, but you will be unable to use the C<Ctrl-S> binding. +You can disable flow control by running the command C<stty -ixon> in your +terminal, or setting C<defflow off> in your F<~/.screenrc>if using GNU Screen. + +=head2 COMMANDS + +=over 4 + +=item C<Enter> + +Selects a match and terminates search mode. +B<It will also run the currently selected command.> + +=item C<Ctrl-G> + +Exits search mode without selecting. + +=item C<E<lt>TABE<gt> + +Opens a new split window, showing all matching completions.  C<E<lt>EscE<gt>> +will close the window again, as will any other action that exits history search +mode.  Possible candidates can be cycled through as normal using C<C-r> and +<C-s>. + +=item Any other ctrl- or meta- key + +This will terminate search mode, leaving the selected item in the input line. +It will not run the command (Except for C<Ctrl-J> and C<Ctrl-M>, which are +functionally equivalent to C<Enter>). + +=back + +=head1 AUTHORS + +Original script L<history_search.pl|https://github.com/coekie/irssi-scripts/blob/master/history_search.pl> Copyright E<copy> 2007 Wouter Coekaerts <coekie@irssi.org> + +Most of the other fancy stuff Copyright E<copy> 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 + +Yeah, probably. + +=head1 TODO + +=over 1 + +=item * B<DONE> document tab behaviour + +=item * add keys (C-n/C-p) to scroll history list if necessary. + +=item * B<DONE> if list is bigger than split size, centre it so selected item is visible + +=item * I<INPROG> allow a mechanism to select by number from list + +=item * steal more of the code from ido_switcher to hilight match positions. + +=item * make flex matching optional (key or setting) + +=item * add some online help (? or C-h triggered, maybe?) + +=item * make temp_split stuff more generic (to be used by help, etc) + +=item * figure out why sometimes the split list doesn't update correctly (eg: no matches) + +=item * consider tracking history manually (via send command/send text) + +=over 4 + +=item * Pro: we could timestamp it. + +=item * Con: Would involve catching up/down and all the other history selection mechanisms. + +=item * Compromise - tag history as it comes it (match it with data via sig handlers?) + +=back + +=item * Possibility of saving/restoring history over sessions? + +=back + +=cut +  use strict;  use Irssi;  use Irssi::TextUI;  use Data::Dumper; -use vars qw($VERSION %IRSSI); -$VERSION = '2.0'; -%IRSSI = +our $VERSION = '2.5'; +our %IRSSI =    (     authors     => 'Tom Feist, Wouter Coekaerts',     contact     => 'shabble+irssi@metavore.org, shabble@#irssi/freenode',     name        => 'rl_history_search',     description => 'Search within your typed history as you type' -                . ' (like ctrl-R in readline applications)', +   . ' (like ctrl-R in readline applications)',     license     => 'GPLv2 or later',     url         => 'http://github.com/shabble/irssi-scripts/tree/master/history-search/', -   changed     => '24/7/2010' +   changed     => '14/4/2011', +   requires    => [qw/uberprompt.pl/],    );  my $search_str = '';  my $search_active = 0; +my $select_num_active = 0; +my $num_buffer; +  my @history_cache  = ();  my @search_matches = ();  my $match_index = 0; +# split info +my $split_ref; +my $original_win_ref; + +# formats +my $list_format; + +my $use_flex_match = 1;  my $DEBUG_ENABLED = 0;  sub DEBUG () { $DEBUG_ENABLED } @@ -81,13 +187,7 @@ sub DEBUG () { $DEBUG_ENABLED }  # check we have uberprompt loaded.  sub script_is_loaded { -    my $name = shift; -    print "Checking if $name is loaded" if DEBUG; -    no strict 'refs'; -    my $retval = defined %{ "Irssi::Script::${name}::" }; -    use strict 'refs'; - -    return $retval; +    return exists($Irssi::Script::{$_[0] . '::'}) ;  }  if (not script_is_loaded('uberprompt')) { @@ -103,14 +203,14 @@ if (not script_is_loaded('uberprompt')) {      }      history_init();  } else { -   history_init(); +    history_init();  }  sub load_uberprompt_failed {      Irssi::signal_remove('script error', 'load_prompt_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(" ", @_); @@ -124,6 +224,7 @@ sub history_init {      Irssi::signal_add      ('setup changed'   => \&setup_changed);      Irssi::signal_add_first('gui key pressed' => \&handle_keypress); +    $list_format = Irssi::theme_register([ list_format => '$*' ]);      setup_changed();  } @@ -131,29 +232,34 @@ sub setup_changed {      $DEBUG_ENABLED = Irssi::settings_get_bool('histsearch_debug');  } +sub temp_split_active () { +    return defined $split_ref; +}  sub history_search {      $search_active = 1;      $search_str = ''; -    $match_index = -1; +    $match_index = 0; -    @history_cache = Irssi::active_win()->get_history_lines(); +    my $win = Irssi::active_win; +    @history_cache = $win->get_history_lines();      @search_matches = (); +    $original_win_ref = $win; + +    update_history_matches();      update_history_prompt();  }  sub history_exit {      $search_active = 0; -    Irssi::signal_emit('change prompt', '', 'UP_INNER'); +    close_temp_split(); +    _set_prompt('');  }  sub update_history_prompt {      my $col = scalar(@search_matches) ? '%g' : '%r'; -    Irssi::signal_emit('change prompt', -                       ' reverse-i-search: `' . $col . $search_str -                       . '%n' . "'", -                       'UP_INNER'); +    _set_prompt(' reverse-i-search: `' . $col . $search_str . '%n' . "'");  }  sub update_history_matches { @@ -161,11 +267,18 @@ sub update_history_matches {      $match_str = $search_str unless defined $match_str;      my %unique; -    my @matches = grep { m/\Q$match_str/i } @history_cache; +    my @matches; + +    if ($use_flex_match) { +        @matches = grep { flex_match($_) >= 0 } @history_cache; +    } else { +        @matches = grep { m/\Q$match_str/i } @history_cache; +    }      @search_matches = ();      # uniquify the results, whilst maintaining order. +    # TODO: duplicates should keep teh most recent one?      foreach my $m (@matches) {          unless (exists($unique{$m})) {              # add them in reverse order. @@ -178,6 +291,80 @@ sub update_history_matches {        join(", ", @search_matches) if DEBUG;  } +sub flex_match { +    my ($obj) = @_; + +    my $pattern = $search_str; +    my $source  = $obj;         #->{name}; + +    #_debug("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 enter_select_num_mode { +    # require that the list be shown. +    return unless temp_split_active(); +    return if $select_num_active; # TODO: should we prevent restarting? + +    $num_buffer = 0; +    _set_prompt("Num select: "); +    $select_num_active = 1; +} + +sub exit_select_num_mode { + +    $select_num_active = 0; +    $num_buffer = 0; +    update_history_prompt(); +} + +sub history_select_num { +    if ($num_buffer > 0 && $num_buffer <= $#search_matches) { +        $match_index = $num_buffer; +        my $match = get_history_match(); +        _debug("Num select: got $match"); +    } +} +  sub get_history_match {      return $search_matches[$match_index];  } @@ -211,65 +398,116 @@ sub handle_keypress {      return unless $search_active; -	if ($key == 10) { # enter +    if ($select_num_active) { +        if ($key >= 48 and $key <= 57) { # Number key + +            $num_buffer = ($num_buffer * 10) + $key - 48; +            _set_prompt("Num select: $num_buffer"); + +        } elsif ($key == 10) {  # ENTER + +            history_select_num(); +            update_input(); +            exit_select_num_mode(); +            history_exit(); + +        } else {                # anything else quits. +            exit_select_num_mode(); +        } +        Irssi::signal_stop(); +        return; +    } + +    if ($key == 6) {            # Ctrl-F +        enter_select_num_mode(); +        Irssi::signal_stop(); +        return; +    } + +    if ($key == 7) {            # Ctrl-G +        print "aborting search" if DEBUG; +        history_exit(); + +        # cancel empties the inputline. +        Irssi::gui_input_set(''); +        Irssi::gui_input_set_pos(0); + +        Irssi::signal_stop(); +        return; +    } +    if ($key == 8) {            # C-h +        $original_win_ref->print("This would show help, " +                                 . "if there was any. Coming soon!"); +    } + +    if ($key == 9) {            # TAB +        update_history_matches(); +        if (not temp_split_active()) { +            create_temp_split() if @search_matches > 0; +        } else { +            print_current_matches(); +        } + +        Irssi::signal_stop(); +        return; +    } +	if ($key == 10) {           # enter          print "selecting history and quitting" if DEBUG;          history_exit();          return;  	} -    if ($key == 18) { # Ctrl-R +    if ($key == 18) {           # Ctrl-R          print "skipping to prev match" if DEBUG;          prev_match();          update_input();          update_history_prompt(); -        Irssi::signal_stop(); # prevent the bind from being re-triggered. +        print_current_matches(); +        Irssi::signal_stop();   # prevent the bind from being re-triggered.          return;      } -    if ($key == 19) { # Ctrl-S +    if ($key == 19) {           # Ctrl-S          print "skipping to next match" if DEBUG;          next_match();          update_input();          update_history_prompt(); +        print_current_matches();          Irssi::signal_stop();          return;      } -    if ($key == 7) { # Ctrl-G -        print "aborting search" if DEBUG; -        history_exit(); - -        # cancel empties the inputline. -        Irssi::gui_input_set(''); -        Irssi::gui_input_set_pos(0); +    # TODO: handle arrow-keys? +    if ($key == 27) { +        close_temp_split();          Irssi::signal_stop();          return;      } -    if ($key == 127) { # DEL +    if ($key >= 32 and $key < 127) { # printable +        $search_str .= chr($key); -        if (length $search_str) { -            $search_str = substr($search_str, 0, -1); -            print "Deleting char, now: $search_str" if DEBUG; -        }          update_history_matches();          update_history_prompt();          update_input(); +        print_current_matches();          Irssi::signal_stop();          return;      } -    # TODO: handle esc- sequences and arrow-keys? - -    if ($key >= 32) { # printable -        $search_str .= chr($key); +    if ($key == 127) {          # DEL +        if (length $search_str) { +            $search_str = substr($search_str, 0, -1); +            print "Deleting char, now: $search_str" if DEBUG; +        }          update_history_matches();          update_history_prompt();          update_input(); +        print_current_matches();          Irssi::signal_stop();          return; @@ -279,3 +517,134 @@ sub handle_keypress {      history_exit();      #Irssi::signal_stop();  } + +sub create_temp_split { + +    Irssi::signal_add_first('window created', 'sig_win_created'); +    Irssi::command('window new split'); +    Irssi::signal_remove('window created', 'sig_win_created'); +} + +sub close_temp_split { + +    if (temp_split_active()) { +        Irssi::command("window close $split_ref->{refnum}"); +        undef $split_ref; +    } + +    # restore original window focus +    if (Irssi::active_win()->{refnum} != $original_win_ref->{refnum}) { +        $original_win_ref->set_active(); +    } +} + +sub sig_win_created { +    my ($win) = @_; +    $split_ref = $win; +    # printing directly from this handler causes irssi to segfault. +    Irssi::timeout_add_once(10, \&print_current_matches, {}); +} + +sub print_current_matches { + +    return unless temp_split_active(); + +    my $num_matches = scalar(@search_matches); +    #return unless $num_matches > 0; + +    # for some woefully unobvious reason, we need to refetch +    # the window reference in order for its attribute hash +    # to be regenerated. +    my $s_win = Irssi::window_find_refnum($split_ref->{refnum}); + +    my $split_height = $s_win->{height}; + +    $s_win->command("^scrollback clear"); + +    # disable timestamps to ensure a clean window. +    my $orig_ts_level = Irssi::parse_special('$timestamp_level'); +    $s_win->command("^set timestamp_level $orig_ts_level -CLIENTCRAP"); + + +    $original_win_ref->print("Num matches: $num_matches, height: $split_height") +      if DEBUG; + +    # print header +    # TODO: make this a format? +    $s_win->print('%_Current history matches. Press <esc> to close.%_', +                  MSGLEVEL_CLIENTCRAP | MSGLEVEL_NEVER); + +    if ($num_matches == 0) { +        $s_win->print('(No Matches)', MSGLEVEL_CLIENTCRAP | MSGLEVEL_NEVER); +        return; +    } +    $split_height -= 2;         # account for header line; + +    my $hist_entry = get_history_match(); + +    my ($start, $end); + +    if ($num_matches > $split_height) { +        # we have too many matches to fit in the window. decide on a new +        # start and end point. + +        my $half_height = int ($split_height / 2); + +        # initial start pos is in the middle of the screen. +        $start = $match_index >= $half_height +          ? $match_index - $half_height +          : 0; +        # and ends with the max number of matches we can fit +        $end   = $start + $split_height > $num_matches - 1 +          ? $num_matches - 1 +          : $start + $split_height; + +        # readjust start if the screen isn't filled. +        if ($end - $start < $split_height) { +            $start = $end - $split_height; +        } + +        _debug("sh: $split_height, hh: $half_height, " +               . "mi: $match_index, start: $start, end: $end"); +    } else { +        $start = 0; +        $end   = $#search_matches; +    } + +    foreach my $i ($start..$end) { +        my $j =  $num_matches - $i; +        my $entry = $search_matches[$i]; + +        my $hilight = $hist_entry eq $entry +          ? '%g' +          : ''; +        $hilight = Irssi::parse_special($hilight); +        my $str = sprintf("%s%-6d %s%%n", $hilight, $j, $entry); +        $s_win->print($str, MSGLEVEL_CLIENTCRAP|MSGLEVEL_NEVER); +    } + +    # restore timestamp settings. +    $s_win->command("^set timestamp_level $orig_ts_level"); +} + +sub print_help { + +} + +sub _print_to_active { +    my ($msg, @args) = @_; +    my $str = sprintf($msg, @args); +    $original_win_ref->print($str,MSGLEVEL_CLIENTCRAP | MSGLEVEL_NEVER); +} + +sub _debug { +    return unless DEBUG; +    my ($msg, @args) = @_; +    my $str = sprintf($msg, @args); +    $original_win_ref->print($str, MSGLEVEL_CLIENTCRAP | MSGLEVEL_NEVER); +} +sub _set_prompt { +    my ($str) = @_; +    $str = ' ' . $str if length $str; +    Irssi::signal_emit('change prompt', $str, 'UP_INNER'); +} diff --git a/ido-mode/README.pod b/ido-mode/README.pod new file mode 100644 index 0000000..62f7190 --- /dev/null +++ b/ido-mode/README.pod @@ -0,0 +1,247 @@ +=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> or C<-flex> 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 + diff --git a/ido-mode/ido_switcher.pl b/ido-mode/ido_switcher.pl index 5576b32..8194f8a 100644 --- a/ido-mode/ido_switcher.pl +++ b/ido-mode/ido_switcher.pl @@ -1,87 +1,256 @@ -# 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 +=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> or C<-flex> 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 Irssi;  use Irssi::TextUI;  use Data::Dumper; -use vars qw($VERSION %IRSSI); -$VERSION = '2.0'; -%IRSSI = + +our $VERSION = '2.1'; +our %IRSSI   =    (     authors     => 'Tom Feist, Wouter Coekaerts',     contact     => 'shabble+irssi@metavore.org, shabble@#irssi/freenode', @@ -93,22 +262,10 @@ $VERSION = '2.0';    ); -# 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 $CMD_NAME = 'ido_switch_start'; +my $CMD_OPTS = '-channels -queries -all -active -exact -flex'; +  my $input_copy     = '';  my $input_pos_copy = 0; @@ -129,6 +286,7 @@ 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; @@ -152,22 +310,25 @@ sub _print {      my $win = Irssi::active_win;      my $str = join('', @_);      $need_clear = 1; -    $win->print($str, Irssi::MSGLEVEL_NEVER); +    $win->print($str, MSGLEVEL_NEVER);  }  sub _debug_print {      return unless DEBUG;      my $win = Irssi::active_win;      my $str = join('', @_); -    $win->print($str, Irssi::MSGLEVEL_CLIENTCRAP); +    $win->print($str, MSGLEVEL_CLIENTCRAP);  }  sub _print_clear {      return unless $need_clear;      my $win = Irssi::active_win(); -    $win->command('/scrollback levelclear -level NEVER'); +    $win->command('/^scrollback levelclear -level NEVER');  } +# 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 = @@ -242,15 +403,9 @@ sub print_all_matches {      #_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; -  } +sub script_is_loaded { +    return exists($Irssi::Script::{$_[0] . '::'}); +}  unless (script_is_loaded('uberprompt')) { @@ -275,11 +430,14 @@ sub load_uberprompt_failed {  }  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::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::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); @@ -288,28 +446,52 @@ sub ido_switch_init {  }  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'); +    $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 { + +    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(''); -    # set startup flags +    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_type         = 'ALL'; -    # refresh in case we toggled it last time. -    $ido_use_flex   = Irssi::settings_get_bool('ido_use_flex'); -    $active_only    = 0; +    # configure settings from provided arguments. + +    # use provided options first, or fall back to /setting. +    $ido_use_flex = exists $options->{exact} +      ? 0 +      : exists $options->{flex} +      ? 1 +      : Irssi::settings_get_bool('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); @@ -370,7 +552,12 @@ sub get_all_windows {                  push @ret, { _build_win_obj($win, $item) };              }          } else { -            _debug_print "Error occurred reading info from window: $win"; +            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);          }      } @@ -384,17 +571,24 @@ sub get_all_windows {          my $list_ref = shift;          my @ret = @$list_ref; -        @ret = sort { $a->{num} <=> $b->{num} } @ret; +        @ret = sort { $a->{num} <=> $b->{num}  } @ret; +        if ($sort_active_first) { +            my @active   = grep {     $_->{active} } @ret; +            my @inactive = grep { not $_->{active} } @ret; -        return @ret; +            return (@active, @inactive); +        } else { +            return @ret; +        }      }      sub ido_switch_select { -        my ($selected) = @_; +        my ($selected, $tag) = @_; -        _debug_print "Selecting window: " . $selected->{name}; +        _debug_print sprintf("Selecting window: %s (%d)", +                             $selected->{name}, $selected->{num}); -        Irssi::command("WINDOW GOTO " . $selected->{name}); +        Irssi::command("WINDOW GOTO " . $selected->{num});          if ($selected->{type} ne 'WIN') {              _debug_print "Selecting window item: " . $selected->{itemname}; @@ -424,7 +618,7 @@ sub get_all_windows {          # 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'; +        my $match_string = '[No matches]';          $show_count = $match_count if $match_count < $show_count; @@ -486,7 +680,7 @@ sub get_all_windows {          push @indicators, 'Active' if $active_only;          push @indicators, ucfirst(lc($mode_type)); -        my $flex = sprintf(' %%k[%%n%s%%k]%%n ', join ',', @indicators); +        my $flex = sprintf(' %%b[%%n%s%%b]%%n ', join ', ', @indicators);          my $search = '';          $search = (sprintf '`%s\': ', $search_str) if length $search_str; @@ -539,7 +733,7 @@ sub get_all_windows {          if ($mode_type ne 'ALL') {              @mode_cache = @window_cache; -            @window_cache = grep { print "Type: " . $_->{type}; $_->{type} eq $mode_type } @window_cache; +            @window_cache = grep { $_->{type} eq $mode_type } @window_cache;          } else {              @window_cache = @mode_cache if @mode_cache;          } @@ -718,6 +912,16 @@ sub get_all_windows {              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"; diff --git a/joinforward/joinforward.pl b/joinforward/joinforward.pl new file mode 100644 index 0000000..1acd72a --- /dev/null +++ b/joinforward/joinforward.pl @@ -0,0 +1,65 @@ +use strict; +use warnings; + + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => 'joinforward.pl', +              description => '', +              license     => 'Public Domain', +             ); + +my $forwards; + + +init(); + + +sub init { +    Irssi::signal_add('event 470', 'sig_470'); # forwarding (on freenode, anhywya) +    Irssi::signal_add('event 473', 'sig_473'); # notinvited. +    # or better to just overload /join? +    Irssi::command_bind('fwdlist', 'cmd_fwdlist'); +    print "Joinforward loaded"; +} + +sub cmd_fwdlist { +    print "Known Forwards:"; +    foreach my $fwd (sort keys %$forwards) { +        print "$fwd -> " . $forwards->{$fwd}; +    } +} + +sub sig_470 { +    my ($server, $args, $sender) = @_; +    #'shibble #mac ##mac :Forwarding to another channel', +    print "Sig 470: $args"; +    if ($args =~ m/(#.*?)\s+(#.*?)/) { +        $forwards->{$1} = $2; +        print "adding $1 -> $2"; +    } +} + +sub sig_473 { +    my ($server, $args, $sender) = @_; +    print "Sig 473: $args"; +    #" shibble #mac :Cannot join channel (+i) - you must be invited'," +    if ($server->{version} =~ m/ircd-seven/) { # assume freenode +        if ($args =~ m/^(#.*?)\s+/) { +            if (exists $forwards->{$1}) { +                $server->command("join " . $forwards->{$1}); +            } +        } + + +    } +} diff --git a/modules/key_emitter/COPYING b/modules/key_emitter/COPYING new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/modules/key_emitter/COPYING @@ -0,0 +1,339 @@ +		    GNU GENERAL PUBLIC LICENSE +		       Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +			    Preamble + +  The licenses for most software are designed to take away your +freedom to share and change it.  By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users.  This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it.  (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.)  You can apply it to +your programs, too. + +  When we speak of free software, we are referring to freedom, not +price.  Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +  To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +  For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have.  You must make sure that they, too, receive or can get the +source code.  And you must show them these terms so they know their +rights. + +  We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +  Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software.  If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +  Finally, any free program is threatened constantly by software +patents.  We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary.  To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + +  The precise terms and conditions for copying, distribution and +modification follow. + +		    GNU GENERAL PUBLIC LICENSE +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +  0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License.  The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language.  (Hereinafter, translation is included without limitation in +the term "modification".)  Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope.  The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +  1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +  2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +    a) You must cause the modified files to carry prominent notices +    stating that you changed the files and the date of any change. + +    b) You must cause any work that you distribute or publish, that in +    whole or in part contains or is derived from the Program or any +    part thereof, to be licensed as a whole at no charge to all third +    parties under the terms of this License. + +    c) If the modified program normally reads commands interactively +    when run, you must cause it, when started running for such +    interactive use in the most ordinary way, to print or display an +    announcement including an appropriate copyright notice and a +    notice that there is no warranty (or else, saying that you provide +    a warranty) and that users may redistribute the program under +    these conditions, and telling the user how to view a copy of this +    License.  (Exception: if the Program itself is interactive but +    does not normally print such an announcement, your work based on +    the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole.  If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works.  But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +  3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +    a) Accompany it with the complete corresponding machine-readable +    source code, which must be distributed under the terms of Sections +    1 and 2 above on a medium customarily used for software interchange; or, + +    b) Accompany it with a written offer, valid for at least three +    years, to give any third party, for a charge no more than your +    cost of physically performing source distribution, a complete +    machine-readable copy of the corresponding source code, to be +    distributed under the terms of Sections 1 and 2 above on a medium +    customarily used for software interchange; or, + +    c) Accompany it with the information you received as to the offer +    to distribute corresponding source code.  (This alternative is +    allowed only for noncommercial distribution and only if you +    received the program in object code or executable form with such +    an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it.  For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable.  However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +  4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License.  Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +  5. You are not required to accept this License, since you have not +signed it.  However, nothing else grants you permission to modify or +distribute the Program or its derivative works.  These actions are +prohibited by law if you do not accept this License.  Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +  6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions.  You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +  7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License.  If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all.  For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices.  Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +  8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded.  In such case, this License incorporates +the limitation as if written in the body of this License. + +  9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time.  Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number.  If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation.  If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +  10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission.  For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this.  Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + +			    NO WARRANTY + +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +		     END OF TERMS AND CONDITIONS + +	    How to Apply These Terms to Your New Programs + +  If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +  To do so, attach the following notices to the program.  It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + +    <one line to give the program's name and a brief idea of what it does.> +    Copyright (C) <year>  <name of author> + +    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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + +    Gnomovision version 69, Copyright (C) year name of author +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. +    This is free software, and you are welcome to redistribute it +    under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License.  Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary.  Here is a sample; alter the names: + +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program +  `Gnomovision' (which makes passes at compilers) written by James Hacker. + +  <signature of Ty Coon>, 1 April 1989 +  Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs.  If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library.  If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/modules/key_emitter/Makefile b/modules/key_emitter/Makefile new file mode 100644 index 0000000..d7e3dd5 --- /dev/null +++ b/modules/key_emitter/Makefile @@ -0,0 +1,57 @@ + +### Edit these parameters ### + +# change this to 'find' if you're on a decent system. +FIND = gfind # stupid OSX non-gnu defaults. + +# Where your irssi include files live. You might need to install an +# 'irssi-dev' package or something like that. +IRSSI_DIST = /opt/stow/repo/irssi-debug/include/irssi + +# probably $(HOME)/.irssi for most people. +IRSSI_USER_DIR = $(HOME)/projects/tmp/test/irssi-debug +MODULE_NAME = key_emitter + +### You shouldn't need to edit anything beyond this point ### + +LIB_NAME = lib$(MODULE_NAME).so +CFLAGS = -Wall -O2 -Werror -g -DMODULE_NAME=\"$(MODULE_NAME)\" +LDFLAGS = -avoid-version -module -bundle -flat_namespace -undefined suppress + +# When you start adding more components to your module, add them here. +OBJECTS = key_emitter_core.o \ +          key_emitter_impl.o + +IRSSI_INCLUDE = -I$(IRSSI_DIST) \ +				-I$(IRSSI_DIST)/src \ +				-I$(IRSSI_DIST)/src/fe-common/core \ +				-I$(IRSSI_DIST)/src/core \ +				-I$(IRSSI_DIST)/src/fe-text \ +				-I$(IRSSI_DIST)/src/irc \ +				-I$(IRSSI_DIST)/src/irc/core \ +				-I$(IRSSI_DIST)/src/irc/dcc \ +				-I$(IRSSI_DIST)/src/irc/notifylist + + +GLIB_CFLAGS = $(shell pkg-config glib-2.0 --cflags) + +all: $(LIB_NAME) + +%.o: %.c Makefile +	$(CC) $(CFLAGS) $(GLIB_CFLAGS) $(IRSSI_INCLUDE) -I. -fPIC -c $< + +$(LIB_NAME): $(OBJECTS) +	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o $@ + +install: $(LIB_NAME) +	install $< $(IRSSI_USER_DIR)/modules + +clean: +	rm -rf *~ *.o *.so core || true + +TAGS: +	$(FIND) -type f -exec etags -a -o TAGS {} \; + +.default: all + +.phony: clean install TAGS diff --git a/modules/key_emitter/README.md b/modules/key_emitter/README.md new file mode 100644 index 0000000..6377a39 --- /dev/null +++ b/modules/key_emitter/README.md @@ -0,0 +1,36 @@ +## What is this? + +A loadable module which processes keybindings and emits easier to handle +signals for scripting. + +## Usage + +* Edit the Makefile as per the comments at the top. +* `make && make install` +* Start up a spare Irssi client (no point segfaulting your main one) +* `/load key_emitter` +* Party in the streets. + +## Contributing + +Contributions to this project are welcome (i.e.: to demonstrate more useful +things that a module can do) + + * Patches can be submitted via e-mail, or preferably via forking +   and sending a pull-request using GitHub.  The repository for this +   code is `git://github.com/shabble/irssi-scripts.git` and the +   corresponding web-page is +   [irssi-scripts/modules](https://github.com/shabble/irssi-scripts/modules). +    +   Details of pull-requests can be found at +   [GitHub](http://help.github.com/pull-requests/) +    + . Please poke me on Freenode IRC (`shabble` on `#irssi`) before +   spending too much time on the code. Use the `git format-patch`-tool when +   emailing patches. + +## Authors + + * Tom Feist [shabble+irssi@metavore.org](mailto://shabble+irssi@metavore.org) +     Chief plunderer of the modular goodness. +  diff --git a/modules/key_emitter/key_emitter_core.c b/modules/key_emitter/key_emitter_core.c new file mode 100644 index 0000000..196dd70 --- /dev/null +++ b/modules/key_emitter/key_emitter_core.c @@ -0,0 +1,34 @@ + +/* Originally this code was part of the irssi-lua module + * <https://github.com/ahf/irssi-lua> and copyright as below: + * + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <key_emitter_core.h> +#include <key_emitter_irssi.h> +#include <key_emitter_impl.h> + +void test_init() { +    module_register(MODULE_NAME, "core"); +    print_load_message(); +} + +void test_deinit() { +    print_unload_message(); +} diff --git a/modules/key_emitter/key_emitter_core.h b/modules/key_emitter/key_emitter_core.h new file mode 100644 index 0000000..e6210d8 --- /dev/null +++ b/modules/key_emitter/key_emitter_core.h @@ -0,0 +1,27 @@ +/* vim: set sw=4 sts=4 et foldmethod=syntax : */ + +/* + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUARD_KEY_EMITTER_CORE_H +#define GUARD_KEY_EMITTER_CORE_H 1 + +void key_emitter_init(); +void key_emitter_deinit(); + +#endif diff --git a/modules/key_emitter/key_emitter_impl.c b/modules/key_emitter/key_emitter_impl.c new file mode 100644 index 0000000..cd2055e --- /dev/null +++ b/modules/key_emitter/key_emitter_impl.c @@ -0,0 +1,18 @@ +#include <key_emitter_irssi.h> +#include <key_emitter_impl.h> +#include <key_emitter_core.h> + +void print_load_message(void) { + +    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, +              "Hello, World. xxx \"%s\"", MODULE_NAME); + +} + +void print_unload_message(void) { + +    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, +              "Goodbye, Cruel World. ~signed \"%s\"", MODULE_NAME); + +} + diff --git a/modules/key_emitter/key_emitter_impl.h b/modules/key_emitter/key_emitter_impl.h new file mode 100644 index 0000000..17f0148 --- /dev/null +++ b/modules/key_emitter/key_emitter_impl.h @@ -0,0 +1,7 @@ +#ifndef _KEY_EMITTER_IMPL_H_ +#define _KEY_EMITTER_IMPL_H_ 1 + +void print_load_message(void); +void print_unload_message(void); + +#endif /* _KEY_EMITTER_IMPL_H_ */ diff --git a/modules/key_emitter/key_emitter_irssi.h b/modules/key_emitter/key_emitter_irssi.h new file mode 100644 index 0000000..4fd2a2e --- /dev/null +++ b/modules/key_emitter/key_emitter_irssi.h @@ -0,0 +1,51 @@ + +/* + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUARD_TEST_IRSSI_H +#define GUARD_TEST_IRSSI_H 1 + +/* FIXME: bug */ +#define UOFF_T_LONG_LONG 1 + +/* #include "config.h" */ +#include "core.h" +#include "common.h" +#include "modules.h" +#include "commands.h" +#include "settings.h" +#include "printtext.h" +#include "window-items.h" +#include "window-activity.h" +#include "levels.h" +#include "servers.h" +#include "chat-protocols.h" +#include "channels.h" +#include "queries.h" +#include "nicklist.h" +#include "chatnets.h" +#include "servers-reconnect.h" +#include "masks.h" +#include "misc.h" +#include "rawlog.h" +#include "log.h" +#include "ignore.h" +#include "fe-exec.h" +#include "pidwait.h" + +#endif diff --git a/modules/overlays/COPYING b/modules/overlays/COPYING new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/modules/overlays/COPYING @@ -0,0 +1,339 @@ +		    GNU GENERAL PUBLIC LICENSE +		       Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +			    Preamble + +  The licenses for most software are designed to take away your +freedom to share and change it.  By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users.  This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it.  (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.)  You can apply it to +your programs, too. + +  When we speak of free software, we are referring to freedom, not +price.  Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +  To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +  For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have.  You must make sure that they, too, receive or can get the +source code.  And you must show them these terms so they know their +rights. + +  We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +  Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software.  If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +  Finally, any free program is threatened constantly by software +patents.  We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary.  To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + +  The precise terms and conditions for copying, distribution and +modification follow. + +		    GNU GENERAL PUBLIC LICENSE +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +  0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License.  The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language.  (Hereinafter, translation is included without limitation in +the term "modification".)  Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope.  The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +  1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +  2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +    a) You must cause the modified files to carry prominent notices +    stating that you changed the files and the date of any change. + +    b) You must cause any work that you distribute or publish, that in +    whole or in part contains or is derived from the Program or any +    part thereof, to be licensed as a whole at no charge to all third +    parties under the terms of this License. + +    c) If the modified program normally reads commands interactively +    when run, you must cause it, when started running for such +    interactive use in the most ordinary way, to print or display an +    announcement including an appropriate copyright notice and a +    notice that there is no warranty (or else, saying that you provide +    a warranty) and that users may redistribute the program under +    these conditions, and telling the user how to view a copy of this +    License.  (Exception: if the Program itself is interactive but +    does not normally print such an announcement, your work based on +    the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole.  If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works.  But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +  3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +    a) Accompany it with the complete corresponding machine-readable +    source code, which must be distributed under the terms of Sections +    1 and 2 above on a medium customarily used for software interchange; or, + +    b) Accompany it with a written offer, valid for at least three +    years, to give any third party, for a charge no more than your +    cost of physically performing source distribution, a complete +    machine-readable copy of the corresponding source code, to be +    distributed under the terms of Sections 1 and 2 above on a medium +    customarily used for software interchange; or, + +    c) Accompany it with the information you received as to the offer +    to distribute corresponding source code.  (This alternative is +    allowed only for noncommercial distribution and only if you +    received the program in object code or executable form with such +    an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it.  For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable.  However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +  4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License.  Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +  5. You are not required to accept this License, since you have not +signed it.  However, nothing else grants you permission to modify or +distribute the Program or its derivative works.  These actions are +prohibited by law if you do not accept this License.  Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +  6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions.  You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +  7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License.  If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all.  For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices.  Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +  8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded.  In such case, this License incorporates +the limitation as if written in the body of this License. + +  9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time.  Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number.  If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation.  If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +  10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission.  For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this.  Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + +			    NO WARRANTY + +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +		     END OF TERMS AND CONDITIONS + +	    How to Apply These Terms to Your New Programs + +  If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +  To do so, attach the following notices to the program.  It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + +    <one line to give the program's name and a brief idea of what it does.> +    Copyright (C) <year>  <name of author> + +    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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + +    Gnomovision version 69, Copyright (C) year name of author +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. +    This is free software, and you are welcome to redistribute it +    under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License.  Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary.  Here is a sample; alter the names: + +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program +  `Gnomovision' (which makes passes at compilers) written by James Hacker. + +  <signature of Ty Coon>, 1 April 1989 +  Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs.  If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library.  If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/modules/overlays/Makefile b/modules/overlays/Makefile new file mode 100644 index 0000000..ba5d787 --- /dev/null +++ b/modules/overlays/Makefile @@ -0,0 +1,59 @@ + +### Edit these parameters ### + +# change this to 'find' if you're on a decent system. +FIND = gfind # stupid OSX non-gnu defaults. + +# Where your irssi include files live. You might need to install an +# 'irssi-dev' package or something like that. +IRSSI_DIST = /opt/stow/repo/irssi-debug/include/irssi + +# probably $(HOME)/.irssi for most people. +IRSSI_USER_DIR = $(HOME)/projects/tmp/test/irssi-debug +MODULE_NAME = overlay + +### You shouldn't need to edit anything beyond this point ### + +LIB_NAME = lib$(MODULE_NAME).so +CFLAGS = -Wall -O2 -Werror -g -DMODULE_NAME=\"$(MODULE_NAME)\" +LDFLAGS = -avoid-version -module -bundle -flat_namespace -undefined suppress + +# When you start adding more components to your module, add them here. +OBJECTS = overlay_core.o \ +          overlay_impl.o + +IRSSI_INC = $(HOME)/sources/irssi-git + +IRSSI_INCLUDE  = -I$(IRSSI_INC) \ +			 	 -I$(IRSSI_INC)/src \ +				 -I$(IRSSI_INC)/src/fe-common/core \ +				 -I$(IRSSI_INC)/src/core \ +				 -I$(IRSSI_INC)/src/fe-text \ +				 -I$(IRSSI_INC)/src/irc \ +				 -I$(IRSSI_INC)/src/irc/core \ +				 -I$(IRSSI_INC)/src/irc/dcc \ +				 -I$(IRSSI_INC)/src/irc/notifylist + + +GLIB_CFLAGS = $(shell pkg-config glib-2.0 --cflags) + +all: $(LIB_NAME) + +%.o: %.c Makefile +	$(CC) $(CFLAGS) $(GLIB_CFLAGS) $(IRSSI_INCLUDE) -I. -fPIC -c $< + +$(LIB_NAME): $(OBJECTS) +	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o $@ + +install: $(LIB_NAME) +	install $< $(IRSSI_USER_DIR)/modules + +clean: +	rm -rf *~ *.o *.so core || true + +TAGS: +	$(FIND) -type f -exec etags -a -o TAGS {} \; + +.default: all + +.phony: clean install TAGS diff --git a/modules/overlays/README.md b/modules/overlays/README.md new file mode 100644 index 0000000..3fdbae7 --- /dev/null +++ b/modules/overlays/README.md @@ -0,0 +1,10 @@ +## What is this? + +A thing that allows mroe direct access to windows and curses information within irssi, via various +signals. + +## Authors + + * Tom Feist [shabble+irssi@metavore.org](mailto://shabble+irssi@metavore.org) +     Chief plunderer of the modular goodness. +  diff --git a/modules/overlays/overlay_core.c b/modules/overlays/overlay_core.c new file mode 100644 index 0000000..590a1be --- /dev/null +++ b/modules/overlays/overlay_core.c @@ -0,0 +1,46 @@ + +/* Originally this code was part of the irssi-lua module + * <https://github.com/ahf/irssi-lua> and copyright as below: + * + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <overlay_core.h> +#include <overlay_irssi.h> +#include <overlay_impl.h> +#include <terminfo-core.h> + +static void cmd_overlay(const char *data, SERVER_REC *server) { + +    terminfo_move(current_term->width, current_term->height); +    //terminfo_clear(); +    print_random_message("What is going on here?"); +} + +void overlay_init() { +    module_register(MODULE_NAME, "core"); +    print_load_message(); + +    command_bind("overlay", NULL, (SIGNAL_FUNC) cmd_overlay); +} + + +void overlay_deinit() { +    command_unbind("overlay", (SIGNAL_FUNC) cmd_overlay); +    print_unload_message(); +} diff --git a/modules/overlays/overlay_core.h b/modules/overlays/overlay_core.h new file mode 100644 index 0000000..39fc743 --- /dev/null +++ b/modules/overlays/overlay_core.h @@ -0,0 +1,27 @@ +/* vim: set sw=4 sts=4 et foldmethod=syntax : */ + +/* + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUARD_KEY_EMITTER_CORE_H +#define GUARD_KEY_EMITTER_CORE_H 1 + +void overlay_init(); +void overlay_deinit(); + +#endif diff --git a/modules/overlays/overlay_impl.c b/modules/overlays/overlay_impl.c new file mode 100644 index 0000000..dbb9748 --- /dev/null +++ b/modules/overlays/overlay_impl.c @@ -0,0 +1,23 @@ +#include <overlay_irssi.h> +#include <overlay_impl.h> +#include <overlay_core.h> + +void print_load_message(void) { + +    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, +              "Hello, World, ~~ \"%s\"", MODULE_NAME); + +} + +void print_random_message(char *str) { +    printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, +              "%s", str); +} + +void print_unload_message(void) { + +    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, +              "Goodbye, Cruel World. ~~ \"%s\"", MODULE_NAME); + +} + diff --git a/modules/overlays/overlay_impl.h b/modules/overlays/overlay_impl.h new file mode 100644 index 0000000..9f2d6ee --- /dev/null +++ b/modules/overlays/overlay_impl.h @@ -0,0 +1,9 @@ +#ifndef _OVERLAY_IMPL_H_ +#define _OVERLAY_IMPL_H_ 1 + +void print_load_message(void); +void print_unload_message(void); +void print_random_message(char *str); + + +#endif /* _OVERLAY_IMPL_H_ */ diff --git a/modules/overlays/overlay_irssi.h b/modules/overlays/overlay_irssi.h new file mode 100644 index 0000000..8c80623 --- /dev/null +++ b/modules/overlays/overlay_irssi.h @@ -0,0 +1,52 @@ + +/* + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUARD_TEST_IRSSI_H +#define GUARD_TEST_IRSSI_H 1 + +/* FIXME: bug */ +#define UOFF_T_LONG_LONG 1 + +/* #include "config.h" */ + +#include "core.h" +#include "common.h" +#include "modules.h" +#include "commands.h" +#include "settings.h" +#include "printtext.h" +#include "window-items.h" +#include "window-activity.h" +#include "levels.h" +#include "servers.h" +#include "chat-protocols.h" +#include "channels.h" +#include "queries.h" +#include "nicklist.h" +#include "chatnets.h" +#include "servers-reconnect.h" +#include "masks.h" +#include "misc.h" +#include "rawlog.h" +#include "log.h" +#include "ignore.h" +#include "fe-exec.h" +#include "pidwait.h" + +#endif diff --git a/modules/perl_plus/COPYING b/modules/perl_plus/COPYING new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/modules/perl_plus/COPYING @@ -0,0 +1,339 @@ +		    GNU GENERAL PUBLIC LICENSE +		       Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +			    Preamble + +  The licenses for most software are designed to take away your +freedom to share and change it.  By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users.  This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it.  (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.)  You can apply it to +your programs, too. + +  When we speak of free software, we are referring to freedom, not +price.  Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +  To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +  For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have.  You must make sure that they, too, receive or can get the +source code.  And you must show them these terms so they know their +rights. + +  We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +  Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software.  If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +  Finally, any free program is threatened constantly by software +patents.  We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary.  To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + +  The precise terms and conditions for copying, distribution and +modification follow. + +		    GNU GENERAL PUBLIC LICENSE +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +  0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License.  The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language.  (Hereinafter, translation is included without limitation in +the term "modification".)  Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope.  The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +  1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +  2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +    a) You must cause the modified files to carry prominent notices +    stating that you changed the files and the date of any change. + +    b) You must cause any work that you distribute or publish, that in +    whole or in part contains or is derived from the Program or any +    part thereof, to be licensed as a whole at no charge to all third +    parties under the terms of this License. + +    c) If the modified program normally reads commands interactively +    when run, you must cause it, when started running for such +    interactive use in the most ordinary way, to print or display an +    announcement including an appropriate copyright notice and a +    notice that there is no warranty (or else, saying that you provide +    a warranty) and that users may redistribute the program under +    these conditions, and telling the user how to view a copy of this +    License.  (Exception: if the Program itself is interactive but +    does not normally print such an announcement, your work based on +    the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole.  If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works.  But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +  3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +    a) Accompany it with the complete corresponding machine-readable +    source code, which must be distributed under the terms of Sections +    1 and 2 above on a medium customarily used for software interchange; or, + +    b) Accompany it with a written offer, valid for at least three +    years, to give any third party, for a charge no more than your +    cost of physically performing source distribution, a complete +    machine-readable copy of the corresponding source code, to be +    distributed under the terms of Sections 1 and 2 above on a medium +    customarily used for software interchange; or, + +    c) Accompany it with the information you received as to the offer +    to distribute corresponding source code.  (This alternative is +    allowed only for noncommercial distribution and only if you +    received the program in object code or executable form with such +    an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it.  For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable.  However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +  4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License.  Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +  5. You are not required to accept this License, since you have not +signed it.  However, nothing else grants you permission to modify or +distribute the Program or its derivative works.  These actions are +prohibited by law if you do not accept this License.  Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +  6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions.  You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +  7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License.  If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all.  For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices.  Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +  8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded.  In such case, this License incorporates +the limitation as if written in the body of this License. + +  9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time.  Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number.  If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation.  If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +  10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission.  For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this.  Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + +			    NO WARRANTY + +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +		     END OF TERMS AND CONDITIONS + +	    How to Apply These Terms to Your New Programs + +  If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +  To do so, attach the following notices to the program.  It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + +    <one line to give the program's name and a brief idea of what it does.> +    Copyright (C) <year>  <name of author> + +    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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + +    Gnomovision version 69, Copyright (C) year name of author +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. +    This is free software, and you are welcome to redistribute it +    under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License.  Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary.  Here is a sample; alter the names: + +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program +  `Gnomovision' (which makes passes at compilers) written by James Hacker. + +  <signature of Ty Coon>, 1 April 1989 +  Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs.  If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library.  If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/modules/perl_plus/Makefile b/modules/perl_plus/Makefile new file mode 100644 index 0000000..1c9fa6d --- /dev/null +++ b/modules/perl_plus/Makefile @@ -0,0 +1,56 @@ +### Edit these parameters ### + +# change this to 'find' if you're on a decent system. +FIND = gfind # stupid OSX non-gnu defaults. + +# Where your irssi include files live. You might need to install an +# 'irssi-dev' package or something like that. +IRSSI_DIST = /opt/stow/repo/irssi-debug/include/irssi + +# probably $(HOME)/.irssi for most people. +IRSSI_USER_DIR = $(HOME)/projects/tmp/test/irssi-debug +MODULE_NAME = perl_plus + +### You shouldn't need to edit anything beyond this point ### + +LIB_NAME = lib$(MODULE_NAME).so +CFLAGS = -Wall -O2 -Werror -g -DMODULE_NAME=\"$(MODULE_NAME)\" +LDFLAGS = -avoid-version -module -bundle -flat_namespace -undefined suppress + +# When you start adding more components to your module, add them here. +OBJECTS = perl_plus_core.o \ +          perl_plus_impl.o + +IRSSI_INCLUDE = -I$(IRSSI_DIST) \ +				-I$(IRSSI_DIST)/src \ +				-I$(IRSSI_DIST)/src/fe-common/core \ +				-I$(IRSSI_DIST)/src/core \ +				-I$(IRSSI_DIST)/src/fe-text \ +				-I$(IRSSI_DIST)/src/irc \ +				-I$(IRSSI_DIST)/src/irc/core \ +				-I$(IRSSI_DIST)/src/irc/dcc \ +				-I$(IRSSI_DIST)/src/irc/notifylist + + +GLIB_CFLAGS = $(shell pkg-config glib-2.0 --cflags) + +all: $(LIB_NAME) + +%.o: %.c Makefile +	$(CC) $(CFLAGS) $(GLIB_CFLAGS) $(IRSSI_INCLUDE) -I. -fPIC -c $< + +$(LIB_NAME): $(OBJECTS) +	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o $@ + +install: $(LIB_NAME) +	install $< $(IRSSI_USER_DIR)/modules + +clean: +	rm -rf *~ *.o *.so core || true + +TAGS: +	$(FIND) -type f -exec etags -a -o TAGS {} \; + +.default: all + +.phony: clean install TAGS diff --git a/modules/perl_plus/README.md b/modules/perl_plus/README.md new file mode 100644 index 0000000..99aa815 --- /dev/null +++ b/modules/perl_plus/README.md @@ -0,0 +1,36 @@ +## What is this? + +A loadable module which (I hope) is going to be able to augment the +existing perl API with a bunch of other things. + +## Usage + +* Edit the Makefile as per the comments at the top. +* `make && make install` +* Start up a spare Irssi client (no point segfaulting your main one) +* `/load perl_plus` +* Party in the streets. + +## Contributing + +Contributions to this project are welcome (i.e.: to demonstrate more useful +things that a module can do) + + * Patches can be submitted via e-mail, or preferably via forking +   and sending a pull-request using GitHub.  The repository for this +   code is `git://github.com/shabble/irssi-scripts.git` and the +   corresponding web-page is +   [irssi-scripts/modules](https://github.com/shabble/irssi-scripts/modules). +    +   Details of pull-requests can be found at +   [GitHub](http://help.github.com/pull-requests/) +    + . Please poke me on Freenode IRC (`shabble` on `#irssi`) before +   spending too much time on the code. Use the `git format-patch`-tool when +   emailing patches. + +## Authors + + * Tom Feist [shabble+irssi@metavore.org](mailto://shabble+irssi@metavore.org) +     Chief plunderer of the modular goodness. +  diff --git a/modules/perl_plus/perl_plus_core.c b/modules/perl_plus/perl_plus_core.c new file mode 100644 index 0000000..61b8ffc --- /dev/null +++ b/modules/perl_plus/perl_plus_core.c @@ -0,0 +1,34 @@ + +/* Originally this code was part of the irssi-lua module + * <https://github.com/ahf/irssi-lua> and copyright as below: + * + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <perl_plus_core.h> +#include <perl_plus_irssi.h> +#include <perl_plus_impl.h> + +void perl_plus_init() { +    module_register(MODULE_NAME, "core"); +    print_load_message(); +} + +void perl_plus_deinit() { +    print_unload_message(); +} diff --git a/modules/perl_plus/perl_plus_core.h b/modules/perl_plus/perl_plus_core.h new file mode 100644 index 0000000..d5fec48 --- /dev/null +++ b/modules/perl_plus/perl_plus_core.h @@ -0,0 +1,27 @@ +/* vim: set sw=4 sts=4 et foldmethod=syntax : */ + +/* + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUARD_PERL_PLUS_CORE_H +#define GUARD_PERL_PLUS_CORE_H 1 + +void perl_plus_init(); +void perl_plus_deinit(); + +#endif diff --git a/modules/perl_plus/perl_plus_impl.c b/modules/perl_plus/perl_plus_impl.c new file mode 100644 index 0000000..b53e6f5 --- /dev/null +++ b/modules/perl_plus/perl_plus_impl.c @@ -0,0 +1,18 @@ +#include <perl_plus_irssi.h> +#include <perl_plus_impl.h> +#include <perl_plus_core.h> + +void print_load_message(void) { + +    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, +              "Hello, World. xxx \"%s\"", MODULE_NAME); + +} + +void print_unload_message(void) { + +    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, +              "Goodbye, Cruel World. ~signed \"%s\"", MODULE_NAME); + +} + diff --git a/modules/perl_plus/perl_plus_impl.h b/modules/perl_plus/perl_plus_impl.h new file mode 100644 index 0000000..acf8923 --- /dev/null +++ b/modules/perl_plus/perl_plus_impl.h @@ -0,0 +1,7 @@ +#ifndef _PERL_PLUS_IMPL_H_ +#define _PERL_PLUS_IMPL_H_ 1 + +void print_load_message(void); +void print_unload_message(void); + +#endif /* _PERL_PLUS_IMPL_H_ */ diff --git a/modules/perl_plus/perl_plus_irssi.h b/modules/perl_plus/perl_plus_irssi.h new file mode 100644 index 0000000..4fd2a2e --- /dev/null +++ b/modules/perl_plus/perl_plus_irssi.h @@ -0,0 +1,51 @@ + +/* + * Copyright (c) 2009 Alexander Færøy <ahf@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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUARD_TEST_IRSSI_H +#define GUARD_TEST_IRSSI_H 1 + +/* FIXME: bug */ +#define UOFF_T_LONG_LONG 1 + +/* #include "config.h" */ +#include "core.h" +#include "common.h" +#include "modules.h" +#include "commands.h" +#include "settings.h" +#include "printtext.h" +#include "window-items.h" +#include "window-activity.h" +#include "levels.h" +#include "servers.h" +#include "chat-protocols.h" +#include "channels.h" +#include "queries.h" +#include "nicklist.h" +#include "chatnets.h" +#include "servers-reconnect.h" +#include "masks.h" +#include "misc.h" +#include "rawlog.h" +#include "log.h" +#include "ignore.h" +#include "fe-exec.h" +#include "pidwait.h" + +#endif diff --git a/modules/test/Makefile b/modules/test/Makefile index 8ba234d..7becf66 100644 --- a/modules/test/Makefile +++ b/modules/test/Makefile @@ -1,14 +1,26 @@ -CFLAGS = -Wall -O2 -Werror -g -LDFLAGS = -avoid-version -module -bundle -flat_namespace -undefined suppress -FIND = gfind # stupid non-gnu defaults. -OBJECTS = test_core.o \ -		  test_impl.o +### Edit these parameters ### +# change this to 'find' if you're on a decent system. +FIND = gfind # stupid OSX non-gnu defaults. +# Where your irssi include files live. You might need to install an +# 'irssi-dev' package or something like that.  IRSSI_DIST = /opt/stow/repo/irssi/include/irssi +# probably $(HOME)/.irssi for most people.  IRSSI_USER_DIR = $(HOME)/projects/tmp/test/irssi +MODULE_NAME = test + +### You shouldn't need to edit anything beyond this point ### + +LIB_NAME = lib$(MODULE_NAME).so +CFLAGS = -Wall -O2 -Werror -g -DMODULE_NAME=\"$(MODULE_NAME)\" +LDFLAGS = -avoid-version -module -bundle -flat_namespace -undefined suppress + +# When you start adding more components to your module, add them here. +OBJECTS = test_core.o \ +          test_impl.o  IRSSI_INCLUDE = -I$(IRSSI_DIST) \  				-I$(IRSSI_DIST)/src \ @@ -23,15 +35,15 @@ IRSSI_INCLUDE = -I$(IRSSI_DIST) \  GLIB_CFLAGS = $(shell pkg-config glib-2.0 --cflags) -all: libtest.so +all: $(LIB_NAME)  %.o: %.c Makefile  	$(CC) $(CFLAGS) $(GLIB_CFLAGS) $(IRSSI_INCLUDE) -I. -fPIC -c $< -libtest.so: $(OBJECTS) +$(LIB_NAME): $(OBJECTS)  	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o $@ -install: libtest.so +install: $(LIB_NAME)  	install $< $(IRSSI_USER_DIR)/modules  clean: diff --git a/modules/test/README.md b/modules/test/README.md index bc98767..dff9815 100644 --- a/modules/test/README.md +++ b/modules/test/README.md @@ -12,9 +12,18 @@ Note that modules are significantly more effort (and less portable, without  a huge amount of effort), so Perl scripts are to be preferred in call cases  where possible. +## Usage + +* Edit the Makefile as per the comments at the top. +* `make && make install` +* Start up a spare Irssi client (no point segfaulting your main one) +* `/load test` +* Party in the streets. +  ## Contributing -Contributions to this project are welcome +Contributions to this project are welcome (i.e.: to demonstrate more useful +things that a module can do)   * Patches can be submitted via e-mail, or preferably via forking     and sending a pull-request using GitHub.  The repository for this @@ -31,11 +40,11 @@ Contributions to this project are welcome  ## Authors - * Alexander Færøy <ahf@irssi.org> + * Alexander Færøy <ahf@irssi.org>       Original Author of irssi-lua, who did all the hard work figuring       out the module interface stuff.  Also an Irssi project committer, I       believe, so that probably counts as cheating. - * Tom Feist `<shabble+irssi@metavore.org>` - + * Tom Feist [shabble+irssi@metavore.org](mailto://shabble+irssi@metavore.org) +     Chief plunderer of the modular goodness. diff --git a/modules/test/test_core.c b/modules/test/test_core.c index 1b5d8c8..a61c0c4 100644 --- a/modules/test/test_core.c +++ b/modules/test/test_core.c @@ -30,4 +30,5 @@ void test_init() {  }  void test_deinit() { +    print_unload_message();  } diff --git a/modules/test/test_core.h b/modules/test/test_core.h index 4119ffc..d0d594c 100644 --- a/modules/test/test_core.h +++ b/modules/test/test_core.h @@ -21,8 +21,6 @@  #ifndef GUARD_TEST_CORE_H  #define GUARD_TEST_CORE_H 1 -#define MODULE_NAME "test" -  void test_init();  void test_deinit(); diff --git a/modules/test/test_impl.c b/modules/test/test_impl.c index b086aff..4192bfd 100644 --- a/modules/test/test_impl.c +++ b/modules/test/test_impl.c @@ -9,3 +9,10 @@ void print_load_message(void) {  } +void print_unload_message(void) { + +    printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, +              "Goodbye, Cruel World. ~signed \"%s\"", MODULE_NAME); + +} + diff --git a/modules/test/test_impl.h b/modules/test/test_impl.h index bd9b185..e5bde72 100644 --- a/modules/test/test_impl.h +++ b/modules/test/test_impl.h @@ -1,9 +1,7 @@  #ifndef _TEST_IMPL_H_  #define _TEST_IMPL_H_ 1 -  void print_load_message(void); - - +void print_unload_message(void);  #endif /* _TEST_IMPL_H_ */ diff --git a/patches/fix-cumode_space.patch b/patches/fix-cumode_space.patch new file mode 100644 index 0000000..9d1e728 --- /dev/null +++ b/patches/fix-cumode_space.patch @@ -0,0 +1,32 @@ +From 35ddd45044388fe1f26e95c43ca0d8bcf30462fe Mon Sep 17 00:00:00 2001 +From: Tom Feist <shabble@metavore.org> +Date: Sat, 19 Mar 2011 08:09:37 +0000 +Subject: [PATCH] fix bug #795 by setting free_arg parameter correctly to false where the expando + expansion is a static string. + +--- + src/irc/core/irc-expandos.c |    8 +++++++- + 1 files changed, 7 insertions(+), 1 deletions(-) + +diff --git a/src/irc/core/irc-expandos.c b/src/irc/core/irc-expandos.c +index 0c0da64..df692cc 100644 +--- a/src/irc/core/irc-expandos.c ++++ b/src/irc/core/irc-expandos.c +@@ -106,7 +106,13 @@ static char *expando_cumode_space(SERVER_REC *server, void *item, int *free_ret) +                 return ""; +  + 	ret = expando_cumode(server, item, free_ret); +-	return *ret == '\0' ? " " : ret; ++ ++	if (*ret == '\0') { ++	    free_ret = FALSE; ++	    return " "; ++	} else { ++	    return ret; ++	} + } +  + static void event_join(IRC_SERVER_REC *server, const char *data, +--  +1.7.4.1 + diff --git a/patches/fix-signal-remove-coderef.patch b/patches/fix-signal-remove-coderef.patch new file mode 100644 index 0000000..e2ecd19 --- /dev/null +++ b/patches/fix-signal-remove-coderef.patch @@ -0,0 +1,22 @@ +From f1f66db22e732ca3e5d920c64c8b904e2fb92762 Mon Sep 17 00:00:00 2001 +From: Tom Feist <shabble@metavore.org> +Date: Fri, 8 Apr 2011 20:28:41 +0100 +Subject: [PATCH] bugfix: allow Irssi::signal_remove to work properly with coderefs + + +diff --git a/src/perl/perl-signals.c b/src/perl/perl-signals.c +index a455cfd..1652d09 100644 +--- a/src/perl/perl-signals.c ++++ b/src/perl/perl-signals.c +@@ -434,8 +434,9 @@ static void perl_signal_remove_list_one(GSList **siglist, PERL_SIGNAL_REC *rec) + } +  + #define sv_func_cmp(f1, f2) \ +-	(f1 == f2 || (SvPOK(f1) && SvPOK(f2) && \ +-		strcmp((char *) SvPV_nolen(f1), (char *) SvPV_nolen(f2)) == 0)) ++  ((SvROK(f1) && SvROK(f2) && SvRV(f1) == SvRV(f2)) || \ ++   (SvPOK(f1) && SvPOK(f2) && \ ++    strcmp((char *) SvPV_nolen(f1), (char *) SvPV_nolen(f2)) == 0)) +  + static void perl_signal_remove_list(GSList **list, SV *func) + { diff --git a/prompt_info/README.pod b/prompt_info/README.pod new file mode 100644 index 0000000..d8e3b3e --- /dev/null +++ b/prompt_info/README.pod @@ -0,0 +1,273 @@ +=pod + +=head1 NAME + +uberprompt.pl + +=head1 DESCRIPTION + +This script replaces the default prompt status-bar item with one capable of +displaying additional information, under either user control or via scripts. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<filename>>. + +It is recommended that you make it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head1 SETUP + +If you have a custom prompt format, you may need to copy it to the +uberprompt_format setting. See below for details. + +=head1 USAGE + +Although the script is designed primarily for other scripts to set +status information into the prompt, the following commands are available: + +=over 4 + +=item * C</prompt set [-inner|-pre|-post|only] E<lt>msgE<gt>> + +Sets the prompt to the given argument. Any use of C<$p> in the argument will +be replaced by the original prompt content. + +A parameter corresponding to the C<UP_*> constants listed below is required, in +the format C</prompt set -inner Hello!> + +=item * C</prompt clear> + +Clears the additional data provided to the prompt. + +=item * C</prompt on> + +Eenables the uberprompt (things may get confused if this is used +whilst the prompt is already enabled) + +=item * C</prompt off> + +Restore the original irssi prompt and prompt_empty statusbars.  unloading the +script has the same effect. + +=item * C</help prompt> + +show help for uberprompt commands + +=back + +=head1 SETTINGS + +=head2 UBERPROMPT FORMAT + +C</set uberprompt_format E<lt>formatE<gt>> + +The default is C<[$*$uber]>, which is the same as the default provided in +F<default.theme>. + +Changing this setting will update the prompt immediately, unlike editing your theme. + +An additional variable available within this format is C<$uber>, which expands to +the content of prompt data provided with the C<UP_INNER> or C</prompt set -inner> +placement argument. + +For all other placement arguments, it will expand to the empty string. + +B<Note:> This setting completely overrides the C<prompt="...";> line in your +.theme file, and may cause unexpected behaviour if your theme wishes to set a +different form of prompt. It can be simply copied from the theme file into the +above setting. + +=head2 OTHER SETTINGS + +=over 4 + +=item * C<uberprompt_autostart> + +Boolean value, which determines if uberprompt should enable itself automatically +upon loading.  If Off, it must be enabled manually with C</prompt on>.  Defaults to On. + +=item * C<uberprompt_debug> + +Boolean value, which determines if uberprompt should print debugging information. +Defaults to Off, and should probably be left that way unless requested for bug-tracing +purposes. + +=item * C<uberprompt_format> + +String value containing the format-string which uberprompt uses to display the +prompt. Defaults to "C<[$*$uber] >", where C<$*> is the content the prompt would +normally display, and C<$uber> is a placeholder variable for dynamic content, as +described in the section above. + +=item * C<uberprompt_load_hook> + +String value which can contain one or more commands to be run whenever the uberprompt +is enabled, either via autostart, or C</prompt on>. Defaults to the empty string, in +which case no commands are run.  Some examples include: + +C</set uberprompt_load_hook /echo prompt enabled> or + +C</^sbar prompt add -after input vim_mode> for those using vim_mode.pl who want +the command status indicator on the prompt line. + +=item * C<uberprompt_unload_hook> + +String value, defaulting to the empty string, which can contain commands which +are executed when the uberprompt is disabled, either by unloading the script, +or by the command C</prompt off>. + +=item * C<uberprompt_use_replaces> + +Boolean value, defaults to Off. If enabled, the format string for the prompt +will be subject to the I<replaces> section of the theme.  The most obvious +effect of this is that bracket characters C<[ ]> are displayed in a different +colour, typically quite dark. + +=back + +B<Note:> For both C<uberprompt_*_hook> settings above, multiple commands can +be chained together in the form C</eval /^cmd1 ; /^cmd2>. The C<^> prevents +any output from the commands (such as error messages) being displayed. + +=head2 SCRIPTING USAGE + +The primary purpose of uberprompt is to be used by other scripts to +display information in a way that is not possible by printing to the active +window or using statusbar items. + +The content of the prompt can be set from other scripts via the C<"change prompt"> +signal. + +For Example: + +    signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER; + +will set the prompt to include that content, by default 'C<[$* some_string]>' + +The possible position arguments are the following strings: + +=over 4 + +=item * C<UP_PRE>   - place the provided string before the prompt - C<$string$prompt> + +=item * C<UP_INNER> - place the provided string inside the prompt - C<{prompt $* $string}> + +=item * C<UP_POST>  - place the provided string after the prompt  - C<$prompt$string> + +=item * C<UP_ONLY>  - replace the prompt with the provided string - C<$string> + +=back + +All strings may use the special variable 'C<$prompt>' to include the prompt +verbatim at that position in the string.  It is probably only useful for +the C<UP_ONLY> mode however. '$C<prompt_nt>' will include the prompt, minus any +trailing whitespace. + +=head2 CHANGE NOTIFICATIONS + +You can also be notified when the prompt changes in response to the previous +signal or manual C</prompt> commands via: + +    signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... }; + +This callback will occur whenever the contents of the prompt is changed. + + +=head2 NOTES FOR SCRIPT WRITERS: + +The following code snippet can be used within your own script as a preamble +to ensure that uberprompt is loaded before your script to avoid +any issues with loading order.  It first checks if uberprompt is loaded, and +if not, attempts to load it.  If the load fails, the script will die +with an error message, otherwise it will call your app_init() function. + +I<---- start of snippet ----> + +    my $DEBUG_ENABLED = 0; +    sub DEBUG () { $DEBUG_ENABLED } + +    # check we have uberprompt loaded. + +    sub script_is_loaded { +       return exists($Irssi::Script::{$_[0] . '::'}); +    } + +    if (not 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"); +        } +        app_init(); +    } else { +        app_init(); +    } + +    sub load_uberprompt_failed { +        Irssi::signal_remove('script error', 'load_prompt_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(" ", @_); +    } + +I<---- end of snippet ----> + +=head1 AUTHORS + +Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=over 4 + +=item *  + +Resizing the terminal rapidly whilst using this script in debug mode may cause +irssi to crash. See bug report at http://bugs.irssi.org/index.php?do=details&task_id=772 for details. + +=back + +=head1 TODO + +=over 4 + +=item * report failure (somehow) to clients if hte prompt is disabled. + +=item * fix issue at autorun startup with sbar item doesn't exist. + +=back + + + +=cut + diff --git a/prompt_info/input_overlay.pl b/prompt_info/old/input_overlay.pl index 7a8dd9d..9d2a42b 100644 --- a/prompt_info/input_overlay.pl +++ b/prompt_info/old/input_overlay.pl @@ -27,8 +27,9 @@ my @regions;  my ($term_w, $term_h) = (0, 0); -my $key_capture = 0; +my $overlay_active = 0;  my $prompt_len = 5; +my $region_id = 0;  sub DEBUG () { 1 } @@ -56,6 +57,16 @@ sub update_terminal_size {      print "Terminal detected as $term_w cols by $term_h rows" if DEBUG;  } +sub find_region { +    my ($pos) = @_; +    foreach my $region (@regions) { +        next unless $pos > $region->{start}; +        return $region if $pos <= $region->{end}; +    } +    print "failed to find region for pos: $pos"; +    return undef; +} +  sub redraw_overlay {      # TODO: we can't assume the active win is the only one with overlays.      #Irssi::active_win->view->redraw(); @@ -90,55 +101,25 @@ sub intercept_keypress {  } -sub new_region { -    my $key = shift; -    print "Creating new Region"; -    my $new_region -      = { -         text  => '', -         start => _pos() -1, -         style => '%_', -         open  => 1, -         draw  => 1, -        }; - -    insert_into_region($key, $new_region); - -    push @regions, $new_region; -} - -sub insert_into_region { -    my ($key, $region) = @_; - -    # if ($key == 127) { # backspace -    #     substr($region->{text}, -1, 1) = ''; -    # } else { -    #     $region->{text} .= chr $key; -    # } -    my $input = Irssi::parse_special('$L'); -    my $len = _pos() - $region->{start}; -#    print "Input: $input, len: $len" if DEBUG; - -    my $str = substr($input, $region->{start} , $len); -#    print "Str: $str" if DEBUG; -    $region->{text} = $str; - - #   printf("region [%d-%d] now contains '%s'", - #          $region->{start}, _pos(), - #          $region->{text}) if DEBUG; -} -  sub observe_keypress {      my $key = shift; -    if ($key_capture && $key > 31 && $key <= 127) { +    print "Key " . chr ($key) . " pressed, pos: " . _pos(); +    if ($key > 31 && $key <= 127) {          # see if we're still appending to the last region:          #print "Observed printable key: " . chr($key) if DEBUG;          #print '';          my $latest_region = $regions[-1];          $latest_region = {} unless defined $latest_region; -        if (not $latest_region->{open}) { -            new_region($key); + +        my $pos = _pos(); +        my $reg = find_region($pos); + +        if (defined $reg) { +            insert_into_region($key, $reg); +        } elsif (not $latest_region->{open}) { +            my $style = $overlay_active?'%_':''; +            new_region($style, $key);          } else {              insert_into_region($key, $latest_region);          } @@ -148,6 +129,7 @@ sub observe_keypress {          redraw_overlay();      }  } +  sub init {      die "This script requires uberprompt.pl" @@ -160,8 +142,7 @@ sub init {      Irssi::signal_add      ('terminal resized',        \&update_terminal_size);      Irssi::signal_add_first('gui print text finished', \&augment_redraw); -    Irssi::command_bind('region_start', \®ion_toggle); -    Irssi::command('/bind ^C /region_start'); +    setup_bindings();      Irssi::signal_add('prompt changed', sub {                            print "Updated prompt length: $_[1]"; @@ -173,22 +154,119 @@ sub init {      update_terminal_size();  } +sub setup_bindings { + +    Irssi::command_bind('region_start', \®ion_toggle); +    Irssi::command_bind('region_clear', \®ion_clear); +    Irssi::command_bind('region_print', \&print_regions); + + +    Irssi::command('/bind ^C /region_start'); +    ##Irssi::command('/bind ^D /region_clear'); +    Irssi::command('/bind ^D /region_print'); + +} + + +################################################################################ + +sub escape_style { +    my ($style) = @_; +    $style =~ s/%/%%/g; + +    return $style; +} + +sub print_regions { +    foreach my $reg (@regions) { +        printf("start: %d end: %d style: %s, text: \"%s\", open: %d, draw: %d", +               $reg->{start}, $reg->{end}, escape_style($reg->{style}), +               $reg->{text},  $reg->{open}, $reg->{draw}); +    } +} + +sub new_region { +    my ($style, $key) = @_; + +    my $new_id = $region_id++; +    _debug("Creating new Region: $new_id"); + +    my $new_region +      = { +         id    => $region_id++, +         text  => '', +         start => _pos(), +         end   => _pos(), +         style => $style, +         open  => 1, +         draw  => 1, +        }; + +    insert_into_region($key, $new_region); + +    push @regions, $new_region; +} + +sub delete_region { +    my ($region) = @_; +    my $idx = 0; +    foreach my $i (0..$#regions) { +        if ($regions[$i]->{id} == $region->{id}) { +            $idx = $i; +            last; +        } +    } +    print "Deleting region: $idx"; +    splice(@regions, $idx, 1); # remove the selected region. +} + +sub insert_into_region { +    my ($key, $region) = @_; + +    my $pos = _pos(); + +    if ($key == 127) { # backspace +        substr($region->{text}, -1, 1) = ''; +        $region->{end}--; +        if ($region->{end} <= $region->{start}) { +            delete_region($region); +        } +    } else { +        printf("text: '%s', pos: %d, offset: %d", +               $region->{text}, $pos, $pos - $region->{start}); +        if ( $region->{end} < $pos) { +            $region->{text} .= chr $key; +        } else { +            substr($region->{text}, $pos - $region->{start}, 0) = chr $key; +        } +        $region->{end}++; +    } +} + +sub region_clear { +    @regions = (); +    Irssi::signal_emit('command redraw'); +} +  sub region_toggle { -    $key_capture = not $key_capture; -    printf("Region is %sactive", $key_capture?'':'in'); +    $overlay_active = not $overlay_active; +    _debug("Region is %sactive", $overlay_active?'':'in');      #@regions = ();      # terminate the previous region -    my $latest_region = $regions[-1]; -    if (defined $latest_region) { -        $latest_region->{open} = 0; + +    my $region = find_region(_pos()); +    if (defined $region) { +        $region->{open} = 0; +        $region->{end}  = _pos(); +        debug("Region closed: %d-%d", $region->{start}, $region->{end});      }  }  sub script_is_loaded {      my $name = shift; -    print "Checking if $name is loaded" if DEBUG; +    _debug("Checking if $name is loaded");      no strict 'refs'; -    my $retval = defined %{ "Irssi::Script::${name}::" }; +    my $retval =  %{ "Irssi::Script::${name}::" };      use strict 'refs';      return $retval; @@ -198,5 +276,13 @@ sub _pos {      return Irssi::gui_input_get_pos();  } +sub _input { +    return Irssi::parse_special('$L'); +} + +sub _debug { +    printf @_ if DEBUG(); +} +  init(); diff --git a/prompt_info/overlays.pl b/prompt_info/old/overlays.pl index b3299e9..b3299e9 100644 --- a/prompt_info/overlays.pl +++ b/prompt_info/old/overlays.pl diff --git a/prompt_info/prompt_info.pl b/prompt_info/old/prompt_info.pl index 8ad63ba..8ad63ba 100644 --- a/prompt_info/prompt_info.pl +++ b/prompt_info/old/prompt_info.pl diff --git a/prompt_info/prompt_replace.pl b/prompt_info/old/prompt_replace.pl index 30120f7..30120f7 100644 --- a/prompt_info/prompt_replace.pl +++ b/prompt_info/old/prompt_replace.pl diff --git a/prompt_info/visual.pl b/prompt_info/old/visual.pl index b29875b..b29875b 100644 --- a/prompt_info/visual.pl +++ b/prompt_info/old/visual.pl diff --git a/prompt_info/uberprompt.pl b/prompt_info/uberprompt.pl index c1c4a55..62bda13 100644 --- a/prompt_info/uberprompt.pl +++ b/prompt_info/uberprompt.pl @@ -1,123 +1,282 @@ -# This script replaces the default prompt status-bar item with one capable -# of displaying additional information, under either user control or via -# scripts. -# -# INSTALL: -# -# Place script in ~/.irssi/scripts/ and potentially symlink into autorun/ -# to ensure it starts at irssi startup. -# -# If not using autorun, manually load the script via: -# -# /script load uberprompt.pl -# -# If you have a custom prompt format, you may need to copy it to the -# uberprompt_format setting. See below for details. -# -# USAGE: -# -# Although the script is designed primarily for other scripts to set -# status information into the prompt, the following commands are available: -# -# TODO: Document positional settings. -# -# /prompt set   - sets the prompt to the given argument. $p in the argument will -#                 be replaced by the original prompt content. -# /prompt clear - clears the additional data provided to the prompt. -# /prompt on    - enables the uberprompt (things may get confused if this is used -#                 whilst the prompt is already enabled) -# /prompt off   - restore the original irssi prompt and prompt_empty statusbars. -#                 unloading the script has the same effect. -# -# Additionally, the format for the prompt can be set via: -# -# UBERPROMPT FORMAT: -# -# /set uberprompt_format <format> -# -# The default is [$*], which is the same as the default provided in default.theme. -# Changing this setting will update the prompt immediately, unlike editing your theme. -# -# An additional variable available within this format is '$uber', which expands to -# the content of prompt data provided with the UP_INNER placement argument. For all -# other placement arguments, it will expand to the empty string ''. -# -# NOTE: this setting completely overrides the prompt="..." line in your .theme -#       file, and may cause unexpected behaviour if your theme wishes to set a -#       different form of prompt. It can be simply copied from the theme file into -#       the above setting. -# -# Usage from other Scripts: signal 'change prompt' => 'string' => position -# -# eg: -# -# signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER; -# -# will set the prompt to include that content, by default '[$* some_string]' -# -# The possible position arguments are the following strings: -# -# UP_PRE   - place the provided string before the prompt -- $string$prompt -# UP_INNER - place the provided string inside the prompt -- {prompt $* $string} -# UP_POST  - place the provided string after the prompt  -- $prompt$string -# UP_ONLY  - replace the prompt with the provided string -- $string -# -# All strings may use the special variable '$prompt' to include the prompt -# verbatim at that position in the string.  It is probably only useful for -# the UP_ONLY mode however. '$prompt_nt' will include the prompt, minus any -# trailing whitespace. -# -# NOTIFICATIONS: -# -# You can also be notified when the prompt changes in response to the previous -# signal or manual commands via: -# -# signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... }; -# -# Bugs: -# -# * Resizing the terminal rapidly whilst using this script in debug mode -#    may cause irssi to crash. See bug report at -#    http://bugs.irssi.org/index.php?do=details&task_id=772 for details. -# -# TODO: -# -# * report failure (somehow) to clients if hte prompt is disabled. -# * fix issue at autorun startup with sbar item doesn't exist. -# -# -# -# -# LICENCE: -# -# Copyright (c) 2010 Tom Feist -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +=pod + +=head1 NAME + +uberprompt.pl + +=head1 DESCRIPTION + +This script replaces the default prompt status-bar item with one capable of +displaying additional information, under either user control or via scripts. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<filename>>. + +It is recommended that you make it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head1 SETUP + +If you have a custom prompt format, you may need to copy it to the +uberprompt_format setting. See below for details. + +=head1 USAGE + +Although the script is designed primarily for other scripts to set +status information into the prompt, the following commands are available: + +=over 4 + +=item * C</prompt set [-inner|-pre|-post|only] E<lt>msgE<gt>> + +Sets the prompt to the given argument. Any use of C<$p> in the argument will +be replaced by the original prompt content. + +A parameter corresponding to the C<UP_*> constants listed below is required, in +the format C</prompt set -inner Hello!> + +=item * C</prompt clear> + +Clears the additional data provided to the prompt. + +=item * C</prompt on> + +Eenables the uberprompt (things may get confused if this is used +whilst the prompt is already enabled) + +=item * C</prompt off> + +Restore the original irssi prompt and prompt_empty statusbars.  unloading the +script has the same effect. + +=item * C</help prompt> + +show help for uberprompt commands + +=back + +=head1 SETTINGS + +=head2 UBERPROMPT FORMAT + +C</set uberprompt_format E<lt>formatE<gt>> + +The default is C<[$*$uber]>, which is the same as the default provided in +F<default.theme>. + +Changing this setting will update the prompt immediately, unlike editing your theme. + +An additional variable available within this format is C<$uber>, which expands to +the content of prompt data provided with the C<UP_INNER> or C</prompt set -inner> +placement argument. + +For all other placement arguments, it will expand to the empty string. + +B<Note:> This setting completely overrides the C<prompt="...";> line in your +.theme file, and may cause unexpected behaviour if your theme wishes to set a +different form of prompt. It can be simply copied from the theme file into the +above setting. + +=head2 OTHER SETTINGS + +=over 4 + +=item * C<uberprompt_autostart> + +Boolean value, which determines if uberprompt should enable itself automatically +upon loading.  If Off, it must be enabled manually with C</prompt on>.  Defaults to On. + +=item * C<uberprompt_debug> + +Boolean value, which determines if uberprompt should print debugging information. +Defaults to Off, and should probably be left that way unless requested for bug-tracing +purposes. + +=item * C<uberprompt_format> + +String value containing the format-string which uberprompt uses to display the +prompt. Defaults to "C<[$*$uber] >", where C<$*> is the content the prompt would +normally display, and C<$uber> is a placeholder variable for dynamic content, as +described in the section above. + +=item * C<uberprompt_load_hook> + +String value which can contain one or more commands to be run whenever the uberprompt +is enabled, either via autostart, or C</prompt on>. Defaults to the empty string, in +which case no commands are run.  Some examples include: + +C</set uberprompt_load_hook /echo prompt enabled> or + +C</^sbar prompt add -after input vim_mode> for those using vim_mode.pl who want +the command status indicator on the prompt line. + +=item * C<uberprompt_unload_hook> + +String value, defaulting to the empty string, which can contain commands which +are executed when the uberprompt is disabled, either by unloading the script, +or by the command C</prompt off>. + +=item * C<uberprompt_use_replaces> + +Boolean value, defaults to Off. If enabled, the format string for the prompt +will be subject to the I<replaces> section of the theme.  The most obvious +effect of this is that bracket characters C<[ ]> are displayed in a different +colour, typically quite dark. + +=back + +B<Note:> For both C<uberprompt_*_hook> settings above, multiple commands can +be chained together in the form C</eval /^cmd1 ; /^cmd2>. The C<^> prevents +any output from the commands (such as error messages) being displayed. + +=head2 SCRIPTING USAGE + +The primary purpose of uberprompt is to be used by other scripts to +display information in a way that is not possible by printing to the active +window or using statusbar items. + +The content of the prompt can be set from other scripts via the C<"change prompt"> +signal. + +For Example: + +    signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER; + +will set the prompt to include that content, by default 'C<[$* some_string]>' + +The possible position arguments are the following strings: + +=over 4 + +=item * C<UP_PRE>   - place the provided string before the prompt - C<$string$prompt> + +=item * C<UP_INNER> - place the provided string inside the prompt - C<{prompt $* $string}> + +=item * C<UP_POST>  - place the provided string after the prompt  - C<$prompt$string> + +=item * C<UP_ONLY>  - replace the prompt with the provided string - C<$string> + +=back + +All strings may use the special variable 'C<$prompt>' to include the prompt +verbatim at that position in the string.  It is probably only useful for +the C<UP_ONLY> mode however. '$C<prompt_nt>' will include the prompt, minus any +trailing whitespace. + +=head2 CHANGE NOTIFICATIONS + +You can also be notified when the prompt changes in response to the previous +signal or manual C</prompt> commands via: + +    signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... }; + +This callback will occur whenever the contents of the prompt is changed. + + +=head2 NOTES FOR SCRIPT WRITERS: + +The following code snippet can be used within your own script as a preamble +to ensure that uberprompt is loaded before your script to avoid +any issues with loading order.  It first checks if uberprompt is loaded, and +if not, attempts to load it.  If the load fails, the script will die +with an error message, otherwise it will call your app_init() function. + +I<---- start of snippet ----> + +    my $DEBUG_ENABLED = 0; +    sub DEBUG () { $DEBUG_ENABLED } + +    # check we have uberprompt loaded. + +    sub script_is_loaded { +       return exists($Irssi::Script::{$_[0] . '::'}); +    } + +    if (not 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"); +        } +        app_init(); +    } else { +        app_init(); +    } + +    sub load_uberprompt_failed { +        Irssi::signal_remove('script error', 'load_prompt_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(" ", @_); +    } + +I<---- end of snippet ----> + +=head1 AUTHORS + +Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=over 4 + +=item *  + +Resizing the terminal rapidly whilst using this script in debug mode may cause +irssi to crash. See bug report at http://bugs.irssi.org/index.php?do=details&task_id=772 for details. + +=back + +=head1 TODO + +=over 4 + +=item * report failure (somehow) to clients if hte prompt is disabled. + +=item * fix issue at autorun startup with sbar item doesn't exist. + +=back + +=cut  use strict;  use warnings;  use Irssi; -use Irssi::TextUI;              # for sbar_items_redraw +use Irssi::TextUI;  use Data::Dumper; -{ package Irssi::Nick } +{ package Irssi::Nick } # magic.  our $VERSION = "0.2";  our %IRSSI = @@ -150,6 +309,8 @@ my $emit_request = 0;  my $expando_refresh_timer;  my $expando_vars = {}; +my $init_callbacks = {load => '', unload => ''}; +  pre_init();  sub pre_init { @@ -159,10 +320,63 @@ sub pre_init {  sub prompt_subcmd_handler {      my ($data, $server, $item) = @_; -    $data =~ s/\s+$//g;         # strip trailing whitespace. +    #$data =~ s/\s+$//g;         # strip trailing whitespace.      Irssi::command_runsub('prompt', $data, $server, $item);  } +sub _error($) { +    my ($msg) = @_; +    Irssi::active_win->print($msg, MSGLEVEL_CLIENTERROR); +} + +sub _debug_print($) { +    return unless DEBUG; +    my ($msg) = @_; +    Irssi::active_win->print($msg, MSGLEVEL_CLIENTCRAP); +} + +sub _print_help { +    my ($args) = @_; +    if ($args =~ m/^\s*prompt/i) { +        my @help_lines = +          ( +           "", +           "PROMPT ON", +           "PROMPT OFF", +           "PROMPT CLEAR", +           "PROMPT SET { -pre | -post | -only | -inner } <content>", +           "", +           "Commands for manipulating the UberPrompt.", +           "", +           "/PROMPT ON    enables uberprompt, replacing the existing prompt ", +           "              statusbar-item", +           "/PROMPT OFF   disables it, and restores the original prompt item", +           "/PROMPT CLEAR resets the value of any additional data set by /PROMPT SET", +           "              or a script", +           "/PROMPT SET   changes the contents of the prompt, according to the mode", +           "              and content provided.", +           "      { -inner sets the value of the \$uber psuedo-variable in the", +           "             /set uberprompt_format setting.", +           "      | -pre places the content before the current prompt string", +           "      | -post places the content after the prompt string", +           "      | -only replaces the entire prompt contents with the given string }", +           "", +           "See Also:", +           '', +           '/SET uberprompt_format    -- defaults to [$*$uber]', +           "/SET uberprompt_autostart -- determines whether /PROMPT ON is run", +           "                             automatically when the script loads", +           "/set uberprompt_use_replaces -- toggles the use of the current theme", +           "                                \"replaces\" setting. Especially", +           "                                noticeable on brackets \"[ ]\" ", +           "", +          ); + +        Irssi::print($_, MSGLEVEL_CLIENTCRAP) for @help_lines; +        Irssi::signal_stop; +    } +} +  sub UNLOAD {      deinit();  } @@ -171,11 +385,14 @@ sub exp_lbrace() { '{' }  sub exp_rbrace() { '}' }  sub deinit { -    Irssi::expando_destroy('brace'); +    Irssi::expando_destroy('lbrace'); +    Irssi::expando_destroy('rbrace'); +      # remove uberprompt and return the original ones.      print "Removing uberprompt and restoring original";      restore_prompt_items();  } +  sub init {      Irssi::statusbar_item_register('uberprompt', 0, 'uberprompt_draw'); @@ -183,7 +400,11 @@ sub init {      Irssi::expando_create('lbrace', \&exp_lbrace, {});      Irssi::expando_create('rbrace', \&exp_rbrace, {}); -    Irssi::settings_add_str('uberprompt', 'uberprompt_format', '[$*$uber] '); +    Irssi::settings_add_str ('uberprompt', 'uberprompt_format', '[$*$uber] '); + +    Irssi::settings_add_str ('uberprompt', 'uberprompt_load_hook',   ''); +    Irssi::settings_add_str ('uberprompt', 'uberprompt_unload_hook', ''); +      Irssi::settings_add_bool('uberprompt', 'uberprompt_debug', 0);      Irssi::settings_add_bool('uberprompt', 'uberprompt_autostart', 1);      Irssi::settings_add_bool('uberprompt', 'uberprompt_use_replaces', 0); @@ -192,18 +413,17 @@ sub init {      Irssi::command_bind("prompt",     \&prompt_subcmd_handler);      Irssi::command_bind('prompt on',  \&replace_prompt_items);      Irssi::command_bind('prompt off', \&restore_prompt_items); -    Irssi::command_bind('prompt set', -                        sub { -                            my $args = shift; -                            $args =~ s/^\s*(\w+)\s*(.*$)/$2/; -                            my $mode = 'UP_' . uc($1); -                            Irssi::signal_emit 'change prompt', $args, $mode; -                        }); +    Irssi::command_bind('prompt set', \&cmd_prompt_set);      Irssi::command_bind('prompt clear',                          sub {                              Irssi::signal_emit 'change prompt', '$p', 'UP_POST';                          }); +    my $prompt_set_args_format = "inner pre post only"; +    Irssi::command_set_options('prompt set', $prompt_set_args_format); + +    Irssi::command_bind('help', \&_print_help); +      Irssi::signal_add('setup changed', \&reload_settings);      # intialise the prompt format. @@ -239,6 +459,27 @@ sub init {      Irssi::signal_add('prompt length request', \&length_request_handler);  } +sub cmd_prompt_set { +    my $args = shift; +    my @options_list = Irssi::command_parse_options "prompt set", $args; +    if (@options_list) { +        my ($options, $rest) = @options_list; + +        my @opt_modes = keys %$options; +        if (@opt_modes != 1) { +            _error '%_/prompt set%_ must specify exactly one mode of' +              . ' {-inner, -only, -pre, -post}'; +            return; +        } + +        my $mode = 'UP_' . uc($opt_modes[0]); + +        Irssi::signal_emit 'change prompt', $rest, $mode; +    } else { +        _error ('%_/prompt set%_ must specify a mode {-inner, -only, -pre, -post}'); +    } +} +  sub refresh_if_me {      my ($channel, $nick) = @_; @@ -254,9 +495,9 @@ sub refresh_if_me {      return unless $my_chan and $my_nick; -    print "Chan: $channel->{name}, " +    _debug_print "Chan: $channel->{name}, "       . "nick: $nick->{nick}, " -     . "me: $my_nick, chan: $my_chan" if DEBUG; +     . "me: $my_nick, chan: $my_chan";      if ($my_chan eq $channel->{name} and $my_nick eq $nick->{nick}) {          uberprompt_refresh(); @@ -271,21 +512,24 @@ sub length_request_handler {  sub reload_settings { -    $use_replaces = Irssi::settings_get_bool('uberprompt_use_replaces'); - +    $use_replaces  = Irssi::settings_get_bool('uberprompt_use_replaces');      $DEBUG_ENABLED = Irssi::settings_get_bool('uberprompt_debug'); +    $init_callbacks = { +                       load   => Irssi::settings_get_str('uberprompt_load_hook'), +                       unload => Irssi::settings_get_str('uberprompt_unload_hook'), +                      }; +      if (DEBUG) {          Irssi::signal_add 'prompt changed', 'debug_prompt_changed';      } else {          Irssi::signal_remove 'prompt changed', 'debug_prompt_changed';      } -      my $new = Irssi::settings_get_str('uberprompt_format');      if ($prompt_format ne $new) { -        print "Updated prompt format" if DEBUG; +        _debug_print("Updated prompt format");          $prompt_format = $new;          $prompt_format =~ s/\$uber/\$\$uber/;          Irssi::abstracts_register(['uberprompt', $prompt_format]); @@ -296,7 +540,7 @@ sub reload_settings {          # an update timer or something, rather than just refreshing on          # every possible activity in init()          while ($prompt_format =~ m/(?<!\$)(\$[A-Za-z,.:;][a-z_]*)/g) { -            print "Detected Irssi expando variable $1" if DEBUG; +            _debug_print("Detected Irssi expando variable $1");              my $var_name = substr $1, 1; # strip the $              $expando_vars->{$var_name} = Irssi::parse_special($1);          } @@ -308,7 +552,7 @@ sub debug_prompt_changed {      $text =~ s/%/%%/g; -    print "DEBUG: Got $text, length: $len"; +    print "DEBUG_HANDLER: Prompt Changed to: \"$text\", length: $len";  }  sub change_prompt_handler { @@ -317,7 +561,7 @@ sub change_prompt_handler {      # fix for people who used prompt_info and hence the signal won't      # pass the second argument.      $pos = 'UP_INNER' unless defined $pos; -    print "Got prompt change signal with: $text, $pos" if DEBUG; +    _debug_print("Got prompt change signal with: $text, $pos");      my ($changed_text, $changed_pos);      $changed_text = defined $prompt_data     ? $prompt_data     ne $text : 1; @@ -327,7 +571,7 @@ sub change_prompt_handler {      $prompt_data_pos = $pos;      if ($changed_text || $changed_pos) { -        print "Redrawing prompt" if DEBUG; +        _debug_print("Redrawing prompt");          uberprompt_refresh();      }  } @@ -337,8 +581,8 @@ sub _escape_prompt_special {      $str =~ s/\$/\$\$/g;      $str =~ s/\\/\\\\/g;      #$str =~ s/%/%%/g; -    $str =~ s/{/\$lbrace/g; -    $str =~ s/}/\$rbrace/g; +    $str =~ s/{/\${lbrace}/g; +    $str =~ s/}/\${rbrace}/g;      return $str;  } @@ -356,7 +600,7 @@ sub uberprompt_render_prompt {      }      my $prompt = '';            # rendered content of the prompt. -    my $theme = Irssi::current_theme; +    my $theme  = Irssi::current_theme;      my $arg = $use_replaces ? 0 : Irssi::EXPAND_FLAG_IGNORE_REPLACES;      $prompt = $theme->format_expand("{uberprompt $prompt_arg}", $arg); @@ -392,12 +636,12 @@ sub uberprompt_render_prompt {          }      } -    #print "Redrawing with: $prompt, size-only: $get_size_only" if DEBUG; +    _debug_print("rendering with: $prompt");      if (($prompt ne $prompt_last) or $emit_request) { -        # print "Emitting prompt changed signal" if DEBUG; +        # _debug_print("Emitting prompt changed signal");          # my $exp = Irssi::current_theme()->format_expand($text, 0);          my $ps = Irssi::parse_special($prompt); @@ -413,7 +657,7 @@ sub uberprompt_draw {      my $prompt = uberprompt_render_prompt();      my $ret = $sb_item->default_handler($get_size_only, $prompt, '', 0); - +    _debug_print("redrawing with: $prompt");      return $ret;  } @@ -423,7 +667,7 @@ sub uberprompt_refresh {  sub replace_prompt_items {      # remove existing ones. -    print "Removing original prompt" if DEBUG; +    _debug_print("Removing original prompt");      _sbar_command('prompt', 'remove', 'prompt');      _sbar_command('prompt', 'remove', 'prompt_empty'); @@ -434,15 +678,37 @@ sub replace_prompt_items {                    qw/-alignment left -before input -priority '-1'/);      _sbar_command('prompt', 'position', '100'); + +    my $load_hook = $init_callbacks->{load}; +    if (defined $load_hook and length $load_hook) { +        eval { +            Irssi::command($load_hook); +        }; +        if ($@) { +            _error("Uberprompt user load-hook command ($load_hook) failed: $@"); +        } +    } +  }  sub restore_prompt_items {      _sbar_command('prompt', 'remove', 'uberprompt'); -    print "Restoring original prompt" if DEBUG; +    _debug_print("Restoring original prompt");      _sbar_command('prompt', 'reset'); + +    my $unload_hook = $init_callbacks->{unload}; + +    if (defined $unload_hook and length $unload_hook) { +        eval { +            Irssi::command($unload_hook); +        }; +        if ($@) { +            _error("Uberprompt user unload-hook command ($unload_hook) failed: $@"); +        } +    }  }  sub _sbar_command { @@ -455,7 +721,7 @@ sub _sbar_command {      my $command = sprintf 'STATUSBAR %s %s %s%s',       $bar, $cmd, $args_str, defined $item ? $item : ''; -    print "Running command: $command" if DEBUG; +    _debug_print("Running command: $command");      Irssi::command($command);  } diff --git a/quit-notify/README.pod b/quit-notify/README.pod new file mode 100644 index 0000000..d2c0978 --- /dev/null +++ b/quit-notify/README.pod @@ -0,0 +1,92 @@ +=pod + +=head1 NAME + +notifyquit.pl + +=head1 DESCRIPTION + +A script intended to alert people to the fact that their conversation partners +have quit or left the channel, especially useful in high-traffic channels, or  +where you have C<JOINS PARTS QUITS> ignored. + +=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. + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<notifyquit.pl>>. + +=head1 SETUP + +This script provides a single setting: + +C</SET notifyquit_exceptions>, which defaults to "C</^https?/ /^ftp/>" + +The setting is a space-separated list of regular expressions in the format +C</EXPR/>. If the extracted nickname matches any of these patterns, it isa +assumed to be a false-positive match, and is sent to the channel with no +further confirmation. + +=head1 USAGE + +When responding to users in a channel in the format C<$theirnick: some message> +(where the C<:> is not necessarily a colon, but the value of your +C<completion_char> setting), this script will check that the nickname still +exists in the channel, and will prompt you for confirmation if they have +since left. + +It is intended for use for people who ignore C<JOINS PARTS QUITS>, etc, and +try to respond to impatient people, or those with a bad connection. + +To send the message once prompted, either hit C<enter>, or C<y>.  Pressing C<n> +will abort sending, but leave the message in your input buffer just in case +you want to keep it. + +=head1 AUTHORS + +Original Copyright E<copy> 2011 Jari Matilainen C<E<lt>vague!#irssi@freenodeE<gt>> + +Some extra bits +Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> + +=head1 LICENCE + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +I<None known.> + +Please report any problems to L<https://github.com/shabble/irssi-scripts/issues/new> +or moan about it in C<#irssi@Freenode>. + +=head1 TODO + +=over 4 + +=item * Keep a watchlist of nicks in the channel, and only act to confirm if +they quit shortly before/during you typing a response. + +=back + + + +=cut + diff --git a/quit-notify/notifyquit.pl b/quit-notify/notifyquit.pl new file mode 100644 index 0000000..eb08d88 --- /dev/null +++ b/quit-notify/notifyquit.pl @@ -0,0 +1,466 @@ +=pod + +=head1 NAME + +notifyquit.pl + +=head1 DESCRIPTION + +A script intended to alert people to the fact that their conversation partners +have quit or left the channel, especially useful in high-traffic channels, or +where you have C<JOINS PARTS QUITS> ignored. + +=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. + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<notifyquit.pl>>. + +=head1 SETUP + +This script provides a single setting: + +C</SET notifyquit_exceptions>, which defaults to "C</^https?/ /^ftp/>" + +The setting is a space-separated list of regular expressions in the format +C</EXPR/>. If the extracted nickname matches any of these patterns, it isa +assumed to be a false-positive match, and is sent to the channel with no +further confirmation. + +=head1 USAGE + +When responding to users in a channel in the format C<$theirnick: some message> +(where the C<:> is not necessarily a colon, but the value of your +C<completion_char> setting), this script will check that the nickname still +exists in the channel, and will prompt you for confirmation if they have +since left. + +It is intended for use for people who ignore C<JOINS PARTS QUITS>, etc, and +try to respond to impatient people, or those with a bad connection. + +To send the message once prompted, either hit C<enter>, or C<y>.  Pressing C<n> +will abort sending, but leave the message in your input buffer just in case +you want to keep it. + +=head1 AUTHORS + +Original Copyright E<copy> 2011 Jari Matilainen C<E<lt>vague!#irssi@freenodeE<gt>> + +Some extra bits +Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> + +=head1 LICENCE + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +I<None known.> + +Please report any problems to L<https://github.com/shabble/irssi-scripts/issues/new> +or moan about it in C<#irssi@Freenode>. + +=head1 TODO + +=over 4 + +=item * Keep a watchlist of nicks in the channel, and only act to confirm if +they quit shortly before/during you typing a response. + +keep track of the most recent departures, and upon sending, see if one of them +is your target. If so, prompt for confirmation. + +So, add them on quit/kick/part, and remove them after a tiemout. + +=back + +=cut + +### +# +# Parts of the script pertaining to uberprompt borrowed from +# shabble (shabble!#irssi/@Freenode), thanks for letting me steal from you :P +# +### + +use strict; +use warnings; +use Data::Dumper; + +our $VERSION = "0.2"; +our %IRSSI = ( +              authors     => "Jari Matilainen", +              contact     => 'vague!#irssi@freenode', +              name        => "notifyquit", +              description => "Notify if user has left the channel", +              license     => "Public Domain", +              url         => "http://vague.se" +             ); + +my $active = 0; +my $permit_pending = 0; +my $pending_input = {}; +my $verbose = 0; +my @match_exceptions; +my $watchlist = {}; + +sub script_is_loaded { +    return exists($Irssi::Script::{$_[0] . '::'}); +} + +if (script_is_loaded('uberprompt')) { +    app_init(); +} else { +    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"); +    } +    app_init(); +} + +sub load_uberprompt_failed { +    Irssi::signal_remove('script error', 'load_prompt_failed'); + +    print "Script could not be loaded. Script cannot continue. " +        . "Check you have uberprompt.pl installed in your scripts directory and " +        .  "try again.  Otherwise, it can be fetched from: "; +    print "https://github.com/shabble/irssi-scripts/raw/master/" +        . "prompt_info/uberprompt.pl"; + +    die "Script Load Failed: " . join(" ", @_); +} + +sub extract_nick { +    my ($str) = @_; + +    my $completion_char +      = quotemeta(Irssi::settings_get_str("completion_char")); + +    # from BNF grammar at http://www.irchelp.org/irchelp/rfc/chapter2.html +    # special := '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}' + +    my $pattern = qr/^( [[:alpha:]]         # starts with a letter +                         (?: [[:alpha:]]         # then letter +                         | \d                # or number +                         | [\[\]\\`^\{\}-])  # or special char +                         *? )                # any number of times +                     $completion_char/x;     # followed by completion char. + +    if ($str =~ m/$pattern/) { +        return $1; +    } else { +        return undef; +    } + +} + +sub check_nick_exemptions { +    my ($nick) = @_; +    foreach my $except (@match_exceptions) { +        _debug("Testing nick $nick against $except"); +        if ($nick =~ $except) { +            _debug( "FAiled match $except"); +            return 0;           # fail +        } +    } +    _debug("match ok"); + +    return 1; +} + +sub sig_send_text { +    my ($data, $server, $witem) = @_; + +    return unless($witem); + +    return unless $witem->{type} eq 'CHANNEL'; + +    # shouldn't need escaping, but it doesn't hurt to be paranoid. +    my $target_nick = extract_nick($data); + +    if ($target_nick) { +        if (check_watchlist($target_nick, $witem, $server) +            and not $witem->nick_find($target_nick)) { + +            #return if $target_nick =~ m/^(?:https?)|ftp/i; +            return unless check_nick_exemptions($target_nick); + +            if ($permit_pending) { + +                $pending_input = {}; +                $permit_pending = 0; +                Irssi::signal_continue(@_); + +            } else { +                my $text +                  = "$target_nick isn't in this channel, send anyway? [Y/n]"; +                $pending_input +                  = { +                     text     => $data, +                     server   => $server, +                     win_item => $witem, +                    }; + +                Irssi::signal_stop; +                require_confirmation($text); +            } +        } +    } +} + +sub sig_gui_keypress { +    my ($key) = @_; + +    return if not $active; + +    my $char = chr($key); + +    # Enter, y, or Y. +    if ($char =~ m/^y?$/i) { +        $permit_pending = 1; +        Irssi::signal_stop; +        Irssi::signal_emit('send text', +                           $pending_input->{text}, +                           $pending_input->{server}, +                           $pending_input->{win_item}); +        $active = 0; +        set_prompt(''); + +    } elsif ($char =~ m/^n?$/i or $key == 3 or $key == 7) { +        # we support n, N, Ctrl-C, and Ctrl-G for no. + +        Irssi::signal_stop; +        set_prompt(''); + +        $permit_pending = 0; +        $active         = 0; +        $pending_input  = {}; + +    } else { +        Irssi::signal_stop; +        return; +    } +} + + +sub add_to_watchlist { +    my ($nick, $channel, $server) = @_; +    my $tag = $server->{tag}; +    _debug("Adding $nick to $channel/$tag"); + +    $watchlist->{$tag}->{$channel}->{$nick} = time(); +} + +sub check_watchlist { +    my ($nick, $channel, $server) = @_; +    my $tag = $server->{tag}; + +    my $check = exists ($watchlist->{$tag}->{$channel}->{$nick}); +    _debug("Check for $nick in $channel/$tag is " .( $check ? 'true' : 'false')); + +    return $check; +} + +sub remove_from_watchlist { +    my ($nick, $channel, $server) = @_; +    my $tag = $server->{tag}; + +    if (exists($watchlist->{$tag}->{$channel}->{$nick})) { +        delete($watchlist->{$tag}->{$channel}->{$nick}); +        _debug("Deleted $nick from $channel/$tag"); +    } +} + +sub start_watchlist_expire_timer { +    my ($nick, $channel, $server, $callback) = @_; + +    my $tag = $server->{tag}; +    my $timeout = Irssi::settings_get_time('notifyquit_timeout'); + +    Irssi::timeout_add_once($timeout, +                            $callback, +                            { nick => $nick, +                              channel => $channel, +                              server => $server, +                            }); +} + +sub sig_message_quit { +    my ($server, $nick, $address, $reason) = @_; + +    my $tag = $server->{tag}; + +    _debug( "$nick quit from $tag"); +    add_to_watchlist($nick, "***", $server); + +    my $quit_cb = sub { + +        # remove from all channels. +        foreach my $chan (keys %{ $watchlist->{$tag} }) { +            # if (exists $chan->{$nick}) { +            #     delete $watchlist->{$tag}->{$chan}->{$nick}; +            # } +            remove_from_watchlist($nick, $chan, $server) +        } +    }; + +    start_watchlist_expire_timer($nick, '***', $server, $quit_cb); + +} + +sub sig_message_part { +    my ($server, $channel, $nick, $address, $reason) = @_; + +    my $tag = $server->{tag}; + +    _debug( "$nick parted from $channel/$tag"); +    add_to_watchlist($nick, $channel, $server); +    my $part_cb = sub { +        remove_from_watchlist($nick, $channel, $server); +    }; + +    start_watchlist_expire_timer($nick, $channel, $server, $part_cb); + +} + +sub sig_message_kick { +    my ($server, $channel, $nick, $kicker, $address, $reason) = @_; +    _debug( "$nick kicked from $channel by $kicker"); + +    my $tag = $server->{tag}; +    add_to_watchlist($nick, $channel, $server); + +    my $kick_cb = sub { +        remove_from_watchlist($nick, $channel, $server); +    }; + +    start_watchlist_expire_timer($nick, $channel, $server, $kick_cb); +} + +sub sig_message_nick { +    my ($server, $newnick, $oldnick, $address) = @_; +    my $tag = $server->{tag}; + +    _debug("$oldnick changed nick to $newnick ($tag)"); +    #_debug( "Not bothering with this for now."); +    add_to_watchlist($newnick, '***', $server); +    remove_from_watchlist($oldnick, '***', $server); + +    my $nick_cb = sub {  +        remove_from_watchlist($newnick, '***', $server); +    }; + +    start_watchlist_expire_timer($newnick, '***', $server, $nick_cb); +} + +sub sig_message_join { +    my ($server, $channel, $nick) = @_; +    add_to_watchlist($nick, $channel, $server); + +} + +sub app_init { +    Irssi::signal_add('setup changed'         => \&sig_setup_changed); +    Irssi::signal_add_first('message quit'    => \&sig_message_quit); +    #Irssi::signal_add_first('message join'    => \&sig_message_join); +    Irssi::signal_add_first('message part'    => \&sig_message_part); +    Irssi::signal_add_first('message kick'    => \&sig_message_kick); +    Irssi::signal_add_first('message nick'    => \&sig_message_nick); +    Irssi::signal_add_first("send text"       => \&sig_send_text); +    Irssi::signal_add_first('gui key pressed' => \&sig_gui_keypress); +    Irssi::settings_add_str($IRSSI{name}, 'notifyquit_exceptions', '/^https?/ /^ftp/'); +    Irssi::settings_add_bool($IRSSI{name}, 'notifyquit_verbose', 0); +    Irssi::settings_add_time($IRSSI{name}, 'notifyquit_timeout', '30s'); + +    # horrible name, but will serve. +    Irssi::command_bind('notifyquit_show_exceptions', \&cmd_show_exceptions); +    Irssi::command_bind('notifyquit_show_watchlist', \&cmd_show_watchlist); + +    sig_setup_changed(); +} + +sub cmd_show_exceptions { + +    foreach my $e (@match_exceptions) { +        print "Exception: $e"; +    } +} + +sub cmd_show_watchlist { +    Irssi::print Dumper($watchlist); +} + +sub sig_setup_changed { + +    my $except_str = Irssi::settings_get_str('notifyquit_exceptions'); +    $verbose = Irssi::settings_get_bool('notifyquit_verbose'); +    my @except_list = split( m{(?:^|(?<=/))\s+(?:(?=/)|$)}, $except_str); + +    @match_exceptions = (); + +    foreach my $except (@except_list) { + +        _debug("Exception regex str: $except"); +        $except =~ s|^/||; +        $except =~ s|/$||; + +        next if $except =~ m/^\s*$/; + +        my $regex; + +        eval { +            $regex = qr/$except/i; +        }; + +        if ($@ or not defined $regex) { +            print "Regex failed to parse: \"$except\": $@"; +        } else { +            _debug("Adding match exception: $regex"); +            push @match_exceptions, $regex; +        } +    } +} + + +sub require_confirmation { +    $active = 1; +    set_prompt(shift); +} + +sub set_prompt { +    my ($msg) = @_; +    $msg = ': ' . $msg if length $msg; +    Irssi::signal_emit('change prompt', $msg, 'UP_INNER'); +} + +sub _debug { + +    return unless $verbose; + +    my ($msg, @params) = @_; +    my $str = sprintf($msg, @params); +    print $str; + +} diff --git a/readme_generator.pl b/readme_generator.pl new file mode 100755 index 0000000..e93239e --- /dev/null +++ b/readme_generator.pl @@ -0,0 +1,77 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +# Goal: extract the comments from the top of each script file, and +# turn them into some sort of markdown-style README.md for github to nom on. +# +# Not sure how it's going to work with multiple files in a dir though. Sections? + +# Change of plan! Github supports POD, so we just use Pod::Select to scrape it. + +use File::Find; +use File::Spec; +use Pod::Select; + +use feature qw/say/; +use Cwd; + +my $overwrite = $ARGV[0]; +if ($overwrite =~ m/--overwrite/) { +    shift @ARGV; # remove it form list of dirs. +    $overwrite = 1; +} else { +    $overwrite = 0; +} + +my @dirs = map { File::Spec->catdir(getcwd(), $_) } @ARGV; + +die unless @dirs; + +find(\&wanted, @dirs); + +sub wanted { +    my ($file, $dir, $path) = ($_, $File::Find::dir, $File::Find::name); +    return unless $file =~ m/\.pl$/; +    return if $file =~ m/^\./; + +    _err("processing file: $path"); +    create_output_file($dir, $file); +} + +sub create_output_file { +    my ($dir, $in_file) = @_; + +    my $parser = Pod::Select->new; + +    my $out_file = "README.pod"; + +    my $in_file_path = File::Spec->catfile($dir, $in_file); +    my $out_file_path = File::Spec->catfile($dir, $out_file); +    my $sec_sep = ''; + +    if (-f $out_file_path and not $overwrite) { +        _err("$out_file_path already exists, going to append") unless $overwrite; +        $sec_sep = "\n\n=for html <br />\n\n"; +    } + +    my $mode = $overwrite ? '>' : '>>'; + +    _err("Writing to $mode $out_file_path"); + +    open my $wfh, $mode, $out_file_path +      or die "Couldn't open $out_file_path for $mode output: $!"; + +    $parser->parse_from_file($in_file_path, $wfh); + +    print $wfh "\n\n=cut\n\n"; + +    close $wfh; +} + +sub _err { +    my ($msg, @args) = @_; +    my $str = sprintf($msg, @args); +    say STDERR $str; +} diff --git a/scrolled-reminder/scrolled_reminder.pl b/scrolled-reminder/scrolled_reminder.pl index 48da622..efe6630 100644 --- a/scrolled-reminder/scrolled_reminder.pl +++ b/scrolled-reminder/scrolled_reminder.pl @@ -82,13 +82,7 @@ our %IRSSI =  # check we have prompt_info loaded.  sub script_is_loaded { -    my $name = shift; -    print "Checking if $name is loaded" if DEBUG; -    no strict 'refs'; -    my $retval = defined %{ "Irssi::Script::${name}::" }; -    use strict 'refs'; - -    return $retval; +    return exists($Irssi::Script::{$_[0] . '::'}) ;  }  unless (script_is_loaded('uberprompt')) { diff --git a/testing/.gitignore b/testing/.gitignore new file mode 100644 index 0000000..119fe08 --- /dev/null +++ b/testing/.gitignore @@ -0,0 +1,5 @@ +Makefile +Makefile.old +blib/ +irssi.log +pm_to_blib diff --git a/testing/Changes b/testing/Changes new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/testing/Changes diff --git a/testing/MANIFEST b/testing/MANIFEST new file mode 100644 index 0000000..1810c3e --- /dev/null +++ b/testing/MANIFEST @@ -0,0 +1,13 @@ +Makefile.PL +MANIFEST +MANIFEST.SKIP +README +Changes + +t/001-use.t +t/002-init.t + +lib/Test/Irssi.pm +lib/Test/Irssi/Callbacks.pm +lib/Test/Irssi/Driver.pm +lib/Test/Irssi/Test.pm
\ No newline at end of file diff --git a/testing/MANIFEST.SKIP b/testing/MANIFEST.SKIP new file mode 100644 index 0000000..1bfcbf3 --- /dev/null +++ b/testing/MANIFEST.SKIP @@ -0,0 +1,9 @@ +.*\.git.* +pm_to_blib +.*\.old +.*\.bak +.*\.swp +blib/.* +^Makefile$ +\d+_local_ + diff --git a/testing/Makefile.PL b/testing/Makefile.PL new file mode 100644 index 0000000..3312c95 --- /dev/null +++ b/testing/Makefile.PL @@ -0,0 +1,30 @@ +use strict; +use warnings; +use Cwd; +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. + +WriteMakefile( +              NAME              => 'Test::Irssi', +              AUTHOR            => 'shabble <shabble+cpan@metavore.org>', +              VERSION_FROM      => 'lib/Test/Irssi.pm', # finds $VERSION +              ABSTRACT_FROM     => 'lib/Test/Irssi.pm', +              PL_FILES          => {}, +              # LIBS              => ["-L/opt/local/lib -lcprops"], +              # INC               => "-I/opt/local/include/cprops", +              PREREQ_PM         => { +                                    'Test::More'        => 0, +                                    'Carp'              => 0, +                                    'MooseX::Declare'   => 0, +                                    'IO::File'          => 0, +                                    'Term::VT102'       => 0, +                                    'Term::Terminfo'    => 0, +                                    'strictures'        => 0, +                                    'Data::Dump'        => 0, +                                   }, +              dist                => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, +              # clean               => { +              #                         FILES => 'CProps-Trie-* Trie.inl _Inline' +              #                        }, +             ); diff --git a/testing/README b/testing/README new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/testing/README diff --git a/testing/lib/Test/Irssi.pm b/testing/lib/Test/Irssi.pm new file mode 100644 index 0000000..0db7ee0 --- /dev/null +++ b/testing/lib/Test/Irssi.pm @@ -0,0 +1,314 @@ +use strictures 1; +use MooseX::Declare; + +our $VERSION = 0.02; + +class Test::Irssi { + +    # requires the latest pre-release POE from +    # https://github.com/rcaputo/poe until a new release is...released. +    use lib $ENV{HOME} . "/projects/poe/lib"; +    use POE; + +    use Term::VT102; +    use Term::Terminfo; +    use feature qw/say switch/; +    use Data::Dump; +    use IO::File; + +    use Test::Irssi::Driver; +    use Test::Irssi::Callbacks; +    use Test::Irssi::Test; + +    has 'generate_tap' +      => ( +          is       => 'rw', +          isa      => 'Bool', +          required => 1, +          default  => 1, +         ); + +    has 'irssi_binary' +      => ( +          is       => 'ro', +          isa      => 'Str', +          required => 1, +         ); + +    has 'irssi_homedir' +      => ( +          is       => 'ro', +          isa      => 'Str', +          required => 1, +         ); + +    has 'terminal_width' +      => ( +          is       => 'ro', +          isa      => 'Int', +          required => 1, +          default  => 80, +         ); + +    has 'terminal_height' +      => ( +          is       => 'ro', +          isa      => 'Int', +          required => 1, +          default  => 24, +         ); + +    has 'vt' +      => ( +          is       => 'ro', +          isa      => 'Term::VT102', +          required => 1, +          lazy     => 1, +          builder  => '_build_vt_obj', +         ); + +    has 'logfile' +      => ( +          is       => 'ro', +          isa      => 'Str', +          required => 1, +          default  => 'irssi-test.log', +         ); + +    has '_logfile_fh' +      => ( +          is       => 'ro', +          isa      => 'IO::File', +          required => 1, +          lazy     => 1, +          builder  => '_build_logfile_fh', +         ); + +    has 'driver' +      => ( +          is       => 'ro', +          isa      => 'Test::Irssi::Driver', +          required => 1, +          lazy     => 1, +          builder  => '_build_driver_obj', +          handles  => { +                       run_headless => 'headless', +                      } +         ); + +    has '_callbacks' +      => ( +          is       => 'ro', +          isa      => 'Test::Irssi::Callbacks', +          required => 1, +          lazy     => 1, +          builder  => '_build_callback_obj', +         ); + +    has 'pending_tests' +      => ( +          is       => 'ro', +          isa      => "ArrayRef", +          required => 1, +          default  => sub { [] }, +          traits   => [qw/Array/], +          handles  => { +                       add_pending_test  => 'push', +                       next_pending_test => 'shift', +                       tests_remaining   => 'count', +                      } +         ); + +    has 'completed_tests' +      => ( +          is       => 'ro', +          isa      => "ArrayRef", +          required => 1, +          default  => sub { [] }, +          traits   => [qw/Array/], +          handles  => { +                       add_completed_test => 'push', +                       tests_completed => 'count', +                      }, +         ); + +    has 'active_test' +      => ( +          is  => 'rw', +          isa => 'Test::Irssi::Test', +         ); + +    sub new_test { +        my ($self, $name, @params) = @_; +        my $new = Test::Irssi::Test->new(name => $name,  +                                         parent => $self, +                                         @params); +        $self->add_pending_test($new); +        return $new; +    } + +    method _build_callback_obj { +        Test::Irssi::Callbacks->new(parent => $self); +    } + +    method _build_driver_obj { +        Test::Irssi::Driver->new(parent => $self); +    } + +    method _build_vt_obj { +        my $rows = $self->terminal_height; +        my $cols = $self->terminal_width; + +        Term::VT102->new($cols, $rows); +    } + +    method _build_logfile_fh { + +        my $logfile = $self->logfile; + +        my $fh = IO::File->new($logfile, 'w'); +        die "Couldn't open $logfile for writing: $!" unless defined $fh; +        $fh->autoflush(1); + +        return $fh; +    } + +    method _vt_setup { +        # options +        my $vt = $self->vt; + +        $vt->option_set(LINEWRAP => 1); +        $vt->option_set(LFTOCRLF => 1); + +        $self->_callbacks->register_callbacks; + +    } +    method screenshot { +        my $data = ''; +        my $vt = $self->vt; +        foreach my $row (1 .. $vt->rows) { +            $data .= $vt->row_plaintext($row) . "\n"; +        } +        return $data; +    } + +    method complete_test { +        # put the completed one onto the completed pile +        my $old_test = $self->active_test; +        $self->add_completed_test($old_test); + +        # TAP: print status. +        if ($self->generate_tap) { +            my $pass = $old_test->passed; +            my $tap = sprintf("%s %d - %s", $pass?'ok':'not ok', +                              $self->tests_completed, +                              $old_test->description); +            say STDOUT $tap; +            if (not $pass) { +                $old_test->details; +                $self->log("-------------------"); +                $self->log($self->screenshot); +                $self->log("-------------------"); + +            } +        } +    } + +    method run_test { +        # and make the next pending one active. +        my $test = $self->next_pending_test; +        $self->active_test($test); + +        # signal to the driver to start executing it. +        $poe_kernel->post(IrssiTestDriver => execute_test => $test); +    } + +    method run { + +        $self->driver->setup; +        $self->_vt_setup; +        $self->log("Driver setup complete"); +        ### Start a session to encapsulate the previous features. + +        # TAP: print number of tests. +        if ($self->generate_tap) { +            print STDOUT "1.." . $self->tests_remaining . "\n"; +        } + +        $poe_kernel->run(); +    } + +    sub apply_delay { +        my ($self, $delay, $next_index) = @_; +        $poe_kernel->post(IrssiTestDriver +                          => create_delay +                          => $delay, $next_index); +    } + +    # TODO: pick one. +    sub inject_text { +        my ($self, $text) = @_; +        $poe_kernel->post(IrssiTestDriver => got_terminal_stdin +                          => $text); +    } + +    sub simulate_keystroke { +        my ($self, $text) = @_; +        $poe_kernel->post(IrssiTestDriver => got_terminal_stdin +                          => $text); + +    } + +    method get_topic_line { +        return $self->vt->row_plaintext(1); +    } + +    method get_prompt_line { +        return $self->vt->row_plaintext($self->terminal_height); +    } + +    method get_window_statusbar_line { +        return $self->vt->row_plaintext($self->terminal_height() - 1); +    } + +    method get_window_contents { +        my $buf = ''; +        for (2..$self->terminal_height() - 2) { +            $buf .=  $self->vt->row_plaintext($_); +        } +        return $buf; +    } + +    method get_cursor_position { +        return ($self->vt->x(), $self->vt->y()); +    } + +    method load_script { +        my ($script_name) = @_; + +    } + +    method summarise_test_results { +        foreach my $test (@{$self->completed_tests}) { +            my $name = $test->name; +            printf("Test %s\t\t-\t%s\n", $name, $test->passed?"pass":"fail"); +            $test->details(); +        } +    } + +    sub log { +        my ($self, $msg) = @_; +        $self->_logfile_fh->say($msg); +    } + +} + +  __END__ + +=head1 NAME + +Test::Irssi - A cunning testing system for Irssi scripts + +=head1 SYNOPSIS + +blah blah blah diff --git a/testing/lib/Test/Irssi/Callbacks.pm b/testing/lib/Test/Irssi/Callbacks.pm new file mode 100644 index 0000000..8321ace --- /dev/null +++ b/testing/lib/Test/Irssi/Callbacks.pm @@ -0,0 +1,123 @@ +use strictures 1; + +package Test::Irssi::Callbacks; + +use Moose; +use Data::Dump qw/dump/; +use Data::Dumper; + +has 'parent' +  => ( +      is       => 'ro', +      isa      => 'Test::Irssi', +      required => 1, +     ); + +sub register_callbacks { +    my ($self) = @_; + +    my $vt = $self->parent->vt; +    $self->log("Callbacks registered"); + +    $vt->callback_set(OUTPUT      => sub { $self->vt_output(@_)    }, undef); +    $vt->callback_set(ROWCHANGE   => sub { $self->vt_rowchange(@_) }, undef); +    $vt->callback_set(CLEAR       => sub { $self->vt_clear(@_)     }, undef); +    $vt->callback_set(SCROLL_DOWN => sub { $self->vt_scr_up(@_)    }, undef); +    $vt->callback_set(SCROLL_UP   => sub { $self->vt_scr_dn(@_)    }, undef); +    $vt->callback_set(GOTO        => sub { $self->vt_goto(@_)      }, undef); + +} + +sub vt_output { +    my ($self, $vt, $cb_name, $cb_data) = @_; +    $self->log( "OUTPUT: " . dump([@_[1..$#_]])); +} + +sub vt_rowchange { +    my $self = shift; +    my ($vt, $cb_name, $arg1, $arg2) = @_; + +    $arg1 //= '?'; +    $arg2 //= '?'; + +    $self->log( "-" x 100); +    $self->log( "Row $arg1 changed: "); + +    my $bottom_line = $vt->rows(); + +    $self->log( "-" x 100); +    $self->log( "Window Line"); +    $self->log( "-" x 100); +    $self->log(  $vt->row_plaintext($bottom_line - 1)); +    $self->log( "-" x 100); +    $self->log( "Prompt line"); +    $self->log( "-" x 100); +    $self->log(  $vt->row_plaintext($bottom_line)); + +} + +sub vt_clear { +    my $self = shift; +    my ($vt, $cb_name, $arg1, $arg2) = @_; +    $arg1 //= '?'; +    $arg2 //= '?'; + +    $self->log( "VT Cleared"); +} + +sub vt_scr_dn { +    my $self = shift; +    my ($vt, $cb_name, $arg1, $arg2) = @_; +    $arg1 //= '?'; +    $arg2 //= '?'; + +    $self->log( "Scroll Down"); +} + +sub vt_scr_up { +    my $self = shift; +    my ($vt, $cb_name, $arg1, $arg2) = @_; +    $arg1 //= '?'; +    $arg2 //= '?'; + +    $self->log( "Scroll Up"); +} + + +sub vt_goto { +    my $self = shift; +    my ($vt, $cb_name, $arg1, $arg2) = @_; +    $arg1 //= '?'; +    $arg2 //= '?'; + +    $self->log( "Goto: $arg1, $arg2"); +} + +sub vt_dump { +    my ($self) = @_; +    my $vt = $self->parent->vt; +    my $rows = $self->parent->terminal_height; +    my $str = ''; +    for my $y (1..$rows) { +        $str .= $vt->row_sgrtext($y) . "\n"; +    } + +    return $str; +} + +sub log { +    my ($self, $msg) = @_; +    #$self->parent->_logfile_fh->say($msg); +} + +__PACKAGE__->meta->make_immutable; + +no Moose; + + + +# # delegate to Callbacks. +# sub vt_dump { +#     my ($self) = @_; +#     my $cb = $self->parent->_callbacks->vt_dump(); +# } diff --git a/testing/lib/Test/Irssi/Driver.pm b/testing/lib/Test/Irssi/Driver.pm new file mode 100644 index 0000000..6b4e5e5 --- /dev/null +++ b/testing/lib/Test/Irssi/Driver.pm @@ -0,0 +1,258 @@ +use strictures 1; + +package Test::Irssi::Driver; + +use Moose; +use lib $ENV{HOME} . "/projects/poe/lib"; + +use POE qw( Wheel::ReadWrite Wheel::Run Filter::Stream ); +use POSIX; +use feature qw/say/; +use Data::Dump qw/dump/; + +has 'parent' +  => ( +      is       => 'ro', +      isa      => 'Test::Irssi', +      required => 1, +     ); + +has 'headless' +  => ( +      is      => 'rw', +      isa     => 'Bool', +      default => 0, +     ); + +sub  START { +    my ($self, $kernel, $heap) = @_[OBJECT, KERNEL, HEAP]; + +    $kernel->alias_set("IrssiTestDriver"); + +    $self->log("Start handler called"); + +    $self->save_term_settings($heap); + +    # Set a signal handler. +    $kernel->sig(CHLD => "got_sigchld"); + +    $self->make_raw_terminal; + +    my @stdio_options = +      ( +       InputHandle  => \*STDIN, +       OutputHandle => \*STDOUT, +       InputEvent   => "got_terminal_stdin", +       Filter       => POE::Filter::Stream->new(), +      ); + +    $self->log("stdio options: " . dump(@stdio_options)); + +    # Start the terminal reader/writer. +    $heap->{stdio} = POE::Wheel::ReadWrite->new(@stdio_options); + +    $self->log("Created stdio wheel"); + +    my $rows = $self->parent->terminal_height; +    my $cols = $self->parent->terminal_width; + +    my @program_options = +      ( +       Program     => $self->parent->irssi_binary, +       ProgramArgs => ['--noconnect', '--home=' . $self->parent->irssi_homedir ], +       Conduit     => "pty", +       Winsize     => [$rows, $cols, 0, 0], +       StdoutEvent => "got_child_stdout", +       StdioFilter => POE::Filter::Stream->new(), +      ); + +    $self->log("wheel options: " . dump(@program_options)); + +    # Start the asynchronous child process. +    $heap->{program} = POE::Wheel::Run->new(@program_options); + +    $self->log("Created child run wheel"); +    $poe_kernel->yield('testing_ready'); +} + +sub STOP { +    my ($self, $heap) = @_[OBJECT,HEAP]; +    $self->log("STOP called"); +    $self->restore_term_settings($heap); +    $self->parent->_logfile_fh->close(); + +    if (not $self->parent->generate_tap) { +        $self->parent->summarise_test_results(); +    } +} + +### Handle terminal STDIN.  Send it to the background program's STDIN. +### If the user presses ^C, then echo a little string + +sub terminal_stdin { +    my ($self, $heap, $input) = @_[OBJECT, HEAP, ARG0]; + +    if ($input =~ m/\003/g) { # C-c +        $input = "/echo I like cakes\n"; +    } elsif ($input =~ m/\x17/g) { # C-w +        $input = "/quit\n"; +    } + +    $heap->{program}->put($input); +} + +### Handle STDOUT from the child program. +sub child_stdout { +    my ($self, $heap, $input) = @_[OBJECT, HEAP, ARG0]; +    # process via vt +    $self->parent->vt->process($input); + +    if (not $self->headless) { +        # send to terminal +        $heap->{stdio}->put($input); +    } +} + +### Handle SIGCHLD.  Shut down if the exiting child process was the +### one we've been managing. + +sub shutdown { +    my ($self, $heap, $kernel) = @_[OBJECT, HEAP, KERNEL]; +    $self->log("Shutdown called"); +    $heap->{program}->kill(15); +    $kernel->alias_remove("IrssiTestDriver"); +} + +sub CHILD { +    my ($self, $heap, $child_pid) = @_[OBJECT, HEAP, ARG1]; +    if ($child_pid == $heap->{program}->PID) { +        delete $heap->{program}; +        delete $heap->{stdio}; +    } +    return 0; +} + +sub setup { +    my $self = shift; + +    my @states = +      ( +       object_states => +       [ $self => +         { +          _start             => 'START', +          _stop              => 'STOP', +          got_sigchld        => 'CHILD', + +          got_terminal_stdin => 'terminal_stdin', +          got_child_stdout   => 'child_stdout', + +          got_delay          => 'timer_expired', +          create_delay       => 'timer_created', + + +          testing_ready      => 'testing_ready', +          test_complete      => 'test_complete', +          execute_test       => 'execute_test', + +          shutdown           => 'shutdown', +         } +       ] +      ); +    $self->log("creating root session"); + +    POE::Session->create(@states); +    $self->log("session created"); + +} + +sub testing_ready { +    my ($self) = $_[OBJECT]; +    # begin by fetching a test from the pending queue. +    $self->log("Starting to run tests"); +    $self->log("-" x 80); +    $self->parent->run_test; +} + +sub execute_test { +    my ($self, $heap, $kernel, $test) = @_[OBJECT,HEAP, KERNEL, ARG0]; +    # do some stuff here to evaluate it. + +    $test->evaluate_test; + +} + +sub test_complete { +    my ($self, $kernel) = @_[OBJECT, KERNEL]; + +    $self->parent->complete_test; + +    if ($self->parent->tests_remaining) { +        $self->parent->run_test; +    } + +    # otherwise, we're done, and can shutdown. +   #kernel->yield('shutdown'); + +} + +sub timer_created { +    my ($self, $heap, $kernel, $duration) = @_[OBJECT, HEAP, KERNEL, ARG0]; +    $kernel->delay(got_delay => $duration); +    $self->log("Timer created for $duration"); +} + +sub timer_expired { +    my ($self, $data) = @_[OBJECT,ARG0]; +    $self->log("Timeout invoking test again."); +    $self->parent->active_test->resume_from_timer; +} + +sub save_term_settings { +    my ($self, $heap) = @_; +    # Save the original terminal settings so they can be restored later. +    $heap->{stdin_tio} = POSIX::Termios->new(); +    $heap->{stdin_tio}->getattr(0); +    $heap->{stdout_tio} = POSIX::Termios->new(); +    $heap->{stdout_tio}->getattr(1); +    $heap->{stderr_tio} = POSIX::Termios->new(); +    $heap->{stderr_tio}->getattr(2); +} + +sub restore_term_settings { +    my ($self, $heap) = @_; + +    $heap->{stdin_tio}->setattr (0, TCSANOW); +    $heap->{stdout_tio}->setattr(1, TCSANOW); +    $heap->{stderr_tio}->setattr(2, TCSANOW); +} + +sub make_raw_terminal { +    my ($self) = @_; +    # Put the terminal into raw input mode.  Otherwise discrete +    # keystrokes will not be read immediately. +    my $tio = POSIX::Termios->new(); +    $tio->getattr(0); +    my $lflag = $tio->getlflag; +    $lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG); +    $tio->setlflag($lflag); +    my $iflag = $tio->getiflag; +    $iflag &= ~(BRKINT | INPCK | ISTRIP | IXON); +    $tio->setiflag($iflag); +    my $cflag = $tio->getcflag; +    $cflag &= ~(CSIZE | PARENB); +    $tio->setcflag($cflag); +    $tio->setattr(0, TCSANOW); +} + +sub log { +    my ($self, $msg) = @_; +    my $fh = $self->parent->_logfile_fh; +    $fh->say($msg); +} + + +__PACKAGE__->meta->make_immutable; + +no Moose; + diff --git a/testing/lib/Test/Irssi/Misc.pm b/testing/lib/Test/Irssi/Misc.pm new file mode 100644 index 0000000..a6339e0 --- /dev/null +++ b/testing/lib/Test/Irssi/Misc.pm @@ -0,0 +1,35 @@ +package Test::Irssi::Misc; +use strictures 1; + + + +sub keycombo_to_code { +    my ($key_combo) = @_; +    my $output = ''; +    my $ctrl = 0; +    my $meta = 0; +    if ($key_combo =~ m/[cC](?:trl)?-(.+)/) { +        $ctrl = 1; +        _parse_rest($1); +    } +    if ($key_combo =~ m/[Mm](?:eta)?-(.+)/) { +        $meta = 1; +        _parse_rest($1); +    } +} + +sub _parse_key { +    my ($rest) = @_; +    my $special = { +                   left => '', +                   right => '', +                   up => '', +                   down => '', +                   tab => '', +                   space => '', +                   spc => '', +                  }; +} + + +1; diff --git a/testing/lib/Test/Irssi/Test.pm b/testing/lib/Test/Irssi/Test.pm new file mode 100644 index 0000000..cd0a6f9 --- /dev/null +++ b/testing/lib/Test/Irssi/Test.pm @@ -0,0 +1,308 @@ +use strictures 1; +use MooseX::Declare; + +class Test::Irssi::Test { + +    use POE; +    use Test::Irssi; +    use Test::Irssi::Driver; +    use feature qw/say/; +    use Data::Dump qw/dump/; + +    has 'parent' +      => ( +          is       => 'ro', +          isa      => 'Test::Irssi', +          required => 1, +         ); + +    has 'name' +      => ( +          is       => 'ro', +          isa      => 'Str', +          required => 1, +         ); + +    has 'description' +      => ( +          is      => 'rw', +          isa     => 'Str', +          default => '', +         ); + +    has 'states' +      => ( +          is      => 'ro', +          isa     => 'ArrayRef', +          traits  => [qw/Array/], +          default => sub { [] }, +          lazy    => 1, +          handles => { +                      add_state   => 'push', +                      state_count => 'count', +                      get_state   => 'get', +                     }, +         ); + +    has 'results' +      => ( +          is      => 'ro', +          isa     => 'ArrayRef', +          default => sub { [] }, +         ); + +    has 'complete' +      => ( +          is      => 'rw', +          isa     => 'Bool', +          default => 0, +         ); + + +    has '_next_state' +      => ( +          is      => 'rw', +          isa     => 'Int', +          default => 0, +          traits  => [qw/Counter/], +          handles => { +                      _increment_state_counter => 'inc', +                      _clear_state             => 'reset', +                     }, +         ); + +    # TODO: should only be valid when complete is set. +    sub passed { +        my $self = shift; +        my $pass = 0; +        foreach my $result (@{$self->results}) { +            $pass = $result; +        } +        return $pass and $self->complete; +    } + +    sub failed { +        my $self = shift; +        return not $self->passed; +    } + + +    sub details { +        my ($self) = shift; +        my $state_count = $self->state_count; +        for (0..$state_count-1) { +            my $state  = $self->states->[$_]; +            my $result = $self->results->[$_]; +            say( "#\t" . $state->{type} . " - " . $state->{desc} . " " +              . " = " .( $result?"ok":"not ok")); +        } +    } +    ############# API FUNCTIONS ########################################## + + +    method add_input_sequence(Str $input) { +        $self->add_state({type  => 'command', +                          of    => 'input', +                          input => $input, +                          desc  => 'input'}); + +        $self->log("Adding $input as input"); +    } + +    method add_delay (Num $delay) { +        $self->add_state({type  => 'command', +                          of    => 'delay', +                          desc  => 'delay', +                          delay => $delay }); +        $self->log("Adding $delay as delay"); + +    } + +    method add_keycode(Str $code) { +        my $input = $self->translate_keycode($code); +        $self->add_state({type  => 'command', +                          desc  => 'input', +                          input => $input }); +        $self->log("Adding $input ($code) as input"); + +    } +    sub add_diag { +        my ($self, $diag) = @_; +        $self->add_state({type => 'command', +                          of   => 'diag', +                          desc => $diag }); +    } + +    sub add_pattern_match { +        my ($self, $pattern, $constraints, $desc) = @_; +        $self->add_state({type        => 'test', +                          of          => 'pattern', +                          pattern     => $pattern, +                          constraints => $constraints, +                          desc        => $desc}); + +        $self->log("Adding $pattern as output match "); +    } + +    sub test_cursor_position { +        my ($self, $x, $y, $desc) = @_; +        $self->add_state({type => 'test', +                          of   => 'cursor', +                          x    => $x, +                          y    => $y, +                          desc => $desc }); +        $self->log("Adding cursor [$x, $y] test "); + +    } + +    sub add_evaluation_function { +        my ($self, $coderef, $desc) = @_; +        $self->add_state({type => 'test', +                          of   => 'function', +                          code => $coderef, +                          desc => $desc}); +    } + + +    ############# END OF API FUNCTIONS #################################### + + + +    method translate_keycode(Str $code) { +        my $seq = ''; +        if ($code =~ m/M-([a-z])/i) { +            $seq = "\x1b" . $1; +        } elsif ($code =~ m/C-([a-z])/i) { +            $seq = chr ( ord(lc $1) - 64 ); +        } +        return $seq; +    } + +    method this_state { +        return $self->_next_state - 1; +    } + +    sub check_output { +        my ($self, $data) = @_; + +        my ($pattern, $constraints) = ($data->{pattern}, $data->{constraints}); + +        my $ok = 0; +        my $line = ''; +        if ($constraints eq 'prompt') { +            $line = $self->parent->get_prompt_line; +        } elsif ($constraints eq 'window_sbar') { +            $line = $self->parent->get_window_statusbar_line; +        } elsif ($constraints eq 'window') { +            # NOTE: not actually a line. +            $line = $self->parent->get_window_contents; +        } elsif ($constraints eq 'topic') { +            $line = $self->parent->get_topic_line; +        } + +        $self->log("Testing pattern against: '$line'"); + +        if ($line =~ m/$pattern/) { +            $self->log("Pattern $pattern passed"); +            $self->results->[$self->this_state] = 1; +        } else { +            $self->log("Pattern $pattern failed"); +            $self->results->[$self->this_state] = 0;; +        } +    } + +    sub get_next_state { +        my ($self) = @_; +        my $item = $self->get_state($self->_next_state); +        $self->_increment_state_counter; + +        return $item; +    } + +    sub evaluate_test { +        my ($self) = @_; + +        while (my $state = $self->get_next_state) { + +            $self->log("Evaluating Test: " . dump($state)); + +            my $type = $state->{type}; + +            if ($type eq 'command') { +                my $subtype = $state->{of}; + +                if ($subtype eq 'diag') { +                    if ($self->parent->generate_tap) { +                        say STDOUT '#' . $state->{desc}; +                    } +                } +                if ($subtype eq 'input') { +                    $self->parent->inject_text($state->{input}); +                    $self->log("input: ". $state->{input}); +                } +                if ($subtype eq 'delay') { +                    $self->log("inserting delay"); +                    $self->parent->apply_delay($state->{delay}); +                    $self->results->[$self->this_state] = 1; +                    return; +                } + +                # all commands are considered to succeed. +                $self->results->[$self->this_state] = 1; + +            } elsif ($type eq 'test') { + +                my $test_type = $state->{of}; + +                if ($test_type eq 'pattern') { +                    my $pattern = $state->{pattern}; +                    $self->check_output($state); +                } +                if ($test_type eq 'cursor') { +                    my ($curs_x, $curs_y) = $self->parent->get_cursor_position; + +                    my $ret = 0; +                    if ($state->{x} == $curs_x and $state->{y} == $curs_y) { +                        $ret = 1; +                    } + +                    $self->results->[$self->this_state] = $ret; + +                } + +                if ($test_type eq 'function') { +                    # code evaluation +                    my @args = ($self, $self->parent, $self->parent->vt); +                    my $ret = $state->{code}->(@args); +                    $ret //= 0; # ensure that undef failures are +                    # marked as such. +                    $self->results->[$self->this_state] = $ret; +                } +            } else { +                # wtf? +            } +        } + +        $poe_kernel->post(IrssiTestDriver => 'test_complete'); + +        $self->complete(1); + +        $self->log("Test Execution Finished"); +    } + +    sub resume_from_timer { +        my ($self) = @_; +        $self->log("Resuming after timeout"); +        $self->evaluate_test; +    } +    sub log { +        my ($self, $msg) = @_; +        $self->parent->_logfile_fh->say($msg); +    } + +  sub _all { $_ || return 0 for @_; 1 } +} + + + +  __END__ diff --git a/testing/lib/Test/Irssi/VirtualIrssi.pm b/testing/lib/Test/Irssi/VirtualIrssi.pm new file mode 100644 index 0000000..dc3bfc7 --- /dev/null +++ b/testing/lib/Test/Irssi/VirtualIrssi.pm @@ -0,0 +1,32 @@ +use strictures 1; +use MooseX::Declare; + +class Test::Irssi::VirtualIrssi { + +# class that pretends to be irssi which you can pull out various data from. + + +has cursor + => ( +     is      => 'ro', +     writer  => '_set_cursor', +     isa     => 'ArrayRef[Int]', +     default => sub { [0, 0] }, +    ); + +has topic_row + => ( +    ); + +has window_row + => ( +    ); + +has prompt_row + => ( +    ); + +has window + => ( +    ); +} diff --git a/testing/t/001-use.t b/testing/t/001-use.t new file mode 100755 index 0000000..6ebbb5a --- /dev/null +++ b/testing/t/001-use.t @@ -0,0 +1,27 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; +use Data::Dumper; + +BEGIN { +    use_ok 'Test::Irssi'; +} + + +my $test = new_ok 'Test::Irssi', +  [irssi_binary => 'null', irssi_homedir => 'null']; + +my @methods = qw/logfile terminal_height terminal_width irssi_homedir irssi_binary/; +can_ok($test, @methods); + +undef $test; + +done_testing; + +__END__ + + + diff --git a/testing/t/002-init.t b/testing/t/002-init.t new file mode 100755 index 0000000..b688f9f --- /dev/null +++ b/testing/t/002-init.t @@ -0,0 +1,33 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; +use Data::Dumper; + +BEGIN { +    use_ok 'Test::Irssi'; +} + + +my $test = new_ok 'Test::Irssi', +  [irssi_binary  => "/opt/stow/repo/irssi-debug/bin/irssi", +   irssi_homedir => $ENV{HOME} . "/projects/tmp/test/irssi-debug"]; + +if (-f $test->logfile) { +    ok(unlink $test->logfile, 'deleted old logfile'); +} + +my $drv = $test->driver; +isa_ok($drv, 'Test::Irssi::Driver', 'driver created ok'); + +diag "Starting POE session"; +$test->run(); + +done_testing; + +__END__ + + + diff --git a/testing/test-shim.pl b/testing/test-shim.pl new file mode 100644 index 0000000..628f7af --- /dev/null +++ b/testing/test-shim.pl @@ -0,0 +1,114 @@ +use strict; +use warnings; + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; +use POSIX; +use Time::HiRes qw/sleep/; +use JSON::Any; + + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => 'test-shim', +              description => '', +              license     => 'Public Domain', +             ); + + +my $forked = 0; + +sub pipe_and_fork { +    my ($read_handle, $write_handle); +    pipe($read_handle, $write_handle); + +    my $oldfh = select($write_handle); +    $| = 1; +    select $oldfh; + +    return if $forked; + +    my $pid = fork(); + +    if (not defined $pid) { +        _error("Can't fork: Aborting"); +        close($read_handle); +        close($write_handle); +        return; +    } + +    $forked = 1; + +    if ($pid > 0) { # this is the parent (Irssi) +        close ($write_handle); +        Irssi::pidwait_add($pid); +        my $job = $pid; +        my $tag; +        my @args = ($read_handle, \$tag, $job); +        $tag = Irssi::input_add(fileno($read_handle), +                                      Irssi::INPUT_READ, +                                      \&child_input, +                                      \@args); + +    } else { # child +        child_process($write_handle); +        close $write_handle; + +        POSIX::_exit(1); +    } +} +sub _cleanup_child { +    my ($read_handle, $input_tag_ref) = @_; +    close $read_handle; +    Irssi::input_remove($$input_tag_ref); +    _msg("child finished"); +    $forked = 0; +} +sub child_input { +    my $args = shift; +    my ($read_handle, $input_tag_ref, $job) = @$args; + +    my $input = <$read_handle>; +    my $data = JSON::Any::jsonToObj($input); +    if (ref $data  ne 'HASH') {  +        _error("Invalid data received: $input"); +        _cleanup_child($read_handle, $input_tag_ref); +    } + +    if (exists $data->{connection}) { +        if ($data->{connection} eq 'close') { +            _cleanup_child($read_handle, $input_tag_ref); +        } +    } else { +        parent_process_response($data); +    } +} + +sub parent_process_response { +    my ($data) = @_; +} + + +sub child_process { +    my ($handle) = @_; + +} + +sub _error { +    my ($msg) = @_; +    my $win = Irssi::active_win(); +    $win->print($msg, Irssi::MSGLEVEL_CLIENTERROR); +} + +sub _msg { +    my ($msg) = @_; +    my $win = Irssi::active_win(); +    $win->print($msg, Irssi::MSGLEVEL_CLIENTCRAP); +} + +Irssi::command_bind("start_pipes", \&pipe_and_fork); diff --git a/testing/test.pl b/testing/test.pl new file mode 100755 index 0000000..bf01530 --- /dev/null +++ b/testing/test.pl @@ -0,0 +1,17 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use feature qw/say/; +#use lib 'blib/lib'; + +use TAP::Harness; +my $harness = TAP::Harness->new({ verbosity => 1, +                                  lib => 'blib/lib', +                                  color => 1, +                                }); + +my @tests = glob($ARGV[0]); +say "Tests: " . join (", ", @tests); +$harness->runtests(@tests); diff --git a/testing/tests/001-basic.t b/testing/tests/001-basic.t new file mode 100755 index 0000000..60578d8 --- /dev/null +++ b/testing/tests/001-basic.t @@ -0,0 +1,50 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use feature qw/say/; +use Test::Irssi; + +my $tester = Test::Irssi->new +  (irssi_binary  => "/opt/stow/repo/irssi-debug/bin/irssi", +   irssi_homedir => $ENV{HOME} . "/projects/tmp/test/irssi-debug"); + +if (exists $ENV{IRSSI_TEST_HEADLESS} and $ENV{IRSSI_TEST_NOHEADLESS} == 1) { +    $tester->run_headless(0); +    $tester->generate_tap(0); +} else { +    $tester->run_headless(1); +    $tester->generate_tap(1); +} + +my $test = $tester->new_test('test1'); +$test->description("simple echo tests"); + +$test->add_input_sequence("/echo Hello cats\n"); +$test->add_delay(1); +$test->add_input_sequence("/echo Hello Again\n"); +$test->add_input_sequence("this is a long test"); +$test->add_delay(0.5); +$test->add_pattern_match(qr/long/, 'prompt', 'prompt contains long'); +$test->add_delay(1); + +$test->add_pattern_match(qr/this is a .*? test/, 'prompt', 'prompt matches'); + +my $test2 = $tester->new_test('test2'); +$test2->description("cursor movement and deletion"); + +$test2->add_delay(1); +$test2->add_input_sequence("\x01"); +$test2->add_delay(0.1); +$test2->add_input_sequence("\x0b"); +$test2->add_delay(0.1); +$test2->add_input_sequence("/clear\n"); +$test2->add_delay(0.1); +$test2->add_input_sequence("/echo moo\n"); + +my $quit = $tester->new_test('quit'); +$quit->description('quitting'); +$quit->add_input_sequence("/quit\n"); + +$tester->run; diff --git a/testing/tests/002-cursor-test.t b/testing/tests/002-cursor-test.t new file mode 100755 index 0000000..eb35170 --- /dev/null +++ b/testing/tests/002-cursor-test.t @@ -0,0 +1,29 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use feature qw/say/; +use Test::Irssi; + +my $tester = Test::Irssi->new +  (irssi_binary  => "/opt/stow/repo/irssi-debug/bin/irssi", +   irssi_homedir => $ENV{HOME} . "/projects/tmp/test/irssi-debug"); + +if (exists $ENV{IRSSI_TEST_HEADLESS} and $ENV{IRSSI_TEST_NOHEADLESS} == 1) { +    $tester->run_headless(0); +    $tester->generate_tap(0); +} else { +    $tester->run_headless(1); +    $tester->generate_tap(1); +} + +my $test = $tester->new_test('test1'); +$test->description("simple echo tests"); +$test->add_diag("Testing 123"); + +my $quit = $tester->new_test('quit'); +$quit->description('quitting'); +$quit->add_input_sequence("/quit\n"); + +$tester->run; diff --git a/tinyurl-tabcomplete/README.pod b/tinyurl-tabcomplete/README.pod new file mode 100644 index 0000000..f29ace0 --- /dev/null +++ b/tinyurl-tabcomplete/README.pod @@ -0,0 +1,56 @@ +=pod + +=head1 NAME + +complete-tiny-url.pl + +=head1 DESCRIPTION + +Shortens web links from your Irssi input field by pressing tab directly after +them. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD complete-tiny-url.pl>. You may wish to have it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head1 USAGE + +Type or paste a URL into your input field, then immediately following the last +character of it, press the C<E<lt>TABE<gt>> key.  After a few seconds, the +URL will be replaced with an appropriate L<http://tinyurl.com/> address. + +=head1 AUTHORS + +Copyright E<copy> 2010-2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> and + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +None Known. Please report any at +L<https://github.com/shabble/irssi-scripts/issues/new> + + + +=cut + diff --git a/tinyurl-tabcomplete/complete-tiny-url.pl b/tinyurl-tabcomplete/complete-tiny-url.pl index 79d1161..b2d448f 100644 --- a/tinyurl-tabcomplete/complete-tiny-url.pl +++ b/tinyurl-tabcomplete/complete-tiny-url.pl @@ -1,17 +1,72 @@ +=pod + +=head1 NAME + +complete-tiny-url.pl + +=head1 DESCRIPTION + +Shortens web links from your Irssi input field by pressing tab directly after +them. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD complete-tiny-url.pl>. You may wish to have it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head1 USAGE + +Type or paste a URL into your input field, then immediately following the last +character of it, press the C<E<lt>TABE<gt>> key.  After a few seconds, the +URL will be replaced with an appropriate L<http://tinyurl.com/> address. + +=head1 AUTHORS + +Copyright E<copy> 2010-2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> and + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +None Known. Please report any at +L<https://github.com/shabble/irssi-scripts/issues/new> + +=cut + +  use strict; -use vars qw($VERSION %IRSSI); +use warnings;  use Irssi;  use WWW::Shorten::TinyURL; -$VERSION = '1.0'; -%IRSSI = ( +our $VERSION = '1.0'; +our %IRSSI = (      authors     => 'Shabble',      contact     => 'shabble+irssi /at/ metavore /dot/ org',      name        => 'Shorten URLs using Tab',      description => 'Hitting Tab after typing/pasting a long URL will replace it with'                   . ' its tinyURL.com equivalent', -    license     => 'WTFPL', +    license     => 'MIT',  );  sub do_complete { diff --git a/undo/kill-ring.pl b/undo/kill-ring.pl new file mode 100644 index 0000000..c3600a6 --- /dev/null +++ b/undo/kill-ring.pl @@ -0,0 +1,129 @@ +# DOCUMENTATION: +# +#  +# +# +# LICENCE: +# +# Copyright (c) 2011 Tom Feist <shabble+irssi@metavore.org> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +use strict; +use warnings; + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => 'kill-ring', +              description => 'keeps track of changes to the cutbuffer' +                             . ' and allows you to cycle through them', +              license     => 'MIT', +              updated     => '$DATE' +             ); + +my @cut_buf_history; + +sub cut_buffer_init { +    Irssi::signal_add_first('gui key pressed' => \&sig_cmd_undo); +    Irssi::command_bind('cut_buf_cycle' => \&cmd_cut_buf_cycle); +} + +sub add_to_history { +    my ($str) = @_; + +    return unless defined $str; + +    my $head = $cut_buf_history[-1]; + +    if (defined $head and $str ne $head) { +        push @cut_buf_history, $str; +    } elsif (not defined $head) { +        push @cut_buf_history, $str; +    } + +    # enforce maximum size +    # TODO: make this a setting? + +    if (@cut_buf_history > 100) { +        shift @cut_buf_history; +    } +} + +sub cmd_cut_buf_cycle { +    print '%_Cut buffer contains:%_'; +    foreach my $buf (@cut_buf_history) { +        print "$buf" +   } +} + +sub sig_cmd_undo { +    my ($key) = @_; +    add_to_history(_cut_buf()); +} + +sub _cut_buf { +    return Irssi::parse_special('$U', 0, 0); +} + +sub _input { +    my ($data) = @_; + +    my $current_data = Irssi::parse_special('$L', 0, 0); + +    # TODO: set this back up. + +    # if ($settings->{utf8}->{value}) { +    #     $current_data = decode_utf8($current_data); +    # } + +    if (defined $data) { +        # if ($settings->{utf8}->{value}) { +        #     Irssi::gui_input_set(encode_utf8($data)); +        # } else { +        Irssi::gui_input_set($data); +        #} +    } else { +        $data = $current_data; +    } + +    return $data; +} + +sub _input_pos { +    my ($pos) = @_; +    my $cur_pos = Irssi::gui_input_get_pos(); +    if (defined $pos) { +        Irssi::gui_input_set_pos($pos) if $pos != $cur_pos; +    } else { +        $pos = $cur_pos; +    } + +    return $pos; +} + +cut_buffer_init(); diff --git a/undo/undo.pl b/undo/undo.pl new file mode 100644 index 0000000..5daa16e --- /dev/null +++ b/undo/undo.pl @@ -0,0 +1,134 @@ +# LICENCE: +# +# Copyright (c) 2011 Tom Feist <shabble+irssi@metavore.org> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +# TODO: attempt to create an undo function for short-term history in +# the input-bar. + +use strict; +use warnings; + +use Irssi; +use Irssi::Irc; +use Irssi::TextUI; + +use Data::Dumper; + +our $VERSION = '0.1'; +our %IRSSI = ( +              authors     => 'shabble', +              contact     => 'shabble+irssi@metavore.org', +              name        => '', +              description => '', +              license     => 'MIT', +             ); + +my @undo_list; + +sub undo_init { +    Irssi::signal_add_first('gui key pressed' => \&sig_cmd_undo);} +} + +sub add_to_undo { +    my ($str, $pos) = @_; + +    return unless defined $str and defined $pos; + +    my $undo_head = $undo_list[-1]; + +    if (ref $undo_head) { +        if ($str ne $undo_head->{str} && $pos != $undo_head->{pos}) { +            push @undo_list, { str => $str, pos => $pos }; +        } +    } else { +        push @undo_list, { str => $str, pos => $pos }; +    } + +    # enforce maximum size +    # TODO: make this a setting? + +    if (@undo_list > 100) { +        shift @undo_list; +    } +} + +sub sig_cmd_undo { +    my ($key) = @_; + +    if ($key == 10) { +        @undo_list = (); +        return; +    } elsif ($key != 28) { +        add_to_undo(_input(), _input_pos()); +        return; +    } + +    Irssi::signal_stop(); + +    my $prev_state = pop @undo_list; + +    if (not defined $prev_state) { +        print "No further undo"; +        return; +    } + +    _input($prev_state->{str}); +    _input_pos($prev_state->{pos}); +} + +sub _input { +    my ($data) = @_; + +    my $current_data = Irssi::parse_special('$L', 0, 0); + +    # TODO: set this back up. + +    # if ($settings->{utf8}->{value}) { +    #     $current_data = decode_utf8($current_data); +    # } + +    if (defined $data) { +        # if ($settings->{utf8}->{value}) { +        #     Irssi::gui_input_set(encode_utf8($data)); +        # } else { +        Irssi::gui_input_set($data); +        #} +    } else { +        $data = $current_data; +    } + +    return $data; +} + +sub _input_pos { +    my ($pos) = @_; +    my $cur_pos = Irssi::gui_input_get_pos(); +    if (defined $pos) { +        Irssi::gui_input_set_pos($pos) if $pos != $cur_pos; +    } else { +        $pos = $cur_pos; +    } + +    return $pos; +} + +undo_init(); diff --git a/vim-mode/README.pod b/vim-mode/README.pod new file mode 100644 index 0000000..83962ff --- /dev/null +++ b/vim-mode/README.pod @@ -0,0 +1,584 @@ +=pod + +=head1 NAME + +vim_mode.pl + +=head1 DESCRIPTION + +An Irssi script to emulate some of the vi(m) features for the Irssi inputline. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD vim_mode.pl>. You may wish to have it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head2 DEPENDENCIES + +For proper :ex mode support, vim-mode requires the installation of 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 for installation. + +If you don't need Ex-mode, you can run vim_mode.pl without the +uberprompt.pl script, but it is strongly recommended that you use it. + +=head3 Irssi requirements + +0.8.12 and above should work fine. However the following features are +disabled in irssi < 0.8.13: + +=over 4 + +=item * C<j> C<k> (only with count, they work fine without count in older versions) + +=item * C<gg>, C<G> + +=back + +It is intended to work with at Irssi  0.8.12 and later versions. However some +features are disabled in older versions (see below for details). + +Perl >= 5.8.1 is recommended for UTF-8 support (which can be disabled if +necessary).  Please report bugs in older versions as well, we'll try to fix +them.  Later versions of Perl are also faster, which is probably beneficial +to a script of this size and complexity. + +=head2 SETUP + +Vim Mode provides certain feedback to the user, such as the currently active +mode (command, insert, ex), and if switching buffers, which buffer(s) currently +match the search terms. + +There are two ways to go about displaying this information, as described in +the following sections: + +=head3 Statusbar Items + +Run the following command to add a statusbar item that shows which mode +you're in. + +C</statusbar window add vim_mode> + +And the following to let C<:b [str]> display a list of channels matching your +search string. + +C</statusbar window add vim_windows> + +B<Note:> Remember to C</save> after adding these statusbar items to make them +permanent. + +B<Note:> If you would rather have these statusbar items on your prompt +line rather than thte window statusbar, please follow these steps. + +For technical reasons, I<uberprompt> must occasionally call C</statusbar prompt +reset>, which will remove or deactivate any manually added items on the prompt +statusbar.  To get around this, uberprompt provides two command hooks, +C<uberprompt_load_hook> and C<uberprompt_unload_hook>.  Both of these settings +can contain one (or more, using C</EVAL>) commands to be executed when the prompt +is enabled and disabled, respectively. + +See the L<uberprompt documentation|https://github.com/shabble/irssi-scripts/blob/master/prompt_info/README.pod> for further details. + +For I<right-aligned> items (that is, after the input field: + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -after input -alignment right vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +For I<left-aligned> items (before the prompt): + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -before prompt -alignment left vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +If you wish to add both C<vim_mode> and C<vim_windows> items, replace steps 1 and 2 +above with the following (right-aligned): + +=over 4 + +=item 1 C</alias vm_add /^eval /^statusbar prompt add -after input -alightment right vim_mode ; /^statusbar prompt add -after input -alignment right vim_windows> + +=item 2 C</alias vm_del /^eval /^statusbar prompt remove vim_mode ; /^statusbar prompt remove vim_windows> + +=back + +and then complete stages 3 and 4 as above.  Replace the C<-after> and C<-alignment> +to suit your location preferences. + +B<Note:> It is also possible to place the items between the prompt and input field, +by specifying C<-after prompt> or C<-before input> as appropriate. + +=head3 Expando Variables + +Vim mode augments the existing Irssi expando (automatic variables) with two +additional ones: C<$vim_cmd_mode> and C<$vim_wins>. + +C<$vim_cmd_mode> is the equivalent of the C<vim_mode> statusbar item, and +C<$vim_wins> is the counterpart of C<vim_windows>. + +They can be added to your theme, or inserted into your uberprompt using +a command such as: + +"C</set uberprompt_format [$vim_cmd_mode] $*$uber] >" + +=head3 FILE-BASED CONFIGURATION + +Additionally to the irssi settings described in L<settings|/SETTINGS>, vim_mode +can be configured through an external configuration file named "vim_moderc" +located in F<~/.irssi/vim_moderc>. If available it's loaded on startup and every +supported ex-command is run. Its syntax is similar to "vimrc". To (re)load it +while vim_mode is running use C<:so[urce]>. + +Currently Supported ex-commands: + +=over 4 + +=item * C<:map> + +=back + +=head1 USAGE + +The following section is divided into the different modes as supported by Vim itself: + +=head2 COMMAND MODE + +It supports most commonly used command mode features: + +=over 2 + +=item * Insert/Command mode. + +C<Esc> and C<Ctrl-C> enter command mode.  C</set vim_mode_cmd_seq j> allows +to use C<jj> as Escape (any other character can be used as well). + +=item * Cursor motion: + +C<h l 0 ^ $ E<lt>SpaceE<gt> E<lt>BSE<gt> f t F T> + +=item * History motion: + +C<j k gg G> C<gg> moves to the oldest (first) history line.  C<G> without a +count moves to the current input line, with a count it goes to the I<count-th> +history line (1 is the oldest). + +=item * Cursor word motion: + +C<w b ge e W gE B E> + +=item * Word objects (only the following work yet): + +C<aw aW> + +=item * Yank and paste: + + C<y p P> + +=item * Change and delete: + +C<c d> + +=item * Delete at cursor: + +C<x X> + +=item * Replace at cursor: + +C<r> + +=item * Insert mode: + +C<i a I A> + +=item * Switch case: + +C<~> + +=item * Repeat change: + +C<.> + +=item * Repeat + +C<ftFT: ; ,> + +=item * Registers: + +C<"a-"z "" "0 "* "+ "_> (black hole) + +=item * Line-wise shortcuts: + +C<dd cc yy> + +=item * Shortcuts: + +C<s S C D> + +=item * Scroll the scrollback buffer: + +C<Ctrl-E Ctrl-D Ctrl-Y Ctrl-U Ctrl-F Ctrl-B> + +=item * Switch to last active window: + +C<Ctrl-6/Ctrl-^> + +=item * Switch split windows: + +<Ctrl-W j Ctrl-W k> + +=item * Undo/Redo: + +C<u Ctrl-R> + +=back + +Counts and combinations work as well, e.g. C<d5fx> or C<3iabcE<lt>escE<gt>>. Counts also work with mapped ex-commands (see below), e.g. if you map C<gb> to do C<:bn>, then C<2gb> will switch to the second next buffer.  Repeat also supports counts. + +=head3 REGISTERS + +=over 4 + +=item * Appending to register with C<"A-"Z> + +=item * C<""> is the default yank/delete register. + +=item * C<"0> contains the last yank (if no register was specified). + +=item * The special registers C<"* "+> both contain irssi's internal cut-buffer. + +=back + +=head2 INSERT MODE + +The following insert mode mappings are supported: + +=over 4 + +=item * Insert register content: Ctrl-R x (where x is the register to insert) + +=back + +=head2 EX-MODE + +Ex-mode (activated by C<:> in command mode) supports the following commands: + +=over 4 + +=item * Command History: + +C<E<lt>uparrowE<gt>> - cycle backwards through history + +C<E<lt>downarrowE<gt>> - cycle forwards through history + +C<:eh> - show ex history + +=item * Switching buffers: + +C<:[N]b [N]> - switch to channel number + +C<:b#>       - switch to last channel + +C<:b> E<lt>partial-channel-nameE<gt> + +C<:b> <partial-server>/<partial-channel> + +C<:buffer {args}> (same as C<:b>) + +C<:[N]bn[ext] [N]> - switch to next window + +C<:[N]bp[rev] [N]> - switch to previous window + +=item * Close window: + +C<:[N]bd[elete] [N]> + +=item * Display windows: + +C<:ls>, C<:buffers> + +=item * Display registers: + +C<:reg[isters] {args}>, C<:di[splay] {args}> + +=item * Display undolist: + +C<:undol[ist]> (mostly used for debugging) + +=item * Source files: + +C<:so[urce]> - only sources vim_moderc at the moment, +                         F<{file}> not supported + +=item * Mappings: + +C<:map> - display custom mappings + +=item * Saving mappings: + +C<:mkv[imrc][!]> - like in Vim, but [file] not supported + +=item * Substitution: + +C<:s///> - I<i> and I<g> are supported as flags, only C<///> can be used as +eparator, and it uses Perl regex syntax instead of Vim syntax. + +=item * Settings: + +C<:se[t]> - display all options + +C<:se[t] {option}>         - display all matching options + +C<:se[t] {option} {value}> - change option to value + +=back + +=head3 MAPPINGS + +=head4 Commands + +=over 4 + +=item * C<:map {lhs}>       - display mappings starting with {lhs} + +=item * C<:map {lhs} {rhs}> - add mapping + +=item * C<:unm[ap] {lhs}>   - remove custom mapping + +=back + +=head4 Parameters + +I<{lhs}> is the key combination to be mapped, I<{rhs}> the target. The +C<E<lt>E<gt>> notation is used + +(e.g. C<E<lt>C-FE<gt>> is I<Ctrl-F>), case is ignored. + Supported C<E<lt>E<gt>> keys are: + +=over 4 + +=item * C<E<lt>C-AE<gt>> - C<E<lt>C-ZE<gt>>, + +=item * C<E<lt>C-^E<gt>>, C<E<lt>C-6E<gt>> + +=item * C<E<lt>SpaceE<gt>> + +=item * C<E<lt>CRE<gt>> - Enter + +=item * C<E<lt>BSE<gt>> - Backspace + +=item * C<E<lt>NopE<gt>> - No-op (Do Nothing). + +=back + +Mapping ex-mode and irssi commands is supported. When mapping ex-mode commands +the trailing C<E<lt>CrE<gt>> is not necessary. Only default mappings can be used +in I<{rhs}>. + +Examples: + +=over 4 + +=item * C<:map w  W>      - to remap w to work like W + +=item * C<:map gb :bnext> - to map gb to call :bnext + +=item * C<:map gB :bprev> + +=item * C<:map g1 :b 1>   - to map g1 to switch to buffer 1 + +=item * C<:map gb :b>     - to map gb to :b, 1gb switches to buffer 1, 5gb to 5 + +=item * C<:map E<lt>C-LE<gt> /clear> - map Ctrl-L to irssi command /clear + +=item * C<:map E<lt>C-GE<gt> /window goto 1> + +=item * C<:map E<lt>C-EE<gt> <Nop>> - disable E<lt>C-EE<gt>, it does nothing now + +=item * C<:unmap E<lt>C-EE<gt>> - restore default behavior of C<E<lt>C-EE<gt>> +after disabling it + +=back + +Note that you must use C</> for irssi commands (like C</clear>), the current value +of Irssi's cmdchars does B<not> work! This is necessary do differentiate between +ex-commands and irssi commands. + +=head2 SETTINGS + +The settings are stored as irssi settings and can be set using C</set> as usual +(prepend C<vim_mode_> to setting name) or using the C<:set> ex-command. The +following settings are available: + +=over 4 + +=item * utf8 - Support UTF-8 characters, boolean, default on + +=item * debug - Enable debug output, boolean, default off + +=item * cmd_seq - Char that when double-pressed simulates C<E<lt>EscE<gt>>, string, default '' (disabled) + +=item * start_cmd - Start every line in command mode, boolean, default off + +=item * max_undo_lines - Sze of the undo buffer. Integer, default 50 items. + +=item * ex_history_size - Number of items stored in the ex-mode history. Integer, default 100. + +=item * prompt_leading_space - Ddetermines whether ex mode prepends a space to the displayed input. Boolean, default on + +=back + +In contrast to irssi's settings, C<:set> accepts 0 and 1 as values for boolean +settings, but only vim_mode's settings can be set/displayed. + +Examples: + +   :set cmd_seq=j   # set cmd_seq to j +   :set cmd_seq=    # disable cmd_seq +   :set debug=on    # enable debug +   :set debug=off   # disable debug + +=head1 SUPPORT + +Any behavior different from Vim (unless explicitly documented) should be +considered a bug and reported. + +B<NOTE:> This script is still under heavy development, and there may be bugs. +Please submit reproducible sequences to the bug-tracker at: +L<http://github.com/shabble/irssi-scripts/issues/new> + +or contact rudi_s or shabble on irc.freenode.net (#irssi and #irssi_vim) + +=head1 AUTHORS + +Copyright E<copy> 2010-2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> and + +Copyright E<copy> 2010-2011 Simon Ruderich C<E<lt>simon@ruderich.orgE<gt>> + +=head1 THANKS + +Particular thanks go to + +=over 4 + +=item * estragib: a lot of testing and many bug reports and feature requests + +=item * iaj: testing + +=item * tmr: explaining how various bits of vim work + +=back + +as well as the rest of C<#irssi> and C<#irssi_vim> on Freenode IRC. + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=over 4 + +=item * + +count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does + +=item * + +mapping an incomplete ex-command doesn't open the ex-mode with the partial +command (e.g. C<:map gb :b> causes an error instead of opening the ex-mode and +displaying C<:bE<lt>cursorE<gt>>) + +=item * + + undo/redo cursor positions are mostly wrong + +=back + +=head1 TODO + +=over 4 + +=item * + +History: + +=over 4 + +=item * + + C< * /,?,n,N> to search through history (like rl_history_search.pl) + +=back + +=item * + +Window switching (C<:b>) + +=over 4 + +=item * + +Tab completion of window(-item) names + +=item * + +non-sequential matches(?) + +=back + +=back + +See also the TODO file at +L<github|https://github.com/shabble/irssi-scripts/blob/master/vim-mode/TODO> for +many many more things. + +=head2 WONTFIX + +Things we're not ever likely to do: + +=over 4 + +=item * Macros + +=back + + + +=cut + diff --git a/vim-mode/input.txt b/vim-mode/input.txt new file mode 100644 index 0000000..790060a --- /dev/null +++ b/vim-mode/input.txt @@ -0,0 +1,58 @@ +How the input system for vim_mode should work. +============================================= + +* insert mode mappings +* normal (command)  mode mappings +* timeout for ambiguous mappings +**  but support for no timeout for some mappings (like Ctrl-R in insert mode) +* maybe 'timeout' and 'ttimeout' settings (and 'timeoutlen' and 'ttimeoutlen') +* partial commands/mappings (like :map a :b a which inserts ex mode and +* displays :b a there without running any command) +* support for mappings entering insert mode: :map gX itexthere<esc> (also without esc!) +** (.) (repeat) for all (most) commands +* more general command order: 3"ap doesn't work but "a3p does +* arbitrary mappings, like 3dwjjihi<esc>10G (at the moment even :map Y y$ doesn't work) +* support to disable mappings temporarily (:set paste, 'pastetoggle') +* (better) support multiple bindings for the same action (especially <BS> and <C-H>) +* unmapping of internal mappings (for example <C-C> to <Nop> as it's also used for colors in irssi) +* better approach to detecting single ESC push (compared to F-keys or meta-* keys) + +We base the input model on a simple state machine. +It can exist in any of 4 (5) states: + - insert mode +   - insert escape mode +     a transitional mode to distingush that we've received an escape char. + - command mode + - ex mode + - visual mode (can be mostly ignored for now, until I sort my overlays stuff +   out) + +to swap between these modes, there are various bindings available. +The most challenging is going from insert -> command.  Typically this is done +by a single push of the <esc> key, but C-c and <jj> are both alternatives that +people use. + +The single entrypoint we have into the input system is detecting keystrokes. +when a 'gui key pressed' signal is fired, we can receive, process and interrupt +it such that it never gets passed to irssi proper. + +in order to detect single <esc> presses from their meta comrades, we operate +as the following: + +if (key == 27) { +    mode = insert_escape +    add_timeout(short, check_escape_buffer) +} elsif (mode == insert_escape) { +    insert(key, escape_buffer) +} else { +   # do nothing +} + +sub check_escape_buffer { +# when the timeout fires, we can assume that only meta-keys would be fast enough +# to insert additional characters into the buffer.  In which case, we replay +# them and clear the buffer. + mode = insert_mode; +# if the buffer is otherwise empty, we have received a single escape press. + mode = command_mode; +} diff --git a/vim-mode/irssi/config b/vim-mode/irssi/config new file mode 100644 index 0000000..d0b0805 --- /dev/null +++ b/vim-mode/irssi/config @@ -0,0 +1,38 @@ +# Minimal default irssi configuration file - with vim-mode statusbars. + + +servers = ( ); + +chatnets = { }; + +channels = ( ); + +aliases = { }; + +statusbar = { +  default = { +    window = { +      items = { +        barstart = { priority = "100"; }; +        time = { }; +        user = { }; +        window = { }; +        window_empty = { }; +        lag = { priority = "-1"; }; +        act = { priority = "10"; }; +        more = { priority = "-1"; alignment = "right"; }; +        barend = { priority = "100"; alignment = "right"; }; +        vim_mode = { }; +        vim_windows = { }; +      }; +    }; + +    prompt = { +      items = { +        uberprompt = { priority = "-1"; }; +        input = { priority = "10"; }; +      }; +      position = "100"; +    }; +  }; +}; diff --git a/vim-mode/irssi/scripts/autorun/uberprompt.pl b/vim-mode/irssi/scripts/autorun/uberprompt.pl new file mode 120000 index 0000000..3d4d97a --- /dev/null +++ b/vim-mode/irssi/scripts/autorun/uberprompt.pl @@ -0,0 +1 @@ +../../../../prompt_info/uberprompt.pl
\ No newline at end of file diff --git a/vim-mode/irssi/scripts/autorun/vim_mode.pl b/vim-mode/irssi/scripts/autorun/vim_mode.pl new file mode 120000 index 0000000..ed78131 --- /dev/null +++ b/vim-mode/irssi/scripts/autorun/vim_mode.pl @@ -0,0 +1 @@ +../../../../vim-mode/vim_mode.pl
\ No newline at end of file diff --git a/vim-mode/tests.pl b/vim-mode/tests.pl new file mode 100644 index 0000000..9007171 --- /dev/null +++ b/vim-mode/tests.pl @@ -0,0 +1,171 @@ +#!/usr/bin/env perl + +# Must be run in a 80x24 terminal unless a fixed POE is released. + + +use strict; +use warnings; + +use lib '../testing/blib/lib'; + +use Test::Irssi; + +# Mode constants: C(ommand), I(nsert). +sub C () { 0 } +sub I () { 1 } + + +sub statusbar_mode { +    my ($test, $mode) = @_; + +    $test->add_pattern_match(qr/^ \[\d{2}:\d{2}\] \[\] \[1\] \[$mode\]\s+$/, +        'window_sbar', "[$mode] in vim-mode statusbar"); +} +sub cursor_position { +    my ($test, $position) = @_; + +    $test->test_cursor_position($position, 24, "Checking cursor position"); +} + +sub check { +    my ($test, $input, $mode, $position, $delay) = @_; + +    if (defined $input) { +        $test->add_input_sequence($input); +        if (not defined $delay) { +            $delay = 0.1; +            $delay += 0.4 if $input =~ /\e/; # esc needs a longer delay +        } +        $test->add_delay($delay); +    } + +    cursor_position($test, $position); +    if ($mode == C) { +        statusbar_mode($test, 'Command'); +    } elsif ($mode == I) { +        statusbar_mode($test, 'Insert'); +    } else { +        die "Wrong mode: $mode"; +    } +} + +my $tester = Test::Irssi->new +  (irssi_binary  => "irssi", +   irssi_homedir => "./irssi/"); + + +$tester->run_headless(1); +$tester->generate_tap(1); + +my $test = $tester->new_test('insert-command-mode'); +$test->description("switching between insert and command mode"); +# Make sure irssi is finished - not entirely sure why this is necessary. +$test->add_delay(2); + +# We start in insert mode. +check $test, undef, I, 12 + 0; +check $test, "\e",  C, 12 + 0; +check $test, 'i',   I, 12 + 0; + +# Quit irssi, necessary to terminate the test. +#$test->add_input_sequence("\n/quit\n"); + + +# FIXME: multiple tests don't work +#$test = $tester->new_test('basic-movement'); +#$test->description('basic movement'); +#$test->add_delay(2); + +my $test_string = +    'Test $tring. with a 4711, words , w.r#s42  etc.   and more123! ..'; + +check $test, $test_string, I, 12 + length $test_string; +check $test, "\e",         C, 12 + 64; + +# h l +check $test, "0",    C, 12 + 0; +check $test, "l",    C, 12 + 1; +check $test, "l",    C, 12 + 2; +check $test, "l",    C, 12 + 3; +check $test, "l",    C, 12 + 4; +check $test, "l",    C, 12 + 5; +check $test, "l",    C, 12 + 6; +check $test, "l",    C, 12 + 7; +check $test, "h",    C, 12 + 6; +check $test, "h",    C, 12 + 5; +check $test, "h",    C, 12 + 4; +check $test, "h",    C, 12 + 3; +check $test, "h",    C, 12 + 2; +check $test, "10l",  C, 12 + 12; +check $test, "7l",   C, 12 + 19; +check $test, "3l",   C, 12 + 22; +check $test, "3h",   C, 12 + 19; +check $test, "50l",  C, 12 + 64; +check $test, "10l",  C, 12 + 64; +check $test, "24h",  C, 12 + 40; +check $test, "100h", C, 12 + 0; + +# 0 ^ $ +check $test, "I     \e", C, 12 + 4; # insert test string for ^ +check $test, "0",        C, 12 + 0; +check $test, "^",        C, 12 + 5; +check $test, "3^",       C, 12 + 5; +check $test, "12^",      C, 12 + 5; +check $test, "\$",       C, 12 + 46; +check $test, "05x",      C, 12 + 0; # remove test string for ^ + +# <Space> <BS> +check $test, "0",      C, 12 + 0; +check $test, " ",      C, 12 + 1; +check $test, " ",      C, 12 + 2; +check $test, " ",      C, 12 + 3; +check $test, " ",      C, 12 + 4; +check $test, " ",      C, 12 + 5; +check $test, " ",      C, 12 + 6; +check $test, " ",      C, 12 + 7; +check $test, " ",      C, 12 + 8; +check $test, "5 ",     C, 12 + 13; +check $test, "2 ",     C, 12 + 15; +check $test, "10 ",    C, 12 + 25; +check $test, "30 ",    C, 12 + 55; +check $test, "20 ",    C, 12 + 64; +check $test, "10 ",    C, 12 + 64; +check $test, " ",      C, 12 + 64; +check $test, "\x7f",   C, 12 + 63; +check $test, "\x7f",   C, 12 + 62; +check $test, "\x7f",   C, 12 + 61; +check $test, "\x7f",   C, 12 + 60; +check $test, "1\x7f",  C, 12 + 59; +check $test, "3\x7f",  C, 12 + 56; +check $test, "5\x7f",  C, 12 + 51; +check $test, "10\x7f", C, 12 + 41; +check $test, "50\x7f", C, 12 + 0; +check $test, "\x7f",   C, 12 + 0; +check $test, "5\x7f",  C, 12 + 0; + +# f t +check $test, "0",   C, 12 + 0; +check $test, "fe",  C, 12 + 1; +check $test, "fs",  C, 12 + 2; +check $test, "ft",  C, 12 + 3; +check $test, "f ",  C, 12 + 4; +check $test, "2f ", C, 12 + 17; +check $test, "5f,", C, 12 + 17; +check $test, "2f,", C, 12 + 32; +check $test, "tw",  C, 12 + 33; +check $test, "t ",  C, 12 + 40; +check $test, "t ",  C, 12 + 40; +check $test, "t ",  C, 12 + 40; +check $test, "2t ", C, 12 + 41; +check $test, "2t ", C, 12 + 46; +check $test, "3t ", C, 12 + 48; +check $test, "t!",  C, 12 + 60; +check $test, "t ",  C, 12 + 61; +check $test, "t.",  C, 12 + 62; +check $test, "t.",  C, 12 + 62; +check $test, "5t.", C, 12 + 62; +check $test, "\$",  C, 12 + 64; + +$test->add_input_sequence("\n/quit\n"); + +$tester->run; diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl index 7d898ab..b0f739f 100644 --- a/vim-mode/vim_mode.pl +++ b/vim-mode/vim_mode.pl @@ -1,238 +1,584 @@ -# A script to emulate some of the vi(m) features for the Irssi inputline. -# -# It should work fine with at least 0.8.12 and later versions. However some -# features are disabled in older versions (see below for details). Perl >= -# 5.8.1 is recommended for UTF-8 support (which can be disabled if necessary). -# Please report bugs in older versions as well, we'll try to fix them. -# -# Any behavior different from Vim (unless explicitly documented) should be -# considered a bug and reported. -# -# NOTE: This script is still under heavy development, and there may be bugs. -# Please submit reproducible sequences to the bug-tracker at: -# http://github.com/shabble/irssi-scripts/issues -# -# or contact rudi_s or shabble on irc.freenode.net (#irssi and #irssi_vim) -# -# -# Features: -# -# It supports most commonly used command mode features: -# -# * Insert/Command mode. Escape and Ctrl-C enter command mode. -#   /set vim_mode_cmd_seq j allows to use jj as Escape (any other character -#   can be used as well). -# * Cursor motion: h l 0 ^ $ <Space> <BS> f t F T -# * History motion: j k gg G -#   gg moves to the oldest (first) history line. -#   G without a count moves to the current input line, with a count it goes to -#   the count-th history line (1 is the oldest). -# * Cursor word motion: w b ge e W gE B E -# * Word objects (only the following work yet): aw aW -# * Yank and paste: y p P -# * Change and delete: c d -# * Delete at cursor: x X -# * Replace at cursor: r -# * Insert mode: i a I A -# * Switch case: ~ -# * Repeat change: . -# * Repeat ftFT: ; , -# * Registers: "a-"z "" "0 "* "+ "_ (black hole) -#   Appending to register with "A-"Z -#   "" is the default yank/delete register. -#   "0 contains the last yank (if no register was specified). -#   The special registers "* "+ contain both irssi's cut-buffer. -# * Line-wise shortcuts: dd cc yy -# * Shortcuts: s S C D -# * Scroll the scrollback buffer: Ctrl-E Ctrl-D Ctrl-Y Ctrl-U Ctrl-F Ctrl-B -# * Switch to last active window: Ctrl-6/Ctrl-^ -# * Switch split windows: Ctrl-W j Ctrl-W k -# * Undo/Redo: u Ctrl-R -# -# Counts and combinations work as well, e.g. d5fx or 3iabc<esc>. Counts also -# work with mapped ex-commands (see below), e.g. if you map gb to do :bn, then -# 2gb will switch to the second next buffer. -# Repeat also supports counts. -# -# The following insert mode mappings are supported: -# -# * Insert register content: Ctrl-R x (where x is the register to insert) -# -# Ex-mode supports (activated by : in command mode) the following commands: -# -# * Switching buffers: :[N]b [N] - switch to channel number -#                      :b#       - switch to last channel -#                      :b <partial-channel-name> -#                      :b <partial-server>/<partial-channel> -#                      :buffer {args} (same as :b) -#                      :[N]bn[ext] [N] - switch to next window -#                      :[N]bp[rev] [N] - switch to previous window -# * Close window:      :[N]bd[elete] [N] -# * Display windows:   :ls :buffers -# * Display registers: :reg[isters] {args} :di[splay] {args} -# * Display undolist:  :undol[ist] (mostly used for debugging) -# * Source files       :so[urce] - only sources vim_moderc at the moment, -#                                  {file} not supported -# * Mappings:          :map             - display custom mappings -#                      :map {lhs}       - display mappings starting with {lhs} -#                      :map {lhs} {rhs} - add mapping -#                      :unm[ap] {lhs}   - remove custom mapping -# * Save mappings:     :mkv[imrc][!] - like in Vim, but [file] not supported -# * Substitute:        :s/// - i and g are supported as flags, only /// can be -#                              used as separator, uses Perl regex instead of -#                              Vim regex -# * Settings:          :se[t]                  - display all options -#                      :se[t] {option}         - display all matching options -#                      :se[t] {option} {value} - change option to value -# -# -# Mappings: -# -# {lhs} is the key combination to be mapped, {rhs} the target. The <> notation -# is used (e.g. <C-F> is Ctrl-F), case is ignored. Supported <> keys: -# <C-A>-<C-Z>, <C-^>, <C-6>, <Space>, <CR>, <BS>, <Nop>. Mapping ex-mode and -# irssi commands is supported. When mapping ex-mode commands the trailing <Cr> -# is not necessary. Only default mappings can be used in {rhs}. -# Examples: -#     :map w  W      - to remap w to work like W -#     :map gb :bnext - to map gb to call :bnext -#     :map gB :bprev -#     :map g1 :b 1   - to map g1 to switch to buffer 1 -#     :map gb :b     - to map gb to :b, 1gb switches to buffer 1, 5gb to 5 -#     :map <C-L> /clear - map Ctrl-L to irssi command /clear -#     :map <C-G> /window goto 1 -#     :map <C-E> <Nop> - disable <C-E>, it does nothing now -#     :unmap <C-E>     - restore default behavior of <C-E> after disabling it -# -# Note that you must use / for irssi commands (like /clear), the current value -# of cmdchars does _not_ work! This is necessary do differentiate between -# ex-commands and irssi commands. -# -# -# Settings: -# -# The settings are stored as irssi settings and can be set using /set as usual -# (prepend vim_mode_ to setting name) or using the :set ex-command. The -# following settings are available: -# -# * utf8: support UTF-8 characters, boolean, default on -# * debug: enable debug output, boolean, default off -# * cmd_seq: char that when double-pressed simulates <esc>, string, default '' -# * start_cmd: start every line in command mode, boolean, default off -# -# In contrast to irssi's settings, :set accepts 0 and 1 as values for boolean -# settings, but only vim_mode's settings can be set/displayed. -# Examples: -#    :set cmd_seq=j   # set cmd_seq to j -#    :set cmd_seq=    # disable cmd_seq -#    :set debug=on    # enable debug -#    :set debug=off   # disable debug -# -# -# The following statusbar items are available: -# -# * vim_mode: displays current mode -# * vim_windows: displays windows selected with :b -# -# -# Configuration -# -# Additionally to the irssi settings described above vim_mode can be -# configured through an external configuration file named "vim_moderc" located -# in ~/.irssi/vim_moderc. If available it's loaded on startup and every -# supported ex-command is run. It's syntax is similar to "vimrc". To (re)load -# it while vim_mode is running use :so[urce]. -# -# Supported ex-commands: -# -# * :map -# -# -# Installation: -# -# As always copy the script into .irssi/scripts and load it with -#     /script load # vim_mode.pl -# -# Use the following command to get a statusbar item that shows which mode -# you're in. -# -#     /statusbar window add vim_mode -# -# And the following to let :b name display a list of matching channels -# -#     /statusbar window add vim_windows -# -# -# Dependencies: -# -# For proper :ex mode support, requires the installation of 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. -# -# If you don't need Ex-mode, you can run vim_mode.pl without the -# uberprompt.pl script, but it is recommended. -# -# -# Irssi requirements: -# -# 0.8.12 and above should work fine. However the following features are -# disabled in irssi < 0.8.13: -# -# * j k (only with count, they work fine without count in older versions) -# * gg G -# -# -# Known bugs: -# -# * count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does -# * mapping an incomplete ex-command doesn't open the ex-mode with the partial -#   command (e.g. :map gb :b causes an error instead of opening the ex-mode -#   and displaying :b<cursor>) -# * undo/redo positions are mostly wrong -# -# -# TODO: -# * History: -#   * /,?,n,N to search through history (like history_search.pl) -# * Window switching (:b) -#  * Tab completion of window(-item) names -#  * non-sequential matches(?) -# -# WONTFIX - things we're not ever likely to do -# * Macros -# -# THANKS: -# -# * estragib: a lot of testing and many bug reports and feature requests -# * iaj: testing -# -# LICENCE: -# -# Copyright (c) 2010 Tom Feist & Simon Ruderich -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# -# Have fun! +=pod + +=head1 NAME + +vim_mode.pl + +=head1 DESCRIPTION + +An Irssi script to emulate some of the vi(m) features for the Irssi inputline. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD vim_mode.pl>. You may wish to have it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head2 DEPENDENCIES + +For proper :ex mode support, vim-mode requires the installation of 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 for installation. + +If you don't need Ex-mode, you can run vim_mode.pl without the +uberprompt.pl script, but it is strongly recommended that you use it. + +=head3 Irssi requirements + +0.8.12 and above should work fine. However the following features are +disabled in irssi < 0.8.13: + +=over 4 + +=item * C<j> C<k> (only with count, they work fine without count in older versions) + +=item * C<gg>, C<G> + +=back + +It is intended to work with at Irssi  0.8.12 and later versions. However some +features are disabled in older versions (see below for details). + +Perl >= 5.8.1 is recommended for UTF-8 support (which can be disabled if +necessary).  Please report bugs in older versions as well, we'll try to fix +them.  Later versions of Perl are also faster, which is probably beneficial +to a script of this size and complexity. + +=head2 SETUP + +Vim Mode provides certain feedback to the user, such as the currently active +mode (command, insert, ex), and if switching buffers, which buffer(s) currently +match the search terms. + +There are two ways to go about displaying this information, as described in +the following sections: + +=head3 Statusbar Items + +Run the following command to add a statusbar item that shows which mode +you're in. + +C</statusbar window add vim_mode> + +And the following to let C<:b [str]> display a list of channels matching your +search string. + +C</statusbar window add vim_windows> + +B<Note:> Remember to C</save> after adding these statusbar items to make them +permanent. + +B<Note:> If you would rather have these statusbar items on your prompt +line rather than thte window statusbar, please follow these steps. + +For technical reasons, I<uberprompt> must occasionally call C</statusbar prompt +reset>, which will remove or deactivate any manually added items on the prompt +statusbar.  To get around this, uberprompt provides two command hooks, +C<uberprompt_load_hook> and C<uberprompt_unload_hook>.  Both of these settings +can contain one (or more, using C</EVAL>) commands to be executed when the prompt +is enabled and disabled, respectively. + +See the L<uberprompt documentation|https://github.com/shabble/irssi-scripts/blob/master/prompt_info/README.pod> for further details. + +For I<right-aligned> items (that is, after the input field: + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -after input -alignment right vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +For I<left-aligned> items (before the prompt): + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -before prompt -alignment left vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +If you wish to add both C<vim_mode> and C<vim_windows> items, replace steps 1 and 2 +above with the following (right-aligned): + +=over 4 + +=item 1 C</alias vm_add /^eval /^statusbar prompt add -after input -alightment right vim_mode ; /^statusbar prompt add -after input -alignment right vim_windows> + +=item 2 C</alias vm_del /^eval /^statusbar prompt remove vim_mode ; /^statusbar prompt remove vim_windows> + +=back + +and then complete stages 3 and 4 as above.  Replace the C<-after> and C<-alignment> +to suit your location preferences. + +B<Note:> It is also possible to place the items between the prompt and input field, +by specifying C<-after prompt> or C<-before input> as appropriate. + +=head3 Expando Variables + +Vim mode augments the existing Irssi expando (automatic variables) with two +additional ones: C<$vim_cmd_mode> and C<$vim_wins>. + +C<$vim_cmd_mode> is the equivalent of the C<vim_mode> statusbar item, and +C<$vim_wins> is the counterpart of C<vim_windows>. + +They can be added to your theme, or inserted into your uberprompt using +a command such as: + +"C</set uberprompt_format [$vim_cmd_mode] $*$uber] >" + +=head3 FILE-BASED CONFIGURATION + +Additionally to the irssi settings described in L<settings|/SETTINGS>, vim_mode +can be configured through an external configuration file named "vim_moderc" +located in F<~/.irssi/vim_moderc>. If available it's loaded on startup and every +supported ex-command is run. Its syntax is similar to "vimrc". To (re)load it +while vim_mode is running use C<:so[urce]>. + +Currently Supported ex-commands: + +=over 4 + +=item * C<:map> + +=back + +=head1 USAGE + +The following section is divided into the different modes as supported by Vim itself: + +=head2 COMMAND MODE + +It supports most commonly used command mode features: + +=over 2 + +=item * Insert/Command mode. + +C<Esc> and C<Ctrl-C> enter command mode.  C</set vim_mode_cmd_seq j> allows +to use C<jj> as Escape (any other character can be used as well). + +=item * Cursor motion: + +C<h l 0 ^ $ E<lt>SpaceE<gt> E<lt>BSE<gt> f t F T> + +=item * History motion: + +C<j k gg G> C<gg> moves to the oldest (first) history line.  C<G> without a +count moves to the current input line, with a count it goes to the I<count-th> +history line (1 is the oldest). + +=item * Cursor word motion: + +C<w b ge e W gE B E> + +=item * Word objects (only the following work yet): + +C<aw aW> + +=item * Yank and paste: + + C<y p P> + +=item * Change and delete: + +C<c d> + +=item * Delete at cursor: + +C<x X> + +=item * Replace at cursor: + +C<r> + +=item * Insert mode: + +C<i a I A> + +=item * Switch case: + +C<~> + +=item * Repeat change: + +C<.> + +=item * Repeat + +C<ftFT: ; ,> + +=item * Registers: + +C<"a-"z "" "0 "* "+ "_> (black hole) + +=item * Line-wise shortcuts: + +C<dd cc yy> + +=item * Shortcuts: + +C<s S C D> + +=item * Scroll the scrollback buffer: + +C<Ctrl-E Ctrl-D Ctrl-Y Ctrl-U Ctrl-F Ctrl-B> + +=item * Switch to last active window: + +C<Ctrl-6/Ctrl-^> + +=item * Switch split windows: + +<Ctrl-W j Ctrl-W k> + +=item * Undo/Redo: + +C<u Ctrl-R> + +=back + +Counts and combinations work as well, e.g. C<d5fx> or C<3iabcE<lt>escE<gt>>. Counts also work with mapped ex-commands (see below), e.g. if you map C<gb> to do C<:bn>, then C<2gb> will switch to the second next buffer.  Repeat also supports counts. + +=head3 REGISTERS + +=over 4 + +=item * Appending to register with C<"A-"Z> + +=item * C<""> is the default yank/delete register. + +=item * C<"0> contains the last yank (if no register was specified). + +=item * The special registers C<"* "+> both contain irssi's internal cut-buffer. + +=back + +=head2 INSERT MODE + +The following insert mode mappings are supported: + +=over 4 + +=item * Insert register content: Ctrl-R x (where x is the register to insert) + +=back + +=head2 EX-MODE + +Ex-mode (activated by C<:> in command mode) supports the following commands: + +=over 4 + +=item * Command History: + +C<E<lt>uparrowE<gt>> - cycle backwards through history + +C<E<lt>downarrowE<gt>> - cycle forwards through history + +C<:eh> - show ex history + +=item * Switching buffers: + +C<:[N]b [N]> - switch to channel number + +C<:b#>       - switch to last channel + +C<:b> E<lt>partial-channel-nameE<gt> + +C<:b> <partial-server>/<partial-channel> + +C<:buffer {args}> (same as C<:b>) + +C<:[N]bn[ext] [N]> - switch to next window + +C<:[N]bp[rev] [N]> - switch to previous window + +=item * Close window: + +C<:[N]bd[elete] [N]> + +=item * Display windows: + +C<:ls>, C<:buffers> + +=item * Display registers: + +C<:reg[isters] {args}>, C<:di[splay] {args}> + +=item * Display undolist: + +C<:undol[ist]> (mostly used for debugging) + +=item * Source files: + +C<:so[urce]> - only sources vim_moderc at the moment, +                         F<{file}> not supported + +=item * Mappings: + +C<:map> - display custom mappings + +=item * Saving mappings: + +C<:mkv[imrc][!]> - like in Vim, but [file] not supported + +=item * Substitution: + +C<:s///> - I<i> and I<g> are supported as flags, only C<///> can be used as +eparator, and it uses Perl regex syntax instead of Vim syntax. + +=item * Settings: + +C<:se[t]> - display all options + +C<:se[t] {option}>         - display all matching options + +C<:se[t] {option} {value}> - change option to value + +=back + +=head3 MAPPINGS + +=head4 Commands + +=over 4 + +=item * C<:map {lhs}>       - display mappings starting with {lhs} + +=item * C<:map {lhs} {rhs}> - add mapping + +=item * C<:unm[ap] {lhs}>   - remove custom mapping + +=back + +=head4 Parameters + +I<{lhs}> is the key combination to be mapped, I<{rhs}> the target. The +C<E<lt>E<gt>> notation is used + +(e.g. C<E<lt>C-FE<gt>> is I<Ctrl-F>), case is ignored. + Supported C<E<lt>E<gt>> keys are: + +=over 4 + +=item * C<E<lt>C-AE<gt>> - C<E<lt>C-ZE<gt>>, + +=item * C<E<lt>C-^E<gt>>, C<E<lt>C-6E<gt>> + +=item * C<E<lt>SpaceE<gt>> + +=item * C<E<lt>CRE<gt>> - Enter + +=item * C<E<lt>BSE<gt>> - Backspace + +=item * C<E<lt>NopE<gt>> - No-op (Do Nothing). + +=back + +Mapping ex-mode and irssi commands is supported. When mapping ex-mode commands +the trailing C<E<lt>CrE<gt>> is not necessary. Only default mappings can be used +in I<{rhs}>. + +Examples: + +=over 4 + +=item * C<:map w  W>      - to remap w to work like W + +=item * C<:map gb :bnext> - to map gb to call :bnext + +=item * C<:map gB :bprev> + +=item * C<:map g1 :b 1>   - to map g1 to switch to buffer 1 + +=item * C<:map gb :b>     - to map gb to :b, 1gb switches to buffer 1, 5gb to 5 + +=item * C<:map E<lt>C-LE<gt> /clear> - map Ctrl-L to irssi command /clear + +=item * C<:map E<lt>C-GE<gt> /window goto 1> + +=item * C<:map E<lt>C-EE<gt> <Nop>> - disable E<lt>C-EE<gt>, it does nothing now + +=item * C<:unmap E<lt>C-EE<gt>> - restore default behavior of C<E<lt>C-EE<gt>> +after disabling it + +=back + +Note that you must use C</> for irssi commands (like C</clear>), the current value +of Irssi's cmdchars does B<not> work! This is necessary do differentiate between +ex-commands and irssi commands. + +=head2 SETTINGS + +The settings are stored as irssi settings and can be set using C</set> as usual +(prepend C<vim_mode_> to setting name) or using the C<:set> ex-command. The +following settings are available: + +=over 4 + +=item * utf8 - Support UTF-8 characters, boolean, default on + +=item * debug - Enable debug output, boolean, default off + +=item * cmd_seq - Char that when double-pressed simulates C<E<lt>EscE<gt>>, string, default '' (disabled) + +=item * start_cmd - Start every line in command mode, boolean, default off + +=item * max_undo_lines - Sze of the undo buffer. Integer, default 50 items. + +=item * ex_history_size - Number of items stored in the ex-mode history. Integer, default 100. + +=item * prompt_leading_space - Ddetermines whether ex mode prepends a space to the displayed input. Boolean, default on + +=back + +In contrast to irssi's settings, C<:set> accepts 0 and 1 as values for boolean +settings, but only vim_mode's settings can be set/displayed. + +Examples: + +   :set cmd_seq=j   # set cmd_seq to j +   :set cmd_seq=    # disable cmd_seq +   :set debug=on    # enable debug +   :set debug=off   # disable debug + +=head1 SUPPORT + +Any behavior different from Vim (unless explicitly documented) should be +considered a bug and reported. + +B<NOTE:> This script is still under heavy development, and there may be bugs. +Please submit reproducible sequences to the bug-tracker at: +L<http://github.com/shabble/irssi-scripts/issues/new> + +or contact rudi_s or shabble on irc.freenode.net (#irssi and #irssi_vim) + +=head1 AUTHORS + +Copyright E<copy> 2010-2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> and + +Copyright E<copy> 2010-2011 Simon Ruderich C<E<lt>simon@ruderich.orgE<gt>> + +=head1 THANKS + +Particular thanks go to + +=over 4 + +=item * estragib: a lot of testing and many bug reports and feature requests + +=item * iaj: testing + +=item * tmr: explaining how various bits of vim work + +=back + +as well as the rest of C<#irssi> and C<#irssi_vim> on Freenode IRC. + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=over 4 + +=item * + +count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does + +=item * + +mapping an incomplete ex-command doesn't open the ex-mode with the partial +command (e.g. C<:map gb :b> causes an error instead of opening the ex-mode and +displaying C<:bE<lt>cursorE<gt>>) + +=item * + + undo/redo cursor positions are mostly wrong + +=back + +=head1 TODO + +=over 4 + +=item * + +History: + +=over 4 + +=item * + + C< * /,?,n,N> to search through history (like rl_history_search.pl) + +=back + +=item * + +Window switching (C<:b>) + +=over 4 + +=item * + +Tab completion of window(-item) names + +=item * + +non-sequential matches(?) + +=back + +=back + +See also the TODO file at +L<github|https://github.com/shabble/irssi-scripts/blob/master/vim-mode/TODO> for +many many more things. + +=head2 WONTFIX + +Things we're not ever likely to do: + +=over 4 + +=item * Macros + +=back + +=cut  use strict;  use warnings; @@ -245,9 +591,9 @@ use Irssi::TextUI;              # for sbar_items_redraw  use Irssi::Irc;                 # necessary for 0.8.14 -use vars qw($VERSION %IRSSI); -$VERSION = "1.0.1"; -%IRSSI = + +our $VERSION = "1.0.2"; +our %IRSSI   =    (     authors         => "Tom Feist (shabble), Simon Ruderich (rudi_s)",     contact         => 'shabble+irssi@metavore.org, ' @@ -267,24 +613,24 @@ sub M_CMD () { 1 }  # insert mode  sub M_INS () { 0 }  # extended mode (after a :?) -sub M_EX () { 2 } +sub M_EX  () { 2 }  # operator command -sub C_OPERATOR () { 0 } +sub C_OPERATOR   () { 0 }  # normal command, no special handling necessary -sub C_NORMAL () { 1 } +sub C_NORMAL     () { 1 }  # command taking another key as argument -sub C_NEEDSKEY () { 2 } +sub C_NEEDSKEY   () { 2 }  # text-object command (i a)  sub C_TEXTOBJECT () { 3 }  # commands entering insert mode -sub C_INSERT () { 4 } +sub C_INSERT     () { 4 }  # ex-mode commands -sub C_EX () { 5 } +sub C_EX         () { 5 }  # irssi commands -sub C_IRSSI () { 6 } +sub C_IRSSI      () { 6 }  # does nothing -sub C_NOP () { 7 } +sub C_NOP        () { 7 }  # setting types, match irssi types as they are stored as irssi settings  sub S_BOOL () { 0 } @@ -310,19 +656,19 @@ my $commands              repeatable => 1 },       # arrow like movement -      h     => { char => 'h',       func => \&cmd_h, type => C_NORMAL }, -      l     => { char => 'l',       func => \&cmd_l, type => C_NORMAL }, +     h      => { char => 'h',       func => \&cmd_h, type => C_NORMAL }, +     l      => { char => 'l',       func => \&cmd_l, type => C_NORMAL },       "\x7F" => { char => '<BS>',    func => \&cmd_h, type => C_NORMAL },       ' '    => { char => '<Space>', func => \&cmd_l, type => C_NORMAL },       # history movement -     j  => { char => 'j',  func => \&cmd_j,  type => C_NORMAL, -             no_operator => 1 }, -     k  => { char => 'k',  func => \&cmd_k,  type => C_NORMAL, -             no_operator => 1 }, -     gg => { char => 'gg', func => \&cmd_gg, type => C_NORMAL, -             no_operator => 1 }, -     G  => { char => 'G',  func => \&cmd_G,  type => C_NORMAL, -             needs_count => 1, no_operator => 1 }, +     j      => { char => 'j',  func => \&cmd_j,  type => C_NORMAL, +                 no_operator => 1 }, +     k      => { char => 'k',  func => \&cmd_k,  type => C_NORMAL, +                 no_operator => 1 }, +     gg     => { char => 'gg', func => \&cmd_gg, type => C_NORMAL, +                 no_operator => 1 }, +     G      => { char => 'G',  func => \&cmd_G,  type => C_NORMAL, +                 needs_count => 1, no_operator => 1 },       # char movement, take an additional parameter and use $movement        f  => { char => 'f', func => \&cmd_f, type => C_NEEDSKEY,                selection_needs_move_right => 1 }, @@ -345,11 +691,11 @@ my $commands       gE => { char => 'gE', func => \&cmd_gE, type => C_NORMAL,               selection_needs_move_right => 1 },       # text-objects, leading _ means can't be mapped! -     _i => { char => 'i', func => \&cmd__i, type => C_TEXTOBJECT }, -     _a => { char => 'a', func => \&cmd__a, type => C_TEXTOBJECT }, +     _i => { char => 'i', func => \&cmd__i,      type => C_TEXTOBJECT }, +     _a => { char => 'a', func => \&cmd__a,      type => C_TEXTOBJECT },       # line movement -     '0' => { char => '0', func => \&cmd_0, type => C_NORMAL }, -     '^' => { char => '^', func => \&cmd_caret, type => C_NORMAL }, +     '0' => { char => '0', func => \&cmd_0,      type => C_NORMAL }, +     '^' => { char => '^', func => \&cmd_caret,  type => C_NORMAL },       '$' => { char => '$', func => \&cmd_dollar, type => C_NORMAL },       # delete chars       x => { char => 'x', func => \&cmd_x, type => C_NORMAL, @@ -423,6 +769,16 @@ my $commands  # All available commands in Ex-Mode.  my $commands_ex    = { +     # arrow keys - not actually used, see handle_input_buffer() +     # TODO: make these work. +     "\e[A"    => { char => ':exprev',    func => \&ex_history_back, +                    type => C_EX }, +     "\e[B"    => { char => ':exnext',    func => \&ex_history_fwd, +                    type => C_EX }, + +     # normal Ex mode commands. +     eh        => { char => ':exhist',    func => \&ex_history_show, +                    type => C_EX },       s         => { char => ':s',         func => \&ex_substitute,                      type => C_EX },       bnext     => { char => ':bnext',     func => \&ex_bnext, @@ -499,11 +855,16 @@ my $commands_ex  # default command mode mappings  my $maps = {}; -# Add all default mappings. -foreach my $char (keys %$commands) { -    next if $char =~ /^_/; # skip private commands (text-objects for now) -    add_map($char, $commands->{$char}); -} +# current imap still pending (first character entered) +my $imap = undef; + +# maps for insert mode +my $imaps +  = { +     # CTRL-R, insert register +     "\x12" => { map  => undef, func => \&insert_ctrl_r }, +    }; +  # GLOBAL VARIABLES @@ -520,8 +881,12 @@ my $settings       start_cmd      => { type => S_BOOL, value => 0 },       # not used yet       max_undo_lines => { type => S_INT,  value => 50 }, +     # size of history buffer for Ex mode. +     ex_history_size => { type => S_INT, value => 100 },       # prompt_leading_space       prompt_leading_space => { type => S_BOOL, value => 1 }, +     # <Leader> value for prepending to commands. +     map_leader     => { type => S_STR,  value => '\\' },      };  sub DEBUG { $settings->{debug}->{value} } @@ -541,6 +906,10 @@ my $should_ignore = 0;  # ex mode buffer  my @ex_buf; +# ex mode history storage. +my @ex_history; +my $ex_history_index = 0; +  # we are waiting for another mapped key (e.g. g pressed, but there are  # multiple mappings like gg gE etc.)  my $pending_map = undef; @@ -582,19 +951,6 @@ my $registers       '*' => '', # same       '_' => '', # black hole register, always empty      }; -foreach my $char ('a' .. 'z') { -    $registers->{$char} = ''; -} - -# current imap still pending (first character entered) -my $imap = undef; - -# maps for insert mode -my $imaps -  = { -     # CTRL-R, insert register -     "\x12" => { map  => undef, func => \&insert_ctrl_r }, -    };  # index into the history list (for j,k)  my $history_index = undef; @@ -615,24 +971,20 @@ my $completion_active = 0;  my $completion_string = '';  sub script_is_loaded { -    my $name = shift; -    print "Checking if $name is loaded" if DEBUG; -    no strict 'refs'; -    my $retval = defined %{ "Irssi::Script::${name}::" }; -    use strict 'refs'; - -    return $retval; +    return exists($Irssi::Script::{shift(@_) . '::'});  } -vim_mode_init(); +  # INSERT MODE COMMANDS  sub insert_ctrl_r {      my ($key) = @_; - +    _debug("ctrl-r called");      my $char = chr($key); +    _debug("ctrl-r called with $char"); +      return if not defined $registers->{$char} or $registers->{$char} eq '';      my $pos = _insert_at_position($registers->{$char}, 1, _input_pos()); @@ -1640,6 +1992,10 @@ sub cmd_ex_command {          return _warn("Ex-mode $1$2 doesn't exist!");      } +    # add this item to the ex mode history +    ex_history_add($arg_str); +    $ex_history_index = 0; # and reset the history position. +      my $count = $1;      if ($count eq '') {          $count = undef; @@ -2022,6 +2378,7 @@ sub _parse_mapping {      my ($string) = @_;      $string =~ s/<([^>]+)>/_parse_mapping_bracket($1)/ge; +    _warn("Parse mapping: $string");      if (index($string, '<invalid>') != -1) {          return undef;      } @@ -2047,6 +2404,8 @@ sub _parse_mapping_bracket {      # <BS>      } elsif ($string eq 'bs') {          $string = chr(127); +    } elsif ($string eq 'leader') { +        $string = $settings->{map_leader}->{value};      # Invalid char, return special string to recognize the error.      } else {          $string = '<invalid>'; @@ -2056,6 +2415,14 @@ sub _parse_mapping_bracket {  sub _parse_mapping_reverse {      my ($string) = @_; +    if (not defined $string) { +        _warn("Unable to reverse-map command: " . join('', @ex_buf)); +        return; +    } + +    my $escaped_leader = quotemeta($settings->{map_leader}->{value}); +    $string =~ s/$escaped_leader/<Leader>/g; +      # Convert char to <char-name>.      $string =~ s/ /<Space>/g;      $string =~ s/\n/<CR>/g; @@ -2070,6 +2437,9 @@ sub _parse_mapping_reverse {  sub _parse_partial_command_reverse {      my ($string) = @_; +    my $escaped_leader = quotemeta($settings->{map_leader}->{value}); +    $string =~ s/$escaped_leader/<Leader>/g; +      # Convert Ctrl-X to ^X.      $string =~ s/([\x01-\x1A])/"^" . chr(ord($1) + 64)/ge;      # Convert Ctrl-6 and Ctrl-^ to <C-^>. @@ -2227,9 +2597,9 @@ sub _matching_windows {  # STATUS ITEMS -# vi mode status item. -sub vim_mode_cb { -    my ($sb_item, $get_size_only) = @_; +#TODO: give these things better names. +sub vim_mode_cmd { +      my $mode_str = '';      if ($mode == M_INS) {          $mode_str = 'Insert'; @@ -2238,7 +2608,7 @@ sub vim_mode_cb {      } else {          $mode_str = '%_Command%_';          if ($register ne '"' or $numeric_prefix or $operator or $movement or -                                $pending_map) { +            $pending_map) {              my $partial = '';              if ($register ne '"') {                  $partial .= '"' . $register; @@ -2260,13 +2630,10 @@ sub vim_mode_cb {              $mode_str .= " ($partial)";          }      } -    $sb_item->default_handler($get_size_only, "{sb $mode_str}", '', 0); +    return $mode_str;  } -# :b window list item. -sub b_windows_cb { -    my ($sb_item, $get_size_only) = @_; - +sub vim_wins_data {      my $windows = '';      # A little code duplication of cmd_ex_command(), but \s+ instead of \s* so @@ -2286,6 +2653,31 @@ sub b_windows_cb {              }          }      } +    return $windows; +} + +sub vim_exp_mode { +    my ($server, $witem, $arg) = @_; +    return vim_mode_cmd(); +} + +sub vim_exp_wins { +    my ($server, $witem, $arg) = @_; +    return vim_wins_data(); +} + +# vi mode status item. +sub vim_mode_cb { +    my ($sb_item, $get_size_only) = @_; +    my $mode_str = vim_mode_cmd(); +    $sb_item->default_handler($get_size_only, "{sb $mode_str}", '', 0); +} + +# :b window list item. +sub b_windows_cb { +    my ($sb_item, $get_size_only) = @_; + +    my $windows = vim_wins_data();      $sb_item->default_handler($get_size_only, "{sb $windows}", '', 0);  } @@ -2309,8 +2701,10 @@ sub got_key {          $input_buf_timer            = Irssi::timeout_add_once(10, \&handle_input_buffer, undef);          print "Buffer Timer tag: $input_buf_timer" if DEBUG; +      } elsif ($mode == M_INS) { -        if ($key == 3) { # Ctrl-C enter command mode + +        if ($key == 3) { # Ctrl-C enters command mode              _update_mode(M_CMD);              _stop();              return; @@ -2370,6 +2764,13 @@ sub got_key {          Irssi::statusbar_items_redraw("vim_mode");      } elsif ($mode == M_EX) { + +        if ($key == 3) { # C-c cancels Ex mdoe as well. +            _update_mode(M_CMD); +            _stop(); +            return; +        } +          handle_command_ex($key);      }  } @@ -2389,20 +2790,33 @@ sub handle_input_buffer {          _update_mode(M_CMD);      } else { -        # we need to identify what we got, and either replay it -        # or pass it off to the command handler. -        # if ($mode == M_CMD) { -        #     # command -        #     my $key_str = join '', map { chr } @input_buf; -        #     if ($key_str =~ m/^\e\[([ABCD])/) { -        #         print "Arrow key: $1" if DEBUG; -        #     } else { -        #         print "Dunno what that is." if DEBUG; -        #     } -        # } else { -        #     _emulate_keystrokes(@input_buf); -        # } -        _emulate_keystrokes(@input_buf); +        # we have more than a single esc, implying an escape sequence +        # (meta-* or esc-*) + +        # currently, we only extract escape sequences if: +        # a) we're in ex mode +        # b) they're arrow keys (for history control) + +        if ($mode == M_EX) { +            # ex mode +            my $key_str = join '', map { chr } @input_buf; +            if ($key_str =~ m/^\e\[([ABCD])/) { +                my $arrow = $1; +                _debug( "Arrow key: $arrow"); +                if ($arrow eq 'A') { # up +                    ex_history_back(); +                } elsif ($arrow eq 'B') { # down +                    ex_history_fwd(); +                } else { +                    $arrow =~ s/C/right/; +                    $arrow =~ s/D/left/; +                    _debug("Arrow key $arrow not supported"); +                } +            } +        } else { +            # otherwise, we just forward them to irssi. +            _emulate_keystrokes(@input_buf); +        }          # Clear insert buffer, pressing "special" keys (like arrow keys)          # resets it. @@ -2414,7 +2828,7 @@ sub handle_input_buffer {  }  sub flush_input_buffer { -    Irssi::timeout_remove($input_buf_timer); +    Irssi::timeout_remove($input_buf_timer) if defined $input_buf_timer;      $input_buf_timer = undef;      # see what we've collected.      print "Input buffer flushed" if DEBUG; @@ -2537,13 +2951,7 @@ sub handle_command_cmd {      } elsif ($cmd->{type} == C_IRSSI) {          print "Processing irssi-command: $map->{char} ($cmd->{char})" if DEBUG; -        # TODO: fix me more better (general server/win/none context?) -        my $server = Irssi::active_server; -        if (defined $server) { -            $server->command($cmd->{func}); -        } else { -            Irssi::command($cmd->{func}); -        } +        _command_with_context($cmd->{func});          $numeric_prefix = undef;          return 1; # call _stop(); @@ -2746,7 +3154,7 @@ sub handle_command_ex {      # DEL key - remove last character      if ($key == 127) {          print "Delete" if DEBUG; -        if (scalar @ex_buf > 0) { +        if (@ex_buf > 0) {              pop @ex_buf;              _set_prompt(':' . join '', @ex_buf);          # Backspacing over : exits ex-mode. @@ -2764,14 +3172,21 @@ sub handle_command_ex {          print "Tab pressed" if DEBUG;          print "Ex buf contains: " . join('', @ex_buf) if DEBUG;          @tab_candidates = _tab_complete(join('', @ex_buf), [keys %$commands_ex]); - +        _debug("Candidates: " . join(", ", @tab_candidates)); +        if (@tab_candidates == 1) { +            @ex_buf = ( split('', $tab_candidates[0]), ' '); +            _set_prompt(':' . join '', @ex_buf); +        }      # Ignore control characters for now. -    } elsif ($key < 32) { +    } elsif ($key > 0 && $key < 32) {          # TODO: use them later, e.g. completion      # Append entered key      } else { -        push @ex_buf, chr $key; +        if ($key != -1) { +            # check we're not called from an ex_history_* function +            push @ex_buf, chr $key; +        }          _set_prompt(':' . join '', @ex_buf);      } @@ -2794,15 +3209,32 @@ sub _tab_complete {  sub vim_mode_init {      Irssi::signal_add_first 'gui key pressed' => \&got_key; -    Irssi::signal_add 'setup changed' => \&setup_changed; -    Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb'); +    Irssi::statusbar_item_register ('vim_mode',    0, 'vim_mode_cb');      Irssi::statusbar_item_register ('vim_windows', 0, 'b_windows_cb'); +    Irssi::expando_create('vim_cmd_mode' => \&vim_exp_mode, {}); +    Irssi::expando_create('vim_wins'     => \&vim_exp_wins, {}); + +      # Register all available settings.      foreach my $name (keys %$settings) {          _setting_register($name);      } +    foreach my $char ('a' .. 'z') { +        $registers->{$char} = ''; +    } + +    setup_changed(); + +    Irssi::signal_add 'setup changed' => \&setup_changed; + +    # Add all default mappings. +    foreach my $char (keys %$commands) { +        next if $char =~ /^_/; # skip private commands (text-objects for now) +        add_map($char, $commands->{$char}); +    } +      # Load the vim_moderc file if it exists.      ex_source('source'); @@ -2811,6 +3243,8 @@ sub vim_mode_init {      if ($settings->{start_cmd}->{value}) {          _update_mode(M_CMD); +    } else { +        _update_mode(M_INS);      }  } @@ -2995,7 +3429,7 @@ sub delete_map {      }      push @add, $keys; -    # Restore default keybindings in case we :unmaped a <Nop> or a remapped +    # Restore default keybindings in case we :unmapped a <Nop> or a remapped      # key.      foreach my $key (@add) {          if (exists $commands->{$key}) { @@ -3129,6 +3563,8 @@ sub _update_mode {      }      Irssi::statusbar_items_redraw("vim_mode"); +    Irssi::statusbar_items_redraw ('uberprompt'); +  }  sub _set_prompt { @@ -3206,3 +3642,105 @@ sub _warn {      print '%_vim_mode: ', $warning, '%_';  } + +sub _debug { +    return unless DEBUG; + +    my ($format, @args) = @_; +    my $str = sprintf($format, @args); +    print $str; +} + +sub _command_with_context { +    my ($command) = @_; +    my $context; +    my $window = Irssi::active_win; +    if (defined $window) { +        my $witem = $window->{active}; +        if (defined $witem and ref($witem) eq 'Irssi::Windowitem') { +            $context = $witem; +        } else { +            $context = $window; +        } +    } else { +        my $server = Irssi::active_server; +        if (defined $server) { +            $context = $server; +        } +    } +    if (defined $context) { +        print "Command $command Using context: " . ref($context) if DEBUG; +        $context->command($command); +    } else { +        print "Command $command has no context" if DEBUG; +        Irssi::command($command); +    } +} + +sub ex_history_add { +    my ($line) = @_; + +    # check it's not an exact dupe of the previous history line + +    my $last_hist = $ex_history[$ex_history_index]; +    $last_hist = '' unless defined $last_hist; + +    return if $last_hist eq $line; + +    _debug("Adding $line to ex command history"); + +    # add it to the history +    unshift @ex_history, $line; + +    if ($settings->{ex_history_size}->{value} < @ex_history) { +        pop @ex_history; # junk the last entry if we've hit the max. +    } +} + +sub ex_history_fwd { + +    my $hist_max = $#ex_history; +    $ex_history_index++; +    if ($ex_history_index > $hist_max) { +        $ex_history_index = 0; +        _debug("ex history hit top, wrapping to 0"); +    } + +    my $line = $ex_history[$ex_history_index]; +    $line = '' if not defined $line; + +    _debug("Ex history line: $line"); + +    @ex_buf = split '', $line; +    handle_command_ex(-1); +} + +sub ex_history_back { +    my $hist_max = $#ex_history; +    $ex_history_index--; +    if ($ex_history_index == -1) { +        $ex_history_index = $hist_max; +        _debug("ex history hit bottom, wrapping to $hist_max"); + +    } + +    my $line = $ex_history[$ex_history_index]; +    $line = '' if not defined $line; + +    _debug("Ex history line: $line"); +    @ex_buf = split '', $line; +    handle_command_ex(-1); + +} + +sub ex_history_show { +    my $win = Irssi::active_win(); +    $win->print("Ex command history:"); +    for my $i (0 .. $#ex_history) { +        my $flag = $i == $ex_history_index +          ? ' <' +          : ''; +        $win->print("$i " . $ex_history[$i] . $flag); +    } +} +vim_mode_init(); | 
