aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Makefile42
-rw-r--r--README.md95
-rw-r--r--act_hide/act_hide.pl353
-rw-r--r--feature-tests/augment_inputline.pl9
-rw-r--r--feature-tests/bindings.pl75
-rw-r--r--feature-tests/easy_exec.pl59
-rw-r--r--feature-tests/exec.pl442
-rw-r--r--feature-tests/key_sig.pl51
-rw-r--r--feature-tests/redir-input.pl9
-rw-r--r--feature-tests/sig_unbind.pl58
-rw-r--r--feature-tests/signal_logger.pl170
-rw-r--r--feature-tests/signal_redir.pl116
-rw-r--r--feature-tests/template.pl56
-rw-r--r--history-search/README.pod135
-rw-r--r--history-search/rl_history_search.pl551
-rw-r--r--ido-mode/README.pod247
-rw-r--r--ido-mode/ido_switcher.pl418
-rw-r--r--joinforward/joinforward.pl25
-rw-r--r--modules/overlays/COPYING339
-rw-r--r--modules/overlays/Makefile59
-rw-r--r--modules/overlays/README.md10
-rw-r--r--modules/overlays/overlay_core.c46
-rw-r--r--modules/overlays/overlay_core.h27
-rw-r--r--modules/overlays/overlay_impl.c23
-rw-r--r--modules/overlays/overlay_impl.h9
-rw-r--r--modules/overlays/overlay_irssi.h52
-rw-r--r--patches/fix-cumode_space.patch32
-rw-r--r--patches/fix-signal-remove-coderef.patch22
-rw-r--r--prompt_info/README.pod273
-rw-r--r--prompt_info/old/input_overlay.pl (renamed from prompt_info/input_overlay.pl)188
-rw-r--r--prompt_info/old/overlays.pl (renamed from prompt_info/overlays.pl)0
-rw-r--r--prompt_info/old/prompt_info.pl (renamed from prompt_info/prompt_info.pl)0
-rw-r--r--prompt_info/old/prompt_replace.pl (renamed from prompt_info/prompt_replace.pl)0
-rw-r--r--prompt_info/old/visual.pl (renamed from prompt_info/visual.pl)0
-rw-r--r--prompt_info/uberprompt.pl456
-rw-r--r--quit-notify/README.pod92
-rw-r--r--quit-notify/notifyquit.pl466
-rwxr-xr-xreadme_generator.pl77
-rw-r--r--scrolled-reminder/scrolled_reminder.pl8
-rw-r--r--testing/.gitignore5
-rw-r--r--testing/Changes0
-rw-r--r--testing/MANIFEST13
-rw-r--r--testing/MANIFEST.SKIP9
-rw-r--r--testing/Makefile.PL30
-rw-r--r--testing/README0
-rw-r--r--testing/lib/Test/Irssi.pm314
-rw-r--r--testing/lib/Test/Irssi/Callbacks.pm123
-rw-r--r--testing/lib/Test/Irssi/Driver.pm258
-rw-r--r--testing/lib/Test/Irssi/Misc.pm35
-rw-r--r--testing/lib/Test/Irssi/Test.pm308
-rw-r--r--testing/lib/Test/Irssi/VirtualIrssi.pm32
-rwxr-xr-xtesting/t/001-use.t27
-rwxr-xr-xtesting/t/002-init.t33
-rw-r--r--testing/test-shim.pl114
-rwxr-xr-xtesting/test.pl17
-rwxr-xr-xtesting/tests/001-basic.t50
-rwxr-xr-xtesting/tests/002-cursor-test.t29
-rw-r--r--tinyurl-tabcomplete/README.pod56
-rw-r--r--tinyurl-tabcomplete/complete-tiny-url.pl63
-rw-r--r--undo/kill-ring.pl129
-rw-r--r--undo/undo.pl134
-rw-r--r--vim-mode/README.pod584
-rw-r--r--vim-mode/input.txt58
-rw-r--r--vim-mode/irssi/config38
l---------vim-mode/irssi/scripts/autorun/uberprompt.pl1
l---------vim-mode/irssi/scripts/autorun/vim_mode.pl1
-rw-r--r--vim-mode/tests.pl171
-rw-r--r--vim-mode/vim_mode.pl1186
69 files changed, 8124 insertions, 789 deletions
diff --git a/.gitignore b/.gitignore
index 830c860..91f484a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/README.md b/README.md
index 3562c91..51c7dcf 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,101 @@
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
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/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
index 006eaf1..ece220d 100644
--- a/feature-tests/bindings.pl
+++ b/feature-tests/bindings.pl
@@ -23,11 +23,39 @@ our %IRSSI = (
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 {
- update_keymap();
+
+ $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 {
@@ -42,35 +70,44 @@ sub cmd_showbinds {
$win->print("Done showing window bindings:", Irssi::MSGLEVEL_CLIENTCRAP);
}
-sub get_keymap {
+
+sub sig_print_text {
my ($text_dest, $str, $str_stripped) = @_;
- if ($text_dest->{level} == Irssi::MSGLEVEL_CLIENTCRAP and $text_dest->{target} eq '') {
- if (not defined($text_dest->{'server'})) {
- if ($str_stripped =~ m/((?:meta-)+)(.)\s+change_window (\d+)/) {
- my ($level, $key, $window) = ($1, $2, $3);
- #my $numlevel = ($level =~ y/-//) - 1;
- my $kk = $level . $key;
- $keymap->{$kk} = $window;
- }
- Irssi::signal_stop();
- }
+ 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 update_keymap {
- $keymap = {};
+
+sub capture_bind_data {
Irssi::signal_remove('command bind' => 'watch_keymap');
- Irssi::signal_add_first('print text' => 'get_keymap');
+ Irssi::signal_add_first('print text' => 'sig_print_text');
Irssi::command('bind'); # stolen from grep
- Irssi::signal_remove('print text' => 'get_keymap');
- Irssi::signal_add('command bind' => 'watch_keymap');
- #Irssi::timeout_add_once(100, 'eventChanged', undef);
+ Irssi::signal_remove('print text' => 'sig_print_text');
+
}
+
# watch keymap changes
sub watch_keymap {
- Irssi::timeout_add_once(1000, 'update_keymap', undef);
+ 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/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/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 66628f1..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;
@@ -153,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 =
@@ -243,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')) {
@@ -281,7 +435,9 @@ sub ido_switch_init {
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);
@@ -296,23 +452,46 @@ sub setup_changed {
$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);
@@ -404,11 +583,12 @@ sub get_all_windows {
}
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};
@@ -438,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;
@@ -500,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;
@@ -553,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;
}
diff --git a/joinforward/joinforward.pl b/joinforward/joinforward.pl
index 26630b2..1acd72a 100644
--- a/joinforward/joinforward.pl
+++ b/joinforward/joinforward.pl
@@ -24,26 +24,37 @@ my $forwards;
init();
-sub init() {
- Irssi::signal_add('event 470', 'sig_470');
- Irssi::signal_add('event 473', 'sig_473');
+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',
- my $nick = quotemeta(Irssi::parse_special('$N'));
- if ($args =~ m/^$nick (#.*?)\s+(#.*?)\s+(.*)$/) {
+ 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
- my $nick = quotemeta(Irssi::parse_special('$N'));
- if ($args =~ m/^$nick\s+(#.*?)\s+/) {
+ if ($args =~ m/^(#.*?)\s+/) {
if (exists $forwards->{$1}) {
$server->command("join " . $forwards->{$1});
}
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/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', \&region_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', \&region_toggle);
+ Irssi::command_bind('region_clear', \&region_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 0706d29..62bda13 100644
--- a/prompt_info/uberprompt.pl
+++ b/prompt_info/uberprompt.pl
@@ -1,127 +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.
-# A parameter corresponding to the UP_* constants listed below
-# is required, in the format `/prompt set -inner Hello!'
-# /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.
-#
-# /help prompt - show help for uberprompt commands
-#
-# 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 =
@@ -154,6 +309,8 @@ my $emit_request = 0;
my $expando_refresh_timer;
my $expando_vars = {};
+my $init_callbacks = {load => '', unload => ''};
+
pre_init();
sub pre_init {
@@ -169,13 +326,13 @@ sub prompt_subcmd_handler {
sub _error($) {
my ($msg) = @_;
- Irssi::active_win->print($msg, Irssi::MSGLEVEL_CLIENTERROR);
+ Irssi::active_win->print($msg, MSGLEVEL_CLIENTERROR);
}
sub _debug_print($) {
return unless DEBUG;
my ($msg) = @_;
- Irssi::active_win->print($msg, Irssi::MSGLEVEL_CLIENTCRAP);
+ Irssi::active_win->print($msg, MSGLEVEL_CLIENTCRAP);
}
sub _print_help {
@@ -198,11 +355,11 @@ sub _print_help {
" 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",
+ " { -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",
+ " | -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:",
'',
@@ -215,7 +372,7 @@ sub _print_help {
"",
);
- Irssi::print($_, Irssi::MSGLEVEL_CLIENTCRAP) for @help_lines;
+ Irssi::print($_, MSGLEVEL_CLIENTCRAP) for @help_lines;
Irssi::signal_stop;
}
}
@@ -228,7 +385,9 @@ 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();
@@ -241,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);
@@ -349,17 +512,20 @@ 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) {
@@ -415,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;
}
@@ -434,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);
@@ -470,7 +636,7 @@ sub uberprompt_render_prompt {
}
}
- #_debug_print("Redrawing with: $prompt, size-only: $get_size_only");
+ _debug_print("rendering with: $prompt");
if (($prompt ne $prompt_last) or $emit_request) {
@@ -491,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;
}
@@ -512,6 +678,17 @@ 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 {
@@ -521,6 +698,17 @@ sub restore_prompt_items {
_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 {
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 428156c..7c5f957 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 }
@@ -316,14 +662,14 @@ my $commands
"\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 },
@@ -346,11 +692,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,
@@ -424,6 +770,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,
@@ -500,11 +856,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
@@ -521,8 +882,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} }
@@ -542,6 +907,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;
@@ -583,19 +952,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;
@@ -616,24 +972,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());
@@ -1641,6 +1993,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;
@@ -2025,6 +2381,7 @@ sub _parse_mapping {
my ($string) = @_;
$string =~ s/<([^>]+)>/_parse_mapping_bracket($1)/ge;
+ _warn("Parse mapping: $string");
if (index($string, '<invalid>') != -1) {
return undef;
}
@@ -2050,6 +2407,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>';
@@ -2059,6 +2418,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;
@@ -2073,6 +2440,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-^>.
@@ -2230,9 +2600,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';
@@ -2241,7 +2611,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;
@@ -2263,13 +2633,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
@@ -2289,6 +2656,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);
}
@@ -2312,8 +2704,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;
@@ -2373,6 +2767,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);
}
}
@@ -2392,20 +2793,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.
@@ -2417,7 +2831,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;
@@ -2540,13 +2954,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();
@@ -2749,7 +3157,7 @@ sub handle_command_ex {
# BS key (8) or DEL key (127) - remove last character.
if ($key == 8 || $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.
@@ -2767,14 +3175,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);
}
@@ -2797,15 +3212,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');
@@ -2814,6 +3246,8 @@ sub vim_mode_init {
if ($settings->{start_cmd}->{value}) {
_update_mode(M_CMD);
+ } else {
+ _update_mode(M_INS);
}
}
@@ -2998,7 +3432,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}) {
@@ -3132,6 +3566,8 @@ sub _update_mode {
}
Irssi::statusbar_items_redraw("vim_mode");
+ Irssi::statusbar_items_redraw ('uberprompt');
+
}
sub _set_prompt {
@@ -3209,3 +3645,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();