aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Feist <shabble@metavore.org>2010-10-14 20:45:40 +0000
committerTom Feist <shabble@metavore.org>2010-10-14 20:45:40 +0000
commitb195e2e51b643a5004c2f7f276444ad93d8e28d3 (patch)
treeffeede956bda0c0e2e28eeac78fb91cdbbc5eff6
parentMerge branch 'dev' of github.com:shabble/irssi-scripts into dev (diff)
parentvim_mode: Mention that :set only sets vim_mode settings. (diff)
downloadirssi-scripts-b195e2e51b643a5004c2f7f276444ad93d8e28d3.tar.gz
irssi-scripts-b195e2e51b643a5004c2f7f276444ad93d8e28d3.zip
merged config
-rw-r--r--docs/allsigs-uniq.txt358
-rwxr-xr-xdocs/buildpod.pl4
-rw-r--r--feature-tests/subscript.pl98
-rw-r--r--feature-tests/test.sub7
-rw-r--r--history-search/rl_history_search.pl63
-rw-r--r--prompt_info/uberprompt.pl379
-rw-r--r--prompt_info/visual.pl191
-rw-r--r--scrolled-reminder/scrolled_reminder.pl89
-rw-r--r--test/irssi/config15
-rw-r--r--vim-mode/TODO42
-rw-r--r--vim-mode/vim_mode.pl602
11 files changed, 1669 insertions, 179 deletions
diff --git a/docs/allsigs-uniq.txt b/docs/allsigs-uniq.txt
new file mode 100644
index 0000000..4dc83e3
--- /dev/null
+++ b/docs/allsigs-uniq.txt
@@ -0,0 +1,358 @@
+"away mode changed"
+"awaylog show"
+"ban type changed"
+"beep"
+"channel created"
+"channel destroyed"
+"channel joined"
+"channel rejoin new"
+"channel sync"
+"channel wholist"
+"chanquery abort"
+"chanquery ban end"
+"chanquery ban"
+"chanquery mode"
+"chanquery who end"
+"chat protocol created"
+"chat protocol deinit"
+"chat protocol destroyed"
+"chat protocol unknown"
+"chatnet destroyed"
+"chatnet read"
+"chatnet saved"
+"complete command action"
+"complete command alias"
+"complete command away"
+"complete command bind"
+"complete command cat"
+"complete command connect"
+"complete command dcc send"
+"complete command disconnect"
+"complete command format"
+"complete command help"
+"complete command load"
+"complete command msg"
+"complete command query"
+"complete command rawlog open"
+"complete command rawlog save"
+"complete command recode remove"
+"complete command reconnect"
+"complete command reload"
+"complete command save"
+"complete command script load"
+"complete command script unload"
+"complete command server add"
+"complete command server remove"
+"complete command server"
+"complete command set"
+"complete command stats"
+"complete command toggle"
+"complete command topic"
+"complete command unalias"
+"complete command window goto"
+"complete command window item move"
+"complete command window server"
+"complete erase command action"
+"complete erase command msg"
+"complete erase command query"
+"complete word"
+"ctcp action"
+"ctcp msg clientinfo"
+"ctcp msg dcc accept"
+"ctcp msg dcc chat"
+"ctcp msg dcc resume"
+"ctcp msg dcc send"
+"ctcp msg dcc"
+"ctcp msg ping"
+"ctcp msg time"
+"ctcp msg userinfo"
+"ctcp msg version"
+"ctcp msg"
+"ctcp reply dcc reject"
+"ctcp reply dcc"
+"ctcp reply ping"
+"ctcp reply"
+"dcc chat message"
+"dcc closed"
+"dcc connected"
+"dcc ctcp action"
+"dcc ctcp dcc"
+"dcc destroyed"
+"dcc error close not found"
+"dcc error connect"
+"dcc error ctcp"
+"dcc error file create"
+"dcc error file open"
+"dcc error get not found"
+"dcc error send exists"
+"dcc error send no route"
+"dcc error unknown type"
+"dcc error write"
+"dcc list print"
+"dcc rejected"
+"dcc reply dcc"
+"dcc reply send pasv"
+"dcc request send"
+"dcc request"
+"dcc server started"
+"default command server"
+"default command"
+"default ctcp msg dcc"
+"default ctcp msg"
+"default ctcp reply dcc"
+"default ctcp reply"
+"default dcc ctcp"
+"default event numeric"
+"default event"
+"error command"
+"event 001"
+"event 004"
+"event 005"
+"event 221"
+"event 254"
+"event 271"
+"event 272"
+"event 281"
+"event 301"
+"event 302"
+"event 303"
+"event 305"
+"event 306"
+"event 311"
+"event 312"
+"event 313"
+"event 314"
+"event 315"
+"event 317"
+"event 318"
+"event 319"
+"event 324"
+"event 326"
+"event 327"
+"event 328"
+"event 329"
+"event 330"
+"event 332"
+"event 333"
+"event 338"
+"event 341"
+"event 344"
+"event 345"
+"event 346"
+"event 347"
+"event 348"
+"event 349"
+"event 352"
+"event 353"
+"event 364"
+"event 365"
+"event 367"
+"event 368"
+"event 369"
+"event 372"
+"event 375"
+"event 376"
+"event 377"
+"event 378"
+"event 379"
+"event 381"
+"event 386"
+"event 387"
+"event 388"
+"event 389"
+"event 396"
+"event 401"
+"event 403"
+"event 404"
+"event 405"
+"event 407"
+"event 408"
+"event 421"
+"event 422"
+"event 432"
+"event 433"
+"event 436"
+"event 437"
+"event 438"
+"event 439"
+"event 442"
+"event 465"
+"event 470"
+"event 471"
+"event 472"
+"event 473"
+"event 474"
+"event 475"
+"event 476"
+"event 477"
+"event 478"
+"event 479"
+"event 482"
+"event 486"
+"event 489"
+"event 494"
+"event 506"
+"event 707"
+"event 716"
+"event 717"
+"event 728"
+"event 729"
+"event connected"
+"event empty"
+"event error"
+"event invite"
+"event join"
+"event kick"
+"event kill"
+"event mode"
+"event nick"
+"event notice"
+"event part"
+"event ping"
+"event pong"
+"event privmsg"
+"event quit"
+"event silence"
+"event topic"
+"event wallops"
+"exec input"
+"expando timer"
+"flood"
+"gui dialog"
+"gui entry redirect"
+"gui exit"
+"gui key pressed"
+"gui page scrolled"
+"gui print text finished"
+"gui print text"
+"gui window create override"
+"gui window created"
+"ignore changed"
+"ignore created"
+"ignore destroyed"
+"irssi init finished"
+"irssi init read settings"
+"lag ping error"
+"layout reset"
+"layout restore item"
+"layout restore window"
+"layout restore"
+"layout save item"
+"layout save window"
+"layout save"
+"list subcommands"
+"log config read"
+"log config save"
+"log create failed"
+"log locked"
+"log new"
+"log written"
+"mainwindow destroyed"
+"mainwindow moved"
+"mainwindow resized"
+"message dcc action"
+"message dcc ctcp"
+"message dcc own"
+"message dcc own_action"
+"message dcc own_ctcp"
+"message dcc"
+"message irc mode"
+"message irc own_action"
+"message join"
+"message own_private"
+"message own_public"
+"message private"
+"message public"
+"message quit"
+"module error"
+"module loaded"
+"module unloaded"
+"netsplit new"
+"nickfind event whois"
+"nicklist changed"
+"nicklist host changed"
+"nicklist new"
+"nicklist remove"
+"notifylist away changed"
+"notifylist event whois away"
+"notifylist event whois end"
+"notifylist event whois"
+"notifylist event"
+"notifylist joined"
+"notifylist left"
+"pidwait"
+"print format"
+"print starting"
+"print text"
+"proxy client dump"
+"query created"
+"query destroyed"
+"query nick changed"
+"query server changed"
+"reload"
+"requested usermode change"
+"script destroyed"
+"script error"
+"send command"
+"send text"
+"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"
+"session restore channel"
+"session restore nick"
+"session restore server"
+"session restore"
+"session save channel"
+"session save server"
+"session save"
+"settings errors"
+"setup changed"
+"setup reread"
+"silent event who"
+"silent event whois"
+"terminal resized"
+"theme changed"
+"theme destroyed"
+"user mode changed"
+"userhost event"
+"whois away"
+"whois default event"
+"whois end"
+"whois event not found"
+"whois event"
+"whois oper"
+"whois try whowas"
+"whowas away"
+"whowas event end"
+"whowas event"
+"window activity"
+"window changed automatic"
+"window changed"
+"window created"
+"window dehilight"
+"window destroyed"
+"window history changed"
+"window item remove"
+"window item server changed"
+"window print info"
+"window refnum changed"
+"window server changed"
diff --git a/docs/buildpod.pl b/docs/buildpod.pl
index dfddf54..debaa3b 100755
--- a/docs/buildpod.pl
+++ b/docs/buildpod.pl
@@ -32,7 +32,7 @@ sub new {
q(find something obviously wrong, or have requests ),
q(for further documentation on topics not yet ),
q(filled out, please ),
- q(<a href="http://github.com/shabble/shab-irssi-scripts/issues#">create an issue</a>),
+ q(<a href="http://github.com/shabble/irssi-scripts/issues#">create an issue</a>),
" on my Github page, and I'll see what I can do.</b></p>",
);
@@ -90,7 +90,7 @@ package main;
use File::Copy;
-my $output_dir = "../../tmp/shab-irssi-scripts/docs/";
+my $output_dir = "../../tmp/irssi-scripts/docs/";
my $batchconv = Pod::Simple::HTMLBatch::Custom->new;
my $css = 'podstyle.css';
diff --git a/feature-tests/subscript.pl b/feature-tests/subscript.pl
new file mode 100644
index 0000000..8550409
--- /dev/null
+++ b/feature-tests/subscript.pl
@@ -0,0 +1,98 @@
+use strict;
+use Irssi;
+use Irssi::TextUI; # for sbar_items_redraw
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = "0.1";
+
+%IRSSI = (
+ authors => "shabble",
+ contact => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode',
+ name => "",
+ description => "",
+ license => "Public Domain",
+ changed => ""
+);
+
+my $functions = {};
+
+init();
+
+sub _input {
+ return int rand 1000;
+}
+
+sub load {
+ my $file = shift;
+ my $funcs;
+ if (-f $file) {
+ print "Loading from file: $file";
+ $funcs = do $file;
+ }
+ if (not defined $funcs) {
+ if ($@) {
+ print "failed to parse $file: $@";
+
+ } elsif ($!) {
+ print "failed to read $file: $!";
+
+ }
+ return;
+ }
+ my $ref = ref $funcs;
+ if ($ref ne 'HASH') {
+ print "$file didn't return a hashref: ", defined $ref ? $ref : 'undef';
+ return;
+ }
+
+ foreach my $name (keys %$funcs) {
+ my $func = $funcs->{$name};
+ if (exists $functions->{$name}) {
+ print "Redefining function $name";
+ } else {
+ print "adding function: $name";
+ }
+ $functions->{$name} = $func;
+ }
+
+ print "Loaded " . scalar(keys(%$funcs)) . " functions";
+}
+
+sub init {
+ Irssi::command_bind('subload', \&cmd_subload);
+ Irssi::command_bind('sublist', \&cmd_sublist);
+ Irssi::command_bind('subcall', \&cmd_subcall);
+}
+
+sub cmd_subload {
+ my $args = shift;
+ print "Going to load: $args";
+ load($args);
+}
+
+sub cmd_sublist {
+ foreach my $name (keys %$functions) {
+ my $func = $functions->{$name};
+ print "Function: $name => $func";
+ }
+}
+
+sub cmd_subcall {
+ my $args = shift;
+ my ($cmd, $cmdargs);
+ if ($args =~ m/^(\w+\b)(.*)$/) {
+ $cmd = $1; $cmdargs = $2;
+ } else {
+ print "Couldn't parse $args";
+ return;
+ }
+ my $fun = $functions->{$cmd};
+
+ if (ref $fun eq 'CODE') {
+ print "Calling $cmd with $cmdargs";
+ $fun->($cmdargs);
+ } else {
+ print "$cmd is not a coderef. cannot run";
+ }
+}
diff --git a/feature-tests/test.sub b/feature-tests/test.sub
new file mode 100644
index 0000000..350e89d
--- /dev/null
+++ b/feature-tests/test.sub
@@ -0,0 +1,7 @@
+{
+ 'moo' => sub { print "Moo" },
+ 'hello' => sub { print join(", ", @_) },
+ 'inp' => sub { print "input is : ", _input(); },
+};
+
+
diff --git a/history-search/rl_history_search.pl b/history-search/rl_history_search.pl
index 83ff934..eebb6c7 100644
--- a/history-search/rl_history_search.pl
+++ b/history-search/rl_history_search.pl
@@ -1,29 +1,39 @@
# Search within your typed history as you type (like ctrl-R in bash)
-# Usage:
-
+#
+# 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
@@ -53,7 +63,7 @@ $VERSION = '1.0';
description => 'Search within your typed history as you type'
. ' (like ctrl-R in readline applications)',
license => 'GPLv2 or later',
- url => 'http://github.com/shabble/shab-irssi-scripts/tree/master/history-search/',
+ url => 'http://github.com/shabble/irssi-scripts/tree/master/history-search/',
changed => '24/7/2010'
);
@@ -65,9 +75,10 @@ my @search_matches = ();
my $match_index = 0;
-sub DEBUG () { 0 }
+my $DEBUG_ENABLED = 0;
+sub DEBUG () { $DEBUG_ENABLED }
-# check we have prompt_info loaded.
+# check we have uberprompt loaded.
sub script_is_loaded {
my $name = shift;
@@ -79,19 +90,29 @@ sub script_is_loaded {
return $retval;
}
-unless (script_is_loaded('prompt_info')) {
- die "This script requires 'prompt_info' in order to work. "
+unless (script_is_loaded('uberprompt')) {
+ die "This script requires 'uberprompt.pl' in order to work. "
. "Please load it and try again";
} else {
history_init();
}
sub history_init {
+ Irssi::settings_add_bool('history_search', 'histsearch_debug', 0);
+
Irssi::command_bind('history_search_start', \&history_search);
- #Irssi::command_bind('history_search_exit', \&history_search_exit);
+
+ Irssi::signal_add ('setup changed' => \&setup_changed);
Irssi::signal_add_first('gui key pressed' => \&handle_keypress);
+
+ setup_changed();
}
+sub setup_changed {
+ $DEBUG_ENABLED = Irssi::settings_get_bool('histsearch_debug');
+}
+
+
sub history_search {
$search_active = 1;
$search_str = '';
@@ -105,16 +126,20 @@ sub history_search {
sub history_exit {
$search_active = 0;
- Irssi::signal_emit('change prompt', '');
+ Irssi::signal_emit('change prompt', '', 'UP_INNER');
}
sub update_history_prompt {
- Irssi::signal_emit('change prompt', " reverse-i-search: \`$search_str'");
+ my $col = scalar(@search_matches) ? '%g' : '%r';
+ Irssi::signal_emit('change prompt',
+ ' reverse-i-search: `' . $col . $search_str
+ . '%n' . "'",
+ 'UP_INNER');
}
sub update_history_matches {
my ($match_str) = @_;
- $match_str //= $search_str;
+ $match_str = $search_str unless defined $match_str;
my %unique;
my @matches = grep { m/\Q$match_str/i } @history_cache;
diff --git a/prompt_info/uberprompt.pl b/prompt_info/uberprompt.pl
new file mode 100644
index 0000000..0581dd3
--- /dev/null
+++ b/prompt_info/uberprompt.pl
@@ -0,0 +1,379 @@
+# This script replaces the default prompt status-bar item with one capable
+# of displaying additional information, under either user control or via
+# scripts.
+#
+# INSTALL:
+#
+# Place script in ~/.irssi/scripts/ and potentially symlink into autorun/
+# to ensure it starts at irssi startup.
+#
+# If not using autorun, manually load the script via:
+#
+# /script load uberprompt.pl
+#
+# If you have a custom prompt format, you may need to copy it to the
+# uberprompt_format setting. See below for details.
+#
+# USAGE:
+#
+# Although the script is designed primarily for other scripts to set
+# status information into the prompt, the following commands are available:
+#
+# TODO: Document positional settings.
+#
+# /prompt set - sets the prompt to the given argument. $p in the argument will
+# be replaced by the original prompt content.
+# /prompt clear - clears the additional data provided to the prompt.
+# /prompt on - enables the uberprompt (things may get confused if this is used
+# whilst the prompt is already enabled)
+# /prompt off - restore the original irssi prompt and prompt_empty statusbars.
+# unloading the script has the same effect.
+#
+# Additionally, the format for the prompt can be set via:
+#
+# UBERPROMPT FORMAT:
+#
+# /set uberprompt_format <format>
+#
+# The default is [$*], which is the same as the default provided in default.theme.
+# Changing this setting will update the prompt immediately, unlike editing your theme.
+#
+# An additional variable available within this format is '$uber', which expands to
+# the content of prompt data provided with the UP_INNER placement argument. For all
+# other placement arguments, it will expand to the empty string ''.
+#
+# NOTE: this setting completely overrides the prompt="..." line in your .theme
+# file, and may cause unexpected behaviour if your theme wishes to set a
+# different form of prompt. It can be simply copied from the theme file into
+# the above setting.
+#
+# Usage from other Scripts: signal 'change prompt' => 'string' => position
+#
+# eg:
+#
+# signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER;
+#
+# will set the prompt to include that content, by default '[$* some_string]'
+#
+# The possible position arguments are the following strings:
+#
+# UP_PRE - place the provided string before the prompt -- $string$prompt
+# UP_INNER - place the provided string inside the prompt -- {prompt $* $string}
+# UP_POST - place the provided string after the prompt -- $prompt$string
+# UP_ONLY - replace the prompt with the provided string -- $string
+#
+# All strings may use the special variable '$prompt' to include the prompt
+# verbatim at that position in the string. It is probably only useful for
+# the UP_ONLY mode however. '$prompt_nt' will include the prompt, minus any
+# trailing whitespace.
+#
+# NOTIFICATIONS:
+#
+# You can also be notified when the prompt changes in response to the previous
+# signal or manual commands via:
+#
+# signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... };
+#
+# Bugs:
+#
+# * Resizing the terminal rapidly whilst using this script in debug mode
+# may cause irssi to crash. See bug report at
+# http://bugs.irssi.org/index.php?do=details&task_id=772 for details.
+#
+# TODO:
+#
+# * report failure (somehow) to clients if hte prompt is disabled.
+# * fix issue at autorun startup with sbar item doesn't exist.
+#
+#
+#
+#
+# LICENCE:
+#
+# Copyright (c) 2010 Tom Feist
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+use strict;
+use warnings;
+
+use Irssi;
+use Irssi::TextUI; # for sbar_items_redraw
+use Data::Dumper;
+
+our $VERSION = "0.2";
+our %IRSSI =
+ (
+ authors => "shabble",
+ contact => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode',
+ name => "uberprompt",
+ description => "Helper script for dynamically adding text "
+ . "into the input-bar prompt.",
+ license => "MIT",
+ changed => "24/7/2010"
+ );
+
+
+my $DEBUG_ENABLED = 0;
+sub DEBUG { $DEBUG_ENABLED }
+
+my $prompt_data = '';
+my $prompt_data_pos = 'UP_INNER';
+
+my $prompt_last = '';
+my $prompt_format = '';
+
+pre_init();
+
+sub pre_init {
+ # my $start_time = Irssi::parse_special('$F');
+ # my $now = time();
+ # my $delta = $now - $start_time;
+
+ # print "Delta is $delta";
+ # if ($delta < 2) {
+ # print "starting a bit later";
+ # Irssi::timeout_add_once(5000, \&init, undef);
+ # } else {
+ # print "starting immediately";
+
+ # init();
+ # }
+ Irssi::command('statusbar prompt reset');
+ init();
+}
+
+sub prompt_subcmd_handler {
+ my ($data, $server, $item) = @_;
+ $data =~ s/\s+$//g; # strip trailing whitespace.
+ Irssi::command_runsub('prompt', $data, $server, $item);
+}
+
+sub UNLOAD {
+ # remove uberprompt and return the original ones.
+ print "Removing uberprompt and restoring original";
+ restore_prompt_items();
+}
+
+sub init {
+ Irssi::statusbar_item_register('uberprompt', 0, 'uberprompt_draw');
+
+ Irssi::settings_add_str('uberprompt', 'uberprompt_format', '[$*$uber] ');
+ Irssi::settings_add_bool('uberprompt', 'uberprompt_debug', 0);
+ Irssi::settings_add_bool('uberprompt', 'uberprompt_autostart', 1);
+
+ Irssi::command_bind("prompt", \&prompt_subcmd_handler);
+ Irssi::command_bind('prompt on', \&replace_prompt_items);
+ Irssi::command_bind('prompt off', \&restore_prompt_items);
+ Irssi::command_bind('prompt set',
+ sub {
+ my $args = shift;
+ $args =~ s/^\s*(\w+)\s*(.*$)/$2/;
+ my $mode = 'UP_' . uc($1);
+ Irssi::signal_emit 'change prompt', $args, $mode;
+ });
+ Irssi::command_bind('prompt clear',
+ sub {
+ Irssi::signal_emit 'change prompt', '$p', 'UP_POST';
+ });
+
+ Irssi::signal_add('setup changed', \&reload_settings);
+
+ # intialise the prompt format.
+ reload_settings();
+
+ # make sure we redraw when necessary.
+ Irssi::signal_add('window changed', \&uberprompt_refresh);
+ Irssi::signal_add('window name changed', \&uberprompt_refresh);
+ Irssi::signal_add('window changed automatic', \&uberprompt_refresh);
+ Irssi::signal_add('window item changed', \&uberprompt_refresh);
+
+ # install our statusbars if required.
+ if (Irssi::settings_get_bool('uberprompt_autostart')) {
+ replace_prompt_items();
+ }
+
+ # API signals
+
+ Irssi::signal_register({'change prompt' => [qw/string string/]});
+ Irssi::signal_add('change prompt' => \&change_prompt_handler);
+
+ # other scripts (specifically overlay/visual) can subscribe to
+ # this event to be notified when the prompt changes.
+ # arguments are new contents (string), new length (int)
+ Irssi::signal_register({'prompt changed' => [qw/string int/]});
+
+ if (DEBUG) {
+ Irssi::signal_add 'prompt changed', \&debug_prompt_changed;
+ }
+}
+
+sub reload_settings {
+
+ $DEBUG_ENABLED = Irssi::settings_get_bool('uberprompt_debug');
+
+ my $new = Irssi::settings_get_str('uberprompt_format');
+ if ($prompt_format ne $new) {
+ print "Updated prompt format" if DEBUG;
+ $prompt_format = $new;
+ $prompt_format =~ s/\$uber/\$\$uber/;
+ Irssi::abstracts_register(['uberprompt', $prompt_format]);
+ }
+}
+
+sub debug_prompt_changed {
+ my ($text, $len) = @_;
+
+ $text =~ s/%/%%/g;
+
+ print "DEBUG: Got $text, length: $len";
+}
+
+sub change_prompt_handler {
+ my ($text, $pos) = @_;
+
+ print "Got prompt change signal with: $text, $pos" if DEBUG;
+
+ my ($changed_text, $changed_pos);
+ $changed_text = defined $prompt_data ? $prompt_data ne $text : 1;
+ $changed_pos = defined $prompt_data_pos ? $prompt_data_pos ne $pos : 1;
+
+ $prompt_data = $text;
+ $prompt_data_pos = $pos;
+
+ if ($changed_text || $changed_pos) {
+ print "Redrawing prompt" if DEBUG;
+ uberprompt_refresh();
+ }
+}
+
+sub _escape_prompt_special {
+ my $str = shift;
+ $str =~ s/\$/\$\$/g;
+ $str =~ s/\\/\\\\/g;
+ return $str;
+}
+
+sub uberprompt_draw {
+ my ($sb_item, $get_size_only) = @_;
+
+ my $window = Irssi::active_win;
+ my $prompt_arg = '';
+
+ # default prompt sbar arguments (from config)
+ if (scalar( () = $window->items )) {
+ $prompt_arg = '$[.15]{itemname}';
+ } else {
+ $prompt_arg = '${winname}';
+ }
+
+ my $prompt = ''; # rendered content of the prompt.
+ my $theme = Irssi::current_theme;
+
+ $prompt = $theme->format_expand("{uberprompt $prompt_arg}");
+
+ if ($prompt_data_pos eq 'UP_ONLY') {
+ $prompt =~ s/\$\$uber//; # no need for recursive prompting, I hope.
+
+ # TODO: only compute this if necessary?
+ my $prompt_nt = $prompt;
+ $prompt_nt =~ s/\s+$//;
+
+ my $pdata_copy = $prompt_data;
+
+ $pdata_copy =~ s/\$prompt_nt/$prompt_nt/;
+ $pdata_copy =~ s/\$prompt/$prompt/;
+
+ $prompt = $pdata_copy;
+
+ } elsif ($prompt_data_pos eq 'UP_INNER' && defined $prompt_data) {
+ my $esc = _escape_prompt_special($prompt_data);
+ $prompt =~ s/\$\$uber/$esc/;
+
+ } else {
+ # remove the $uber marker
+ $prompt =~ s/\$\$uber//;
+
+ # and add the additional text at the appropriate position.
+ if ($prompt_data_pos eq 'UP_PRE') {
+ $prompt = $prompt_data . $prompt;
+ } elsif ($prompt_data_pos eq 'UP_POST') {
+ $prompt .= $prompt_data;
+ }
+ }
+
+ print "Redrawing with: $prompt, size-only: $get_size_only" if DEBUG;
+
+
+ if ($prompt ne $prompt_last) {
+
+ # print "Emitting prompt changed signal" if DEBUG;
+ # my $exp = Irssi::current_theme()->format_expand($text, 0);
+ my $ps = Irssi::parse_special($prompt);
+
+ Irssi::signal_emit('prompt changed', $ps, length($ps));
+ $prompt_last = $prompt;
+ }
+ my $ret = $sb_item->default_handler($get_size_only, $prompt, '', 0);
+
+ return $ret;
+}
+
+sub uberprompt_refresh {
+ Irssi::statusbar_items_redraw('uberprompt');
+}
+
+sub replace_prompt_items {
+ # remove existing ones.
+ print "Removing original prompt" if DEBUG;
+
+ _sbar_command('prompt', 'remove', 'prompt');
+ _sbar_command('prompt', 'remove', 'prompt_empty');
+
+ # add the new one.
+
+ _sbar_command('prompt', 'add', 'uberprompt',
+ qw/-alignment left -before input -priority '-1'/);
+
+ _sbar_command('prompt', 'position', '100');
+}
+
+sub restore_prompt_items {
+
+ _sbar_command('prompt', 'remove', 'uberprompt');
+
+ print "Restoring original prompt" if DEBUG;
+
+ _sbar_command('prompt', 'reset');
+}
+
+sub _sbar_command {
+ my ($bar, $cmd, $item, @args) = @_;
+
+ my $args_str = join ' ', @args;
+
+ $args_str .= ' ' if length $args_str && defined $item;
+
+ my $command = sprintf 'STATUSBAR %s %s %s%s',
+ $bar, $cmd, $args_str, defined($item)?$item:'';
+
+ print "Running command: $command" if DEBUG;
+ Irssi::command($command);
+}
+
diff --git a/prompt_info/visual.pl b/prompt_info/visual.pl
new file mode 100644
index 0000000..1413d0d
--- /dev/null
+++ b/prompt_info/visual.pl
@@ -0,0 +1,191 @@
+use strict;
+use warnings;
+
+use Irssi;
+use Irssi::TextUI; # for sbar_items_redraw
+use Data::Dumper;
+
+
+
+our $VERSION = "0.1";
+our %IRSSI =
+ (
+ authors => "shabble",
+ contact => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode',
+ name => "prompt_info",
+ description => "Helper script for dynamically adding text "
+ . "into the input-bar prompt.",
+ license => "Public Domain",
+ changed => "24/7/2010"
+ );
+
+sub DEBUG () { 1 }
+#sub DEBUG () { 0 }
+
+my $region_active = 0;
+
+my ($term_w, $term_h) = (0, 0);
+
+# visual region selected.
+my ($region_start, $region_end) = (0, 0);
+my $region_content = '';
+
+sub update_terminal_size {
+
+ my @stty_data = qx/stty -a/;
+ my $line = $stty_data[0];
+
+ # linux
+ # speed 38400 baud; rows 36; columns 126; line = 0;
+ if ($line =~ m/rows (\d+); columns (\d+);/) {
+ $term_h = $1;
+ $term_w = $2;
+ # osx
+ # speed 9600 baud; 40 rows; 235 columns;
+ } elsif ($line =~ m/(\d+) rows; (\d+) columns;/) {
+ $term_h = $1;
+ $term_w = $2;
+ } else {
+ # guess?
+ $term_h = 24;
+ $term_w = 80;
+ }
+
+ print "Terminal detected as $term_w cols by $term_h rows" if DEBUG;
+}
+
+sub visual_subcmd_handler {
+ my ($data, $server, $item) = @_;
+ $data =~ s/\s+$//g; # strip trailing whitespace.
+ Irssi::command_runsub('visual', $data, $server, $item);
+}
+
+sub init {
+
+ # misc faff
+ Irssi::command_bind('visual', \&visual_subcmd_handler);
+ Irssi::command_bind('visual toggle', \&cmd_toggle_visual);
+ Irssi::command_bind('visual clear', \&cmd_clear_visual);
+
+ Irssi::command("^BIND ^F /visual toggle");
+ Irssi::command("^BIND ^G /visual clear");
+
+ Irssi::command_bind 'print_test',
+ sub {
+ Irssi::gui_printtext(0, 0, '%8hello there%n');
+ };
+
+ # redraw interception
+ Irssi::signal_add_last('command redraw', \&augment_redraw);
+ Irssi::signal_add_first('gui key pressed', \&ctrl_l_intercept);
+
+ # for updating the overlay.
+ Irssi::signal_add_last ('gui key pressed', \&key_pressed);
+
+ # things to refresh the overlay for.
+ Irssi::signal_add('window changed', \&uberprompt_refresh);
+ Irssi::signal_add('window name changed', \&uberprompt_refresh);
+ Irssi::signal_add('window changed automatic', \&uberprompt_refresh);
+ Irssi::signal_add('window item changed', \&uberprompt_refresh);
+
+ Irssi::signal_add('terminal resized', \&update_terminal_size);
+
+ # so we know where the bottom line is
+ update_terminal_size();
+
+
+}
+sub cmd_clear_visual {
+ _clear_visual_region();
+ #refresh_visual_overlay();
+ Irssi::statusbar_items_redraw('input');
+}
+
+
+sub augment_redraw {
+ print "Redraw called" if DEBUG;
+ uberprompt_refresh();
+ Irssi::timeout_add_once(10, \&refresh_visual_overlay, 0);
+}
+
+
+sub cmd_toggle_visual {
+
+ $region_active = not $region_active;
+
+ if ($region_active) {
+ $region_start = _pos();
+ $region_end = 0; # reset end marker.
+ print "visual mode started at $region_start" if DEBUG;
+ } else {
+ $region_end = _pos();
+ print "Visual mode ended at $region_end" if DEBUG;
+
+ if ($region_end > $region_start) {
+ my $input = Irssi::parse_special('$L', 0, 0);
+ my $str = substr($input, $region_start, $region_end - $region_start);
+ print "Region selected: $str" if DEBUG;
+ } else {
+ print "Invalid region selection: [ $region_start - $region_end ]"
+ if DEBUG;
+ $region_start = $region_end = 0;
+ }
+ cmd_clear_visual();
+ }
+}
+
+sub ctrl_l_intercept {
+ my $key = shift;
+
+ if ($key == 12) { # C-l
+ print "C-l pressed" if DEBUG;
+ Irssi::command("redraw");
+ Irssi::signal_stop();
+ } elsif ($key == 10) { # RET
+ _clear_visual_region();
+ }
+}
+
+sub key_pressed {
+ # this handler needs to be last so the actual character is printed by irssi
+ # before we overlay on it. Otherwise things are all a bit off-by-1
+ return unless $region_active;
+
+ refresh_visual_overlay();
+}
+
+sub _clear_visual_region {
+ print "Clearing Region markers" if DEBUG;
+ $region_end = 0;
+ $region_start = 0;
+}
+
+
+sub refresh_visual_overlay {
+
+ my $end_pos = $region_end;
+ $end_pos ||= _pos(); # if not set, take current position as end.
+
+ my $len = $end_pos - $region_start;
+ return unless $len; # no point drawing an empty overlay
+
+ my $input = Irssi::parse_special('$L');
+ my $offset = $prompt_item->{size} + $region_start;
+
+ my $text = substr($input, $region_start, $len);
+
+ print "printing '$text' at $offset [$region_start, $end_pos] ($len)" if DEBUG;
+
+ $text = '%8' . $text . '%8';
+ _draw_overlay($offset, $text, $len);
+
+}
+
+sub _draw_overlay {
+ my ($offset, $text, $len) = @_;
+ Irssi::gui_printtext($offset, $term_h, $text);
+}
+
+sub _pos {
+ return Irssi::gui_input_get_pos();
+}
diff --git a/scrolled-reminder/scrolled_reminder.pl b/scrolled-reminder/scrolled_reminder.pl
index b69e3df..b1deb9c 100644
--- a/scrolled-reminder/scrolled_reminder.pl
+++ b/scrolled-reminder/scrolled_reminder.pl
@@ -1,3 +1,58 @@
+# ABOUT:
+#
+# This script attempts to prevent you from responding accidentally to long-finished
+# conversations if you have scrolled back and are not viewing the current activity
+# of the channel.
+#
+# If you attempt to send a message when you are not at the most recent point in the
+# channel buffer, it will intercept the message and offer you a menu of options
+# instead.
+#
+# USAGE:
+#
+# When scrolled up and sending a message, the subsequent prompt has the following
+# options:
+#
+# * Ctrl-C - cancel sending the message. It remains in your input line, but is not
+# sent to the channel.
+# * Ctrl-K - send the message to the channel. The input line is cleared.
+# * Space - Jump to the bottom (most recent) part of the channel buffer.
+# Unlike the first two, this does not cancel the prompt, so it allows
+# you to determine if your message is still appropriate before sending.
+#
+#
+# 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.
+#
+# 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.
+#
+
use strict;
use warnings;
@@ -7,16 +62,8 @@ use Irssi::Irc;
use Data::Dumper;
-# Everyone is permitted to copy and distribute verbatim or modified
-# copies of this license document, and changing it is allowed as long
-# as the name is changed.
-
-# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
-# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-# 0. You just DO WHAT THE FUCK YOU WANT TO.
-
-sub DEBUG () { 1 }
+my $DEBUG_ENABLED = 0;
+sub DEBUG () { $DEBUG_ENABLED }
our $VERSION = '0.01';
our %IRSSI =
@@ -27,8 +74,8 @@ our %IRSSI =
description => 'Requires confirmation to messages sent'
. 'when the current window is scrolled up',
- license => 'WTFPL; http://sam.zoy.org/wtfpl/',
- url => 'http://github.com/shabble/shab-irssi-scripts/'
+ license => 'MIT',
+ url => 'http://github.com/shabble/irssi-scripts/'
. 'tree/master/scrolled-reminder/',
);
@@ -44,8 +91,8 @@ sub script_is_loaded {
return $retval;
}
-unless (script_is_loaded('prompt_info')) {
- die "This script requires 'prompt_info' in order to work. "
+unless (script_is_loaded('uberprompt')) {
+ die "This script requires the 'uberprompt.pl' script in order to work. "
. "Please load it and try again";
} else {
scroll_reminder_init();
@@ -56,11 +103,23 @@ my $pending_input = {};
my $active;
sub scroll_reminder_init {
+
+
$permit_pending = 0;
$active = 0;
+
+ Irssi::settings_add_bool('scrollminder', 'scrollminder_debug', 0);
+
# we need to be first so we can intercept stuff.
Irssi::signal_add_first('send text', \&handle_send_text);
Irssi::signal_add_first('gui key pressed', \&handle_keypress);
+ Irssi::signal_add('setup changed' => \&setup_changed);
+
+ setup_changed();
+}
+
+sub setup_changed {
+ $DEBUG_ENABLED = Irssi::settings_get_bool('scrollminder_debug');
}
################################################################
@@ -181,5 +240,5 @@ sub set_prompt {
my $msg = shift;
# add a leading space unless we're trying to clear it entirely.
$msg = ' ' . $msg if length $msg;
- Irssi::signal_emit('change prompt', $msg);
+ Irssi::signal_emit('change prompt', $msg, 'UP_INNER');
}
diff --git a/test/irssi/config b/test/irssi/config
index 25101e6..5a7b307 100644
--- a/test/irssi/config
+++ b/test/irssi/config
@@ -165,7 +165,9 @@ aliases = {
xxx = "/dump Irssi::bindings()";
showbind = "/script exec \\%foo = Irssi::UI::bindings()\\; print \\$foo{\"\\\\$0\"}\\;";
botaway = "/away auto-away [\002BX\002-MsgLog On] (Away since 1:43 am Mon Aug 24)";
- vim = "/script load vim_mode.pl";
+ vim = "script load vim_mode.pl";
+ unvim = "/script unload vim_mode";
+ pl = "script load prompt_replace.pl";
};
statusbar = {
@@ -271,6 +273,7 @@ statusbar = {
prompt_empty = { };
input = { priority = "10"; };
};
+ position = "100";
};
promptadd = { disabled = "yes"; };
};
@@ -287,6 +290,9 @@ settings = {
autoinstall_custom_prompt = "yes";
mass_hilight_action = "/echo $nick";
mass_hilight_threshold = "0";
+ vim_mode_debug = "yes";
+ superprompt_format = "%K[%W$tag%c/%K$cumode%n$*%K]%n ";
+ uberprompt_format = "[$*] ";
};
"fe-common/core" = { bell_beeps = "yes"; };
"irc/core" = { ctcp_userinfo_reply = ""; };
@@ -302,5 +308,10 @@ keyboard = (
{ key = "^R"; id = "command"; data = "history_search_start "; },
{ key = "meta-h"; id = "command"; data = "echo moo /echo moo"; },
{ key = "meta-q"; id = "command"; data = "echo bacons"; },
- { key = "meta-c"; id = "command"; data = "echo moo ; /echo bar"; }
+ { key = "meta-c"; id = "command"; data = "echo moo ; /echo bar"; },
+ { key = "meta-l"; id = "command"; data = "visual "; },
+ { key = "meta-m"; id = "command"; data = "redraw "; },
+ { key = "^V"; id = "command"; data = "visual "; },
+ { key = "^F"; id = "command"; data = "visual toggle"; },
+ { key = "^G"; id = "command"; data = "visual clear"; }
);
diff --git a/vim-mode/TODO b/vim-mode/TODO
new file mode 100644
index 0000000..e225f3e
--- /dev/null
+++ b/vim-mode/TODO
@@ -0,0 +1,42 @@
+- fix known bugs and known todos!
+- esc is a little slow as ins->cmd mode key
+- 3"ap doesn't work but "a3p does
+- search with / and ? in history
+ first search on current line, then wrap to history
+- implement tab completion for ex-mode
+- recheck everything end of the line related
+- marks
+- text-objects
+- 2daW doesn't work at end of line, should do nothing
+- :imap
+- custom scripts (vim-like functions) which can be mapped
+ - surroundings, with an external script if somebody writes it
+- Ctrl-R = to evaluate expressions, first only simple math
+- Ctrl-R shouldn't have a timeout in insert mode
+- %
+
+ # Ignore invalid operator/char combinations.
+ #} elsif ($operator and ($char eq 'j' or $char eq 'k')) {
+ # # FIXME
+ # print "Invalid operator/char: $operator $char" if DEBUG;
+ # $skip = 1;
+
+- undo/redo positions _suck_
+- cc/dd . doesn't work
+
+- daw is not correctly displayed in status line, it shows d_a instead of da
+
+- :map ,, w
+ :map ,,, b
+ ,,, - very short pause - ,,, doesn't work; possibly because of
+ flush_pending_map().
+
+- support :map gX iINSERT TEXT<ESC> and similar stuff (suggested by estragib)
+- it would be nice if :ls somehow indicated current and alternate buffer
+ (maybe activity too?) (suggested by estragib)
+- < estragib> oh, i can't map \<something> either
+ < estragib> hehe, definitely something to do with escaping. \\ prints \
+- < estragib> there's a minimal difference to vim when yanking with yB:
+ abc def ghi jkl<ESC>bhyBP
+ < estragib> vim : abc def ghighi jkl
+ < estragib> vim-mode: abc def ghi ghi jkl
diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl
index 28e254d..6ff8c78 100644
--- a/vim-mode/vim_mode.pl
+++ b/vim-mode/vim_mode.pl
@@ -22,7 +22,7 @@
# * 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> f t F T
+# * 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
@@ -49,7 +49,9 @@
# * 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 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:
@@ -58,45 +60,64 @@
#
# Ex-mode supports (activated by : in command mode) the following commands:
#
-# * Switching buffers: :b <num> - switch to channel number
-# :b# - switch to last channel
+# * 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)
-# :bn[ext] - switch to next window
-# :bp[rev] - switch to previous window
-# * Close window: :bd[elete]
+# :[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] :di[splay] {args}
+# * 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>. Mapping ex-mode and irssi commands
-# is supported. Only default mappings can be used in {rhs}.
+# <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 w W - to remap w to work like W
+# :map gb :bnext - to map gb to call :bnext
# :map gB :bprev
-# :map <C-L> /clear # map Ctrl-L to irssi command /clear
+# :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
#
#
-# The following irssi settings are available:
+# 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 ''
+#
+# 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.
#
-# * vim_mode_utf8: support UTF-8 characters, default on
-# * vim_mode_debug: enable debug output, default off
-# * vim_mode_cmd_seq: char that when double-pressed simulates <esc>
#
# The following statusbar items are available:
#
@@ -156,6 +177,9 @@
# 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
#
#
@@ -243,6 +267,13 @@ sub C_INSERT () { 4 }
sub C_EX () { 5 }
# irssi commands
sub C_IRSSI () { 6 }
+# does nothing
+sub C_NOP () { 7 }
+
+# setting types, match irssi types as they are stored as irssi settings
+sub S_BOOL () { 0 }
+sub S_INT () { 1 }
+sub S_STR () { 2 }
# word and non-word regex, keep in sync with setup_changed()!
my $word = qr/[\w_]/o;
@@ -263,15 +294,19 @@ my $commands
repeatable => 1 },
# arrow like movement
- h => { char => 'h', func => \&cmd_h, type => C_NORMAL },
- l => { char => 'l', func => \&cmd_l, type => C_NORMAL },
- ' ' => { char => '<Space>', func => \&cmd_space, type => C_NORMAL },
+ h => { char => 'h', func => \&cmd_h, type => C_NORMAL },
+ l => { char => 'l', func => \&cmd_l, type => C_NORMAL },
+ "\x7F" => { char => '<BS>', func => \&cmd_h, type => C_NORMAL },
+ ' ' => { char => '<Space>', func => \&cmd_l, type => C_NORMAL },
# history movement
- j => { char => 'j', func => \&cmd_j, type => C_NORMAL },
- k => { char => 'k', func => \&cmd_k, type => C_NORMAL },
- gg => { char => 'gg', func => \&cmd_gg, type => C_NORMAL },
+ 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 },
+ 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 },
@@ -289,97 +324,141 @@ my $commands
ge => { char => 'ge', func => \&cmd_ge, type => C_NORMAL,
selection_needs_move_right => 1 },
W => { char => 'W', func => \&cmd_W, type => C_NORMAL },
- B => { char => 'B', func => \&cmd_B, type => C_NORMAL,
- selection_needs_move_right => 1 },
+ B => { char => 'B', func => \&cmd_B, type => C_NORMAL },
E => { char => 'E', func => \&cmd_E, type => C_NORMAL },
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 },
'$' => { char => '$', func => \&cmd_dollar, type => C_NORMAL },
# delete chars
x => { char => 'x', func => \&cmd_x, type => C_NORMAL,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
X => { char => 'X', func => \&cmd_X, type => C_NORMAL,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
+ # C_NORMAL is correct, operator c takes care of insert mode
s => { char => 's', func => \&cmd_s, type => C_NORMAL,
- repeatable => 1 }, # operator c takes care of insert mode
+ repeatable => 1, no_operator => 1 },
+ # C_NORMAL is correct, operator c takes care of insert mode
S => { char => 'S', func => \&cmd_S, type => C_NORMAL,
- repeatable => 1 }, # operator c takes care of insert mode
+ repeatable => 1, no_operator => 1 },
# insert mode
i => { char => 'i', func => \&cmd_i, type => C_INSERT,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
I => { char => 'I', func => \&cmd_I, type => C_INSERT,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
a => { char => 'a', func => \&cmd_a, type => C_INSERT,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
A => { char => 'A', func => \&cmd_A, type => C_INSERT,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
# replace
r => { char => 'r', func => \&cmd_r, type => C_NEEDSKEY,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
# paste
p => { char => 'p', func => \&cmd_p, type => C_NORMAL,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
P => { char => 'P', func => \&cmd_P, type => C_NORMAL,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
# to end of line
C => { char => 'C', func => \&cmd_C, type => C_NORMAL,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
D => { char => 'D', func => \&cmd_D, type => C_NORMAL,
- repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
# scrolling
- "\x05" => { char => '<C-E>', func => \&cmd_ctrl_d, type => C_NORMAL },
+ "\x05" => { char => '<C-E>', func => \&cmd_ctrl_d, type => C_NORMAL,
+ no_operator => 1 },
"\x04" => { char => '<C-D>', func => \&cmd_ctrl_d, type => C_NORMAL,
- needs_count => 1 },
- "\x19" => { char => '<C-Y>', func => \&cmd_ctrl_u, type => C_NORMAL },
+ needs_count => 1, no_operator => 1 },
+ "\x19" => { char => '<C-Y>', func => \&cmd_ctrl_u, type => C_NORMAL,
+ no_operator => 1 },
"\x15" => { char => '<C-U>', func => \&cmd_ctrl_u, type => C_NORMAL,
- needs_count => 1 },
- "\x06" => { char => '<C-F>', func => \&cmd_ctrl_f, type => C_NORMAL },
- "\x02" => { char => '<C-B>', func => \&cmd_ctrl_b, type => C_NORMAL },
+ needs_count => 1, no_operator => 1 },
+ "\x06" => { char => '<C-F>', func => \&cmd_ctrl_f, type => C_NORMAL,
+ no_operator => 1 },
+ "\x02" => { char => '<C-B>', func => \&cmd_ctrl_b, type => C_NORMAL,
+ no_operator => 1 },
# window switching
- "\x17j" => { char => '<C-W>j', func => \&cmd_ctrl_wj, type => C_NORMAL },
- "\x17k" => { char => '<C-W>k', func => \&cmd_ctrl_wk, type => C_NORMAL },
- "\x1e" => { char => '<C-^>', func => \&cmd_ctrl_6, type => C_NORMAL },
+ "\x17j" => { char => '<C-W>j', func => \&cmd_ctrl_wj, type => C_NORMAL,
+ no_operator => 1 },
+ "\x17k" => { char => '<C-W>k', func => \&cmd_ctrl_wk, type => C_NORMAL,
+ no_operator => 1 },
+ "\x1e" => { char => '<C-^>', func => \&cmd_ctrl_6, type => C_NORMAL,
+ no_operator => 1 },
# misc
'~' => { char => '~', func => \&cmd_tilde, type => C_NORMAL,
- repeatable => 1 },
- '"' => { char => '"', func => \&cmd_register, type => C_NEEDSKEY },
- '.' => { char => '.', type => C_NORMAL, repeatable => 1 },
+ repeatable => 1, no_operator => 1 },
+ '"' => { char => '"', func => \&cmd_register, type => C_NEEDSKEY,
+ no_operator => 1 },
+ '.' => { char => '.', type => C_NORMAL, repeatable => 1,
+ no_operator => 1 },
':' => { char => ':', type => C_NORMAL },
"\n" => { char => '<CR>', type => C_NORMAL }, # return
# undo
- 'u' => { char => 'u', func => \&cmd_undo, type => C_NORMAL },
- "\x12" => { char => '<C-R>', func => \&cmd_redo, type => C_NORMAL },
+ 'u' => { char => 'u', func => \&cmd_undo, type => C_NORMAL,
+ no_operator => 1 },
+ "\x12" => { char => '<C-R>', func => \&cmd_redo, type => C_NORMAL,
+ no_operator => 1 },
};
# All available commands in Ex-Mode.
my $commands_ex
= {
- s => { char => ':s', func => \&ex_substitute, type => C_EX },
- bnext => { char => ':bnext', func => \&ex_bnext, type => C_EX },
- bn => { char => ':bn', func => \&ex_bnext, type => C_EX },
- bprev => { char => ':bprev', func => \&ex_bprev, type => C_EX },
- bp => { char => ':bp', func => \&ex_bprev, type => C_EX },
- bdelete => { char => ':bdelete', func => \&ex_bdelete, type => C_EX },
- bd => { char => ':bd', func => \&ex_bdelete, type => C_EX },
- buffer => { char => ':buffer', func => \&ex_buffer, type => C_EX },
- b => { char => ':b', func => \&ex_buffer, type => C_EX },
- registers => { char => ':registers', func => \&ex_registers, type => C_EX },
- reg => { char => ':reg', func => \&ex_registers, type => C_EX },
- display => { char => ':display', func => \&ex_registers, type => C_EX },
- di => { char => ':di', func => \&ex_registers, type => C_EX },
- buffers => { char => ':buffer', func => \&ex_buffers, type => C_EX },
- ls => { char => ':ls', func => \&ex_buffers, type => C_EX },
- undolist => { char => ':undolist', func => \&ex_undolist, type => C_EX },
- undol => { char => ':undol', func => \&ex_undolist, type => C_EX },
- map => { char => ':map', func => \&ex_map, type => C_EX },
- source => { char => ':source', func => \&ex_source, type => C_EX },
- so => { char => ':so', func => \&ex_source, type => C_EX },
+ s => { char => ':s', func => \&ex_substitute,
+ type => C_EX },
+ bnext => { char => ':bnext', func => \&ex_bnext,
+ type => C_EX, uses_count => 1 },
+ bn => { char => ':bn', func => \&ex_bnext,
+ type => C_EX, uses_count => 1 },
+ bprev => { char => ':bprev', func => \&ex_bprev,
+ type => C_EX, uses_count => 1 },
+ bp => { char => ':bp', func => \&ex_bprev,
+ type => C_EX, uses_count => 1 },
+ bdelete => { char => ':bdelete', func => \&ex_bdelete,
+ type => C_EX, uses_count => 1 },
+ bd => { char => ':bd', func => \&ex_bdelete,
+ type => C_EX, uses_count => 1 },
+ buffer => { char => ':buffer', func => \&ex_buffer,
+ type => C_EX, uses_count => 1 },
+ b => { char => ':b', func => \&ex_buffer,
+ type => C_EX, uses_count => 1 },
+ registers => { char => ':registers', func => \&ex_registers,
+ type => C_EX },
+ reg => { char => ':reg', func => \&ex_registers,
+ type => C_EX },
+ display => { char => ':display', func => \&ex_registers,
+ type => C_EX },
+ di => { char => ':di', func => \&ex_registers,
+ type => C_EX },
+ buffers => { char => ':buffer', func => \&ex_buffers,
+ type => C_EX },
+ ls => { char => ':ls', func => \&ex_buffers,
+ type => C_EX },
+ undolist => { char => ':undolist', func => \&ex_undolist,
+ type => C_EX },
+ undol => { char => ':undol', func => \&ex_undolist,
+ type => C_EX },
+ map => { char => ':map', func => \&ex_map,
+ type => C_EX },
+ unmap => { char => ':unmap', func => \&ex_unmap,
+ type => C_EX },
+ unm => { char => ':unm', func => \&ex_unmap,
+ type => C_EX },
+ source => { char => ':source', func => \&ex_source,
+ type => C_EX },
+ so => { char => ':so', func => \&ex_source,
+ type => C_EX },
+ mkvimrc => { char => ':mkvimrc', func => \&ex_mkvimrc,
+ type => C_EX },
+ mkv => { char => ':mkv', func => \&ex_mkvimrc,
+ type => C_EX },
+ se => { char => ':se', func => \&ex_set,
+ type => C_EX },
+ set => { char => ':set', func => \&ex_set,
+ type => C_EX },
};
# MAPPINGS
@@ -395,12 +474,20 @@ foreach my $char (keys %$commands) {
# GLOBAL VARIABLES
-my $DEBUG_ENABLED = 0;
-
-sub DEBUG { $DEBUG_ENABLED }
+# all vim_mode settings, must be enabled in vim_mode_init() before usage
+my $settings
+ = {
+ # print debug output
+ debug => { type => S_BOOL, value => 0 },
+ # use UTF-8 internally for string calculations/manipulations
+ utf8 => { type => S_BOOL, value => 1 },
+ # esc-shortcut in insert mode
+ cmd_seq => { type => S_STR, value => '' },
+ # not used yet
+ max_undo_lines => { type => S_INT, value => 50 },
+ };
-# use UTF-8 internally for string calculations/manipulations
-my $utf8 = 1;
+sub DEBUG { $settings->{debug}->{value} }
# buffer to keep track of the last N keystrokes, used for Esc detection and
# insert mode mappings
@@ -502,7 +589,7 @@ sub insert_ctrl_r {
my ($key) = @_;
my $char = chr($key);
- return if not defined $registers->{$char} or not $registers->{$char};
+ return if not defined $registers->{$char} or $registers->{$char} eq '';
my $pos = _insert_at_position($registers->{$char}, 1, _input_pos());
_input_pos($pos + 1);
@@ -621,10 +708,6 @@ sub cmd_l {
$pos = _fix_input_pos($pos, $length);
return (undef, $pos);
}
-sub cmd_space {
- my ($count, $pos, $repeat) = @_;
- return cmd_l($count, $pos);
-}
# later history (down)
sub cmd_j {
@@ -657,7 +740,7 @@ sub cmd_j {
} elsif ($history_index >= 0) {
my $history = $history[$history_index];
# History is not in UTF-8!
- if ($utf8) {
+ if ($settings->{utf8}->{value}) {
$history = decode_utf8($history);
}
_input($history);
@@ -689,7 +772,7 @@ sub cmd_k {
if ($history_index >= 0) {
my $history = $history[$history_index];
# History is not in UTF-8!
- if ($utf8) {
+ if ($settings->{utf8}->{value}) {
$history = decode_utf8($history);
}
_input($history);
@@ -726,7 +809,7 @@ sub cmd_G {
my $history = $history[$history_index];
# History is not in UTF-8!
- if ($utf8) {
+ if ($settings->{utf8}->{value}) {
$history = decode_utf8($history);
}
_input($history);
@@ -1274,7 +1357,7 @@ sub cmd_P {
sub _paste_at_position {
my ($count, $pos) = @_;
- return if not $registers->{$register};
+ return if $registers->{$register} eq '';
return _insert_at_position($registers->{$register}, $count, $pos);
}
@@ -1300,6 +1383,8 @@ sub cmd_ctrl_d {
$count = $window->{height} / 2;
}
$window->view()->scroll($count);
+
+ Irssi::statusbar_items_redraw('more');
return (undef, undef);
}
sub cmd_ctrl_u {
@@ -1311,6 +1396,8 @@ sub cmd_ctrl_u {
$count = $window->{height} / 2;
}
$window->view()->scroll($count * -1);
+
+ Irssi::statusbar_items_redraw('more');
return (undef, undef);
}
sub cmd_ctrl_f {
@@ -1318,13 +1405,14 @@ sub cmd_ctrl_f {
my $window = Irssi::active_win();
$window->view()->scroll($count * $window->{height});
+
+ Irssi::statusbar_items_redraw('more');
return (undef, undef);
}
sub cmd_ctrl_b {
my ($count, $pos, $repeat) = @_;
- cmd_ctrl_f($count * -1, $pos, $repeat);
- return (undef, undef);
+ return cmd_ctrl_f($count * -1, $pos, $repeat);
}
sub cmd_ctrl_wj {
@@ -1436,19 +1524,26 @@ sub _fix_input_pos {
sub cmd_ex_command {
my $arg_str = join '', @ex_buf;
- if ($arg_str !~ /^([a-z]+)/) {
+ if ($arg_str !~ /^(\d*)?([a-z]+)/) {
return _warn("Invalid Ex-mode command!");
}
- if (not exists $commands_ex->{$1}) {
- return _warn("Ex-mode $1 doesn't exist!");
+ # Abort if command doesn't exist or used with count for unsupported
+ # commands.
+ if (not exists $commands_ex->{$2} or
+ ($1 ne '' and not $commands_ex->{$2}->{uses_count})) {
+ return _warn("Ex-mode $1$2 doesn't exist!");
}
- $commands_ex->{$1}->{func}($arg_str);
+ my $count = $1;
+ if ($count eq '') {
+ $count = undef;
+ }
+ $commands_ex->{$2}->{func}($arg_str, $count);
}
sub ex_substitute {
- my ($arg_str) = @_;
+ my ($arg_str, $count) = @_;
# :s///
if ($arg_str =~ m|^s/(.+)/(.*)/([ig]*)|) {
@@ -1484,35 +1579,83 @@ sub ex_substitute {
}
sub ex_bnext {
- Irssi::command('window next');
+ my ($arg_str, $count) = @_;
+
+ if (not defined $count) {
+ if ($arg_str =~ /^bn(?:ext)?\s(\d+)$/) {
+ $count = $1;
+ } else {
+ $count = 1;
+ }
+ }
+
+ while ($count-- > 0) {
+ Irssi::command('window next');
+ }
}
sub ex_bprev {
- Irssi::command('window previous');
+ my ($arg_str, $count) = @_;
+
+ if (not defined $count) {
+ if ($arg_str =~ /^bp(?:rev)?\s(\d+)$/) {
+ $count = $1;
+ } else {
+ $count = 1;
+ }
+ }
+
+ while ($count-- > 0) {
+ Irssi::command('window previous');
+ }
}
sub ex_bdelete {
+ my ($arg_str, $count) = @_;
+
+ if (not defined $count) {
+ if ($arg_str =~ /^bd(?:elete)?\s(\d+)$/) {
+ $count = $1;
+ }
+ }
+
+ if (defined $count) {
+ my $window = Irssi::window_find_refnum($count);
+ if (not $window) {
+ return;
+ }
+ $window->set_active();
+ }
Irssi::command('window close');
}
sub ex_buffer {
- my ($arg_str) = @_;
+ my ($arg_str, $count) = @_;
# :b[buffer] {args}
- if ($arg_str =~ m|^b(?:uffer)?\s*(.+)$|) {
+ if ($arg_str =~ m|^b(?:uffer)?\s*(.+)$| or defined $count) {
my $window;
my $item;
my $buffer = $1;
+ # :[N]:b[uffer]
+ if (defined $count) {
+ $window = Irssi::window_find_refnum($count);
# Go to window number.
- if ($buffer =~ /^[0-9]+$/) {
+ } elsif ($buffer =~ /^[0-9]+$/) {
$window = Irssi::window_find_refnum($buffer);
# Go to previous window.
} elsif ($buffer eq '#') {
Irssi::command('window last');
# Go to best regex matching window.
} else {
- my $matches = _matching_windows($buffer);
- if (scalar @$matches > 0) {
- $window = @$matches[0]->{window};
- $item = @$matches[0]->{item};
+ eval {
+ my $matches = _matching_windows($buffer);
+ if (scalar @$matches > 0) {
+ $window = @$matches[0]->{window};
+ $item = @$matches[0]->{item};
+ }
+ };
+ # Catch errors in /$buffer/ regex.
+ if ($@) {
+ _warn($@);
}
}
@@ -1528,7 +1671,7 @@ sub ex_buffer {
}
sub ex_registers {
- my ($arg_str) = @_;
+ my ($arg_str, $count) = @_;
# :reg[isters] {arg} and :di[splay] {arg}
if ($arg_str =~ /^(?:reg(?:isters)?|di(?:splay)?)(?:\s+(.+)$)?/) {
@@ -1558,18 +1701,22 @@ sub ex_registers {
}
sub ex_buffers {
+ my ($arg_str, $count) = @_;
+
Irssi::command('window list');
}
sub ex_undolist {
+ my ($arg_str, $count) = @_;
+
_print_undo_buffer();
}
sub ex_map {
- my ($arg_str) = @_;
+ my ($arg_str, $count) = @_;
# :map {lhs} {rhs}
- if ($arg_str =~ /^map (\S+) (\S.+)$/) {
+ if ($arg_str =~ /^map (\S+) (\S.*)$/) {
my $lhs = _parse_mapping($1);
my $rhs = $2;
@@ -1581,11 +1728,14 @@ sub ex_map {
my $command;
# Ex-mode command
if (index($rhs, ':') == 0) {
- $rhs = substr $rhs, 1;
- if (not exists $commands_ex->{$rhs}) {
- return _warn_ex('map', "$2 not found");
+ $rhs =~ /^:(\S+)(\s.+)?$/;
+ if (not exists $commands_ex->{$1}) {
+ return _warn_ex('map', "$rhs not found");
} else {
- $command = $commands_ex->{$rhs};
+ $command = { char => $rhs,
+ func => $commands_ex->{$1}->{func},
+ type => C_EX,
+ };
}
# Irssi command
} elsif (index($rhs, '/') == 0) {
@@ -1593,6 +1743,12 @@ sub ex_map {
func => substr($rhs, 1),
type => C_IRSSI,
};
+ # <Nop> does nothing
+ } elsif (lc $rhs eq '<nop>') {
+ $command = { char => '<Nop>',
+ func => undef,
+ type => C_NOP,
+ };
# command-mode command
} else {
$rhs = _parse_mapping($2);
@@ -1606,14 +1762,20 @@ sub ex_map {
}
add_map($lhs, $command);
- # :map
- } elsif ($arg_str eq 'map') {
+ # :map [lhs]
+ } elsif ($arg_str eq 'map' or $arg_str =~ /^map (\S+)$/) {
+ # Necessary for case insensitive matchings. lc alone won't work.
+ my $search = $1;
+ $search = '' if not defined $search;
+ $search = _parse_mapping_reverse(_parse_mapping($search));
+
my $active_window = Irssi::active_win();
foreach my $key (sort keys %$maps) {
my $map = $maps->{$key};
my $cmd = $map->{cmd};
if (defined $cmd) {
next if $map->{char} eq $cmd->{char}; # skip default mappings
+ next if $map->{char} !~ /^\Q$search\E/; # skip non-matches
$active_window->print(sprintf "%-15s %s", $map->{char},
$cmd->{char});
}
@@ -1622,6 +1784,25 @@ sub ex_map {
_warn_ex('map');
}
}
+sub ex_unmap {
+ my ($arg_str, $count) = @_;
+
+ # :unm[ap] {lhs}
+ if ($arg_str !~ /^unm(?:ap)? (\S+)$/) {
+ return _warn_ex('unmap');
+ }
+
+ my $lhs = _parse_mapping($1);
+ if (not defined $lhs) {
+ return _warn_ex('unmap', 'invalid {lhs}');
+ # Prevent unmapping of unknown or default mappings.
+ } elsif (not exists $maps->{$lhs} or not defined $maps->{$lhs}->{cmd} or
+ ($commands->{$lhs} and $maps->{$lhs}->{cmd} == $commands->{$lhs})) {
+ return _warn_ex('unmap', "$1 not found");
+ }
+
+ delete_map($lhs);
+}
sub _parse_mapping {
my ($string) = @_;
@@ -1648,6 +1829,9 @@ sub _parse_mapping_bracket {
# <CR>
} elsif ($string eq 'cr') {
$string = "\n";
+ # <BS>
+ } elsif ($string eq 'bs') {
+ $string = chr(127);
# Invalid char, return special string to recognize the error.
} else {
$string = '<invalid>';
@@ -1660,6 +1844,7 @@ sub _parse_mapping_reverse {
# Convert char to <char-name>.
$string =~ s/ /<Space>/g;
$string =~ s/\n/<CR>/g;
+ $string =~ s/\x7F/<BS>/g;
# Convert Ctrl-X to <C-X>.
$string =~ s/([\x01-\x1A])/"<C-" . chr(ord($1) + 64) . ">"/ge;
# Convert Ctrl-6 and Ctrl-^ to <C-^>.
@@ -1669,6 +1854,8 @@ sub _parse_mapping_reverse {
}
sub ex_source {
+ my ($arg_str, $count) = @_;
+
# :so[urce], but only loads the vim_moderc file at the moment
open my $file, '<', Irssi::get_irssi_dir() . '/vim_moderc' or return;
@@ -1678,7 +1865,7 @@ sub ex_source {
chomp $line;
# :map {lhs} {rhs}, keep in sync with ex_map()
- if ($line =~ /^\s*map (\S+) (\S.+)$/) {
+ if ($line =~ /^\s*map (\S+) (\S.*)$/) {
ex_map($line);
} else {
_warn_ex('source', "command not supported: $line");
@@ -1686,6 +1873,70 @@ sub ex_source {
}
}
+sub ex_mkvimrc {
+ my ($arg_str, $count) = @_;
+
+ # :mkv[imrc][!], [file] not supported
+
+ my $vim_moderc = Irssi::get_irssi_dir(). '/vim_moderc';
+ if (-f $vim_moderc and $arg_str !~ /^mkv(?:imrc)?!$/) {
+ return _warn_ex('mkvimrc', "$vim_moderc already exists");
+ }
+
+ open my $file, '>', $vim_moderc or return;
+
+ # copied from ex_map()
+ foreach my $key (sort keys %$maps) {
+ my $map = $maps->{$key};
+ my $cmd = $map->{cmd};
+ if (defined $cmd) {
+ next if $map->{char} eq $cmd->{char}; # skip default mappings
+ print $file "map $map->{char} $cmd->{char}\n";
+ }
+ }
+
+ close $file;
+}
+
+sub ex_set {
+ my ($arg_str, $count) = @_;
+
+ # :se[t] [option] [value]
+ if ($arg_str =~ /^se(?:t)?(?:\s(\S+)(?:\s(.+)$)?)?/) {
+ # :se[t] {option} {value}
+ if (defined $1 and defined $2) {
+ if (not exists $settings->{$1}) {
+ return _warn_ex('map', "setting '$1' not found");
+ }
+ my $name = $1;
+ my $value = $2;
+ # Also accept numeric values for boolean options.
+ if ($settings->{$name}->{type} == S_BOOL and
+ $value !~ /^(on|off)$/) {
+ $value = $value ? 'on' : 'off';
+ }
+ Irssi::command("set vim_mode_$name $value");
+
+ # :se[t] [option]
+ } else {
+ my $search = defined $1 ? $1 : '';
+ my $active_window = Irssi::active_win();
+ foreach my $setting (sort keys %$settings) {
+ next if $setting !~ /^\Q$search\E/; # skip non-matches
+ my $value = $settings->{$setting}->{value};
+ # Irssi only accepts 'on' and 'off' as values for boolean
+ # options.
+ if ($settings->{$setting}->{type} == S_BOOL) {
+ $value = $value ? 'on' : 'off';
+ }
+ $active_window->print($setting . '=' . $value);
+ }
+ }
+ } else {
+ _warn_ex('map');
+ }
+}
+
sub _warn_ex {
my ($command, $description) = @_;
my $message = "Error in ex-mode command $command";
@@ -1783,14 +2034,21 @@ sub b_windows_cb {
my $windows = '';
- # A little code duplication of cmd_ex_command()!
+ # A little code duplication of cmd_ex_command(), but \s+ instead of \s* so
+ # :bd doesn't display buffers matching d.
my $arg_str = join '', @ex_buf;
- if ($arg_str =~ m|^b(?:uffer)?\s*(.+)$|) {
+ if ($arg_str =~ m|^b(?:uffer)?\s+(.+)$|) {
my $buffer = $1;
if ($buffer !~ /^[0-9]$/ and $buffer ne '#') {
# Display matching windows.
- my $matches = _matching_windows($buffer);
- $windows = join ',', map { $_->{text} } @$matches;
+ eval {
+ my $matches = _matching_windows($buffer);
+ $windows = join ',', map { $_->{text} } @$matches;
+ };
+ # Catch errors in /$buffer/ regex.
+ if ($@) {
+ _warn($@);
+ }
}
}
@@ -1945,6 +2203,7 @@ sub flush_pending_map {
$pending_map ne $old_pending_map;
handle_command_cmd(undef);
+ Irssi::statusbar_items_redraw("vim_mode");
}
sub handle_numeric_prefix {
@@ -1975,8 +2234,8 @@ sub handle_command_cmd {
}
# Counts
- if (!$movement and ($char =~ m/[1-9]/ or
- ($numeric_prefix && $char =~ m/[0-9]/))) {
+ if (!$movement and !$pending_map and
+ ($char =~ m/[1-9]/ or ($numeric_prefix && $char =~ m/[0-9]/))) {
print "Processing numeric prefix: $char" if DEBUG;
handle_numeric_prefix($char);
return 1; # call _stop()
@@ -2028,16 +2287,26 @@ sub handle_command_cmd {
return 1; # call _stop()
}
- # Ex-mode commands can also be bound in command mode. Works only if the
- # ex-mode command doesn't need any additional arguments.
+ # Ex-mode commands can also be bound in command mode.
if ($cmd->{type} == C_EX) {
print "Processing ex-command: $map->{char} ($cmd->{char})" if DEBUG;
- $cmd->{func}->($cmd->{char});
+
+ $cmd->{func}->(substr($cmd->{char}, 1), $numeric_prefix);
+ $numeric_prefix = undef;
+
return 1; # call _stop()
# As can irssi commands.
} elsif ($cmd->{type} == C_IRSSI) {
print "Processing irssi-command: $map->{char} ($cmd->{char})" if DEBUG;
Irssi::command($cmd->{func});
+
+ $numeric_prefix = undef;
+ return 1; # call _stop();
+ # <Nop> does nothing.
+ } elsif ($cmd->{type} == C_NOP) {
+ print "Processing <Nop>: $map->{char}" if DEBUG;
+
+ $numeric_prefix = undef;
return 1; # call _stop();
}
@@ -2059,8 +2328,11 @@ sub handle_command_cmd {
if ($operator) {
# But allow cc/dd/yy.
if ($operator == $cmd) {
- print "Processing line operator: $map->{char} ($cmd->{char})"
+ print "Processing line operator: ",
+ $map->{char}, " (",
+ $cmd->{char} ,")"
if DEBUG;
+
my $pos = _input_pos();
$cmd->{func}->(0, _input_len(), undef, 0);
# Restore position for yy.
@@ -2082,8 +2354,8 @@ sub handle_command_cmd {
# Start Ex mode.
} elsif ($cmd == $commands->{':'}) {
- if (not script_is_loaded('prompt_info')) {
- _warn("Warning: Ex mode requires the 'prompt_info' script. " .
+ if (not script_is_loaded('uberprompt')) {
+ _warn("Warning: Ex mode requires the 'uberprompt' script. " .
"Please load it and try again.");
} else {
_update_mode(M_EX);
@@ -2120,6 +2392,13 @@ sub handle_command_cmd {
}
}
+ # Ignore invalid operator/command combinations.
+ if ($operator and $cmd->{no_operator}) {
+ print "Invalid operator/command: $operator->{char} $cmd->{char}"
+ if DEBUG;
+ $skip = 1;
+ }
+
if ($skip) {
print "Skipping movement and operator." if DEBUG;
} else {
@@ -2269,38 +2548,40 @@ sub vim_mode_init {
sub setup_changed {
my $value;
- # Delete all possible imaps created by /set vim_mode_cmd_seq.
- foreach my $char ('a' .. 'z') {
- delete $imaps->{$char};
+ if ($settings->{cmd_seq}->{value} ne '') {
+ delete $imaps->{$settings->{cmd_seq}->{value}};
}
-
$value = Irssi::settings_get_str('vim_mode_cmd_seq');
- if ($value) {
+ if ($value eq '') {
+ $settings->{cmd_seq}->{value} = $value;
+ } else {
if (length $value == 1) {
$imaps->{$value} = { 'map' => $value,
'func' => sub { _update_mode(M_CMD) }
};
+ $settings->{cmd_seq}->{value} = $value;
} else {
_warn("Error: vim_mode_cmd_seq must be a single character");
}
}
- $DEBUG_ENABLED = Irssi::settings_get_bool('vim_mode_debug');
+ $settings->{debug}->{value} = Irssi::settings_get_bool('vim_mode_debug');
my $new_utf8 = Irssi::settings_get_bool('vim_mode_utf8');
-
- if ($new_utf8 != $utf8) {
+ if ($new_utf8 != $settings->{utf8}->{value}) {
# recompile the patterns when switching to/from utf-8
$word = qr/[\w_]/o;
$non_word = qr/[^\w_\s]/o;
- }
+ $settings->{utf8}->{value} = $new_utf8;
+ }
if ($new_utf8 and (!$^V or $^V lt v5.8.1)) {
_warn("Warning: UTF-8 isn't supported very well in perl < 5.8.1! " .
"Please disable the vim_mode_utf8 setting.");
}
- $utf8 = $new_utf8;
+ $settings->{max_undo_lines}->{value}
+ = Irssi::settings_get_int('vim_mode_max_undo_lines');
}
sub UNLOAD {
@@ -2333,7 +2614,7 @@ sub _add_undo_entry {
unshift @undo_buffer, [$line, $pos];
$undo_index = 0;
}
- my $max = Irssi::settings_get_int('vim_mode_max_undo_lines');
+ my $max = $settings->{max_undo_lines}->{value};
}
sub _restore_undo_entry {
@@ -2408,6 +2689,45 @@ sub add_map {
$maps->{$keys}->{char} = _parse_mapping_reverse($keys);
$maps->{$keys}->{cmd} = $command;
}
+sub delete_map {
+ my ($keys) = @_;
+
+ # Abort for non-existent mappings or placeholder mappings.
+ return if not exists $maps->{$keys} or not defined $maps->{$keys}->{cmd};
+
+ my @add = ();
+
+ # If no maps need the current key, then remove it and all other
+ # unnecessary keys in the "tree".
+ if (keys %{$maps->{$keys}->{maps}} == 0) {
+ my $tmp = $keys;
+ while (length $tmp > 1) {
+ my $map = substr $tmp, -1, 1, '';
+ delete $maps->{$tmp}->{maps}->{$tmp . $map};
+ if (not $maps->{$tmp}->{cmd} and keys %{$maps->{$tmp}->{maps}} == 0) {
+ push @add, $tmp;
+ delete $maps->{$tmp};
+ } else {
+ last;
+ }
+ }
+ }
+
+ if (keys %{$maps->{$keys}->{maps}} > 0) {
+ $maps->{$keys}->{cmd} = undef;
+ } else {
+ delete $maps->{$keys};
+ }
+ push @add, $keys;
+
+ # Restore default keybindings in case we :unmaped a <Nop> or a remapped
+ # key.
+ foreach my $key (@add) {
+ if (exists $commands->{$key}) {
+ add_map($key, $commands->{$key});
+ }
+ }
+}
sub _commit_line {
@@ -2420,12 +2740,12 @@ sub _input {
my $current_data = Irssi::parse_special('$L', 0, 0);
- if ($utf8) {
+ if ($settings->{utf8}->{value}) {
$current_data = decode_utf8($current_data);
}
if (defined $data) {
- if ($utf8) {
+ if ($settings->{utf8}->{value}) {
Irssi::gui_input_set(encode_utf8($data));
} else {
Irssi::gui_input_set($data);
@@ -2535,7 +2855,7 @@ sub _set_prompt {
my $msg = shift;
# add a leading space unless we're trying to clear it entirely.
$msg = ' ' . $msg if length $msg;
- Irssi::signal_emit('change prompt', $msg);
+ Irssi::signal_emit('change prompt', $msg, 'UP_INNER');
}
sub _warn {