aboutsummaryrefslogtreecommitdiffstats
path: root/vim-mode
diff options
context:
space:
mode:
authorTom Feist <shabble@metavore.org>2010-10-08 19:24:22 +0000
committerTom Feist <shabble@metavore.org>2010-10-08 19:24:22 +0000
commit73885e45b779c1a05d15c2c365e02051bccb562b (patch)
treec87baaf9ebb13d699c52df3d6ea5482162ed12ed /vim-mode
parentupdated config with some handy aliases. (diff)
parentvim_mode: Fix display of 'more' statusbar when scrolling. (diff)
downloadirssi-scripts-73885e45b779c1a05d15c2c365e02051bccb562b.tar.gz
irssi-scripts-73885e45b779c1a05d15c2c365e02051bccb562b.zip
Merge remote branch 'origin/dev'
Diffstat (limited to '')
-rw-r--r--vim-mode/vim_mode.pl1124
1 files changed, 765 insertions, 359 deletions
diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl
index 3631a1d..feea4c6 100644
--- a/vim-mode/vim_mode.pl
+++ b/vim-mode/vim_mode.pl
@@ -5,6 +5,9 @@
# 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
@@ -34,13 +37,14 @@
# * Switch case: ~
# * Repeat change: .
# * Repeat ftFT: ; ,
-# * Registers: "a-"z "" "* "+ "_ (black hole)
+# * 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-D Ctrl-U Ctrl-F Ctrl-B
+# * 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
@@ -59,12 +63,34 @@
# :b <partial-channel-name>
# :b <partial-server>/<partial-channel>
# :buffer {args} (same as :b)
-# :bn - switch to next window
-# :bp - switch to previous window
-# * Close window: :bd
+# :bn[ext] - switch to next window
+# :bp[rev] - switch to previous window
+# * Close window: :bd[elete]
# * Display windows: :ls :buffers
# * Display registers: :reg[isters] :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} {rhs} - add mapping
+# * Substitute: :s/// - i and g are supported as flags, only /// can be
+# used as separator, uses Perl regex instead of
+# Vim regex
+#
+#
+# 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}.
+# Examples:
+# :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 <C-G> /window goto 1
+#
#
# The following irssi settings are available:
#
@@ -78,6 +104,19 @@
# * 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
@@ -117,6 +156,7 @@
# Known bugs:
#
# * count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does
+# * undo/redo positions are mostly wrong
#
#
# TODO:
@@ -182,14 +222,177 @@ $VERSION = "1.0.1";
# CONSTANTS
-sub M_CMD() { 1 } # command mode
-sub M_INS() { 0 } # insert mode
-sub M_EX () { 2 } # extended mode (after a :?)
+# command mode
+sub M_CMD () { 1 }
+# insert mode
+sub M_INS () { 0 }
+# extended mode (after a :?)
+sub M_EX () { 2 }
+
+# operator command
+sub C_OPERATOR () { 0 }
+# normal command, no special handling necessary
+sub C_NORMAL () { 1 }
+# command taking another key as argument
+sub C_NEEDSKEY () { 2 }
+# text-object command (i a)
+sub C_TEXTOBJECT () { 3 }
+# commands entering insert mode
+sub C_INSERT () { 4 }
+# ex-mode commands
+sub C_EX () { 5 }
+# irssi commands
+sub C_IRSSI () { 6 }
# word and non-word regex, keep in sync with setup_changed()!
my $word = qr/[\w_]/o;
my $non_word = qr/[^\w_\s]/o;
+# COMMANDS
+
+# All available commands in command mode, they are stored with a char as key,
+# but this is not necessarily the key the command is currently mapped to.
+my $commands
+ = {
+ # operators
+ c => { char => 'c', func => \&cmd_operator_c, type => C_OPERATOR,
+ repeatable => 1 },
+ d => { char => 'd', func => \&cmd_operator_d, type => C_OPERATOR,
+ repeatable => 1 },
+ y => { char => 'y', func => \&cmd_operator_y, type => C_OPERATOR,
+ 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 },
+ # 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 },
+ G => { char => 'G', func => \&cmd_G, type => C_NORMAL,
+ needs_count => 1 },
+ # char movement, take an additional parameter and use $movement
+ f => { char => 'f', func => \&cmd_f, type => C_NEEDSKEY,
+ selection_needs_move_right => 1 },
+ t => { char => 't', func => \&cmd_t, type => C_NEEDSKEY,
+ selection_needs_move_right => 1 },
+ F => { char => 'F', func => \&cmd_F, type => C_NEEDSKEY },
+ T => { char => 'T', func => \&cmd_T, type => C_NEEDSKEY },
+ ';' => { char => ';', func => \&cmd_semicolon, type => C_NORMAL },
+ ',' => { char => ',', func => \&cmd_comma, type => C_NORMAL },
+ # word movement
+ w => { char => 'w', func => \&cmd_w, type => C_NORMAL },
+ b => { char => 'b', func => \&cmd_b, type => C_NORMAL },
+ e => { char => 'e', func => \&cmd_e, type => C_NORMAL,
+ selection_needs_move_right => 1 },
+ 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 },
+ 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 },
+ # 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 },
+ X => { char => 'X', func => \&cmd_X, type => C_NORMAL,
+ repeatable => 1 },
+ s => { char => 's', func => \&cmd_s, type => C_NORMAL,
+ repeatable => 1 }, # 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
+ # insert mode
+ i => { char => 'i', func => \&cmd_i, type => C_INSERT,
+ repeatable => 1 },
+ I => { char => 'I', func => \&cmd_I, type => C_INSERT,
+ repeatable => 1 },
+ a => { char => 'a', func => \&cmd_a, type => C_INSERT,
+ repeatable => 1 },
+ A => { char => 'A', func => \&cmd_A, type => C_INSERT,
+ repeatable => 1 },
+ # replace
+ r => { char => 'r', func => \&cmd_r, type => C_NEEDSKEY,
+ repeatable => 1 },
+ # paste
+ p => { char => 'p', func => \&cmd_p, type => C_NORMAL,
+ repeatable => 1 },
+ P => { char => 'P', func => \&cmd_P, type => C_NORMAL,
+ repeatable => 1 },
+ # to end of line
+ C => { char => 'C', func => \&cmd_C, type => C_NORMAL,
+ repeatable => 1 },
+ D => { char => 'D', func => \&cmd_D, type => C_NORMAL,
+ repeatable => 1 },
+ # scrolling
+ "\x05" => { char => '<C-E>', func => \&cmd_ctrl_d, type => C_NORMAL },
+ "\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 },
+ "\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 },
+ # 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 },
+ # misc
+ '~' => { char => '~', func => \&cmd_tilde, type => C_NORMAL,
+ repeatable => 1 },
+ '"' => { char => '"', func => \&cmd_register, type => C_NEEDSKEY },
+ '.' => { char => '.', type => C_NORMAL, repeatable => 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 },
+ };
+
+# 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 },
+ };
+
+# MAPPINGS
+
+# 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});
+}
+
# GLOBAL VARIABLES
my $DEBUG_ENABLED = 0;
@@ -214,16 +417,20 @@ my $should_ignore = 0;
# ex mode buffer
my @ex_buf;
+# we are waiting for another mapped key (e.g. g pressed, but there are
+# multiple mappings like gg gE etc.)
+my $pending_map = undef;
+
# for commands like 10x
my $numeric_prefix = undef;
-# vi operators like d, c, ..
+# current operator as $command hash
my $operator = undef;
# vi movements, only used when a movement needs more than one key (like f t).
my $movement = undef;
# last vi command, used by .
my $last
= {
- 'char' => 'i', # = i to support . when loading the script
+ 'cmd' => $commands->{i}, # = i to support . when loading the script
'numeric_prefix' => undef,
'operator' => undef,
'movement' => undef,
@@ -246,6 +453,7 @@ my $register = '"';
my $registers
= {
'"' => '', # default register
+ '0' => '', # yank register
'+' => '', # contains irssi's cut buffer
'*' => '', # same
'_' => '', # black hole register, always empty
@@ -260,8 +468,8 @@ my $imap = undef;
# maps for insert mode
my $imaps
= {
- # ctrl-r, insert register
- "\x12" => { map => undef, func => \&cmd_insert_ctrl_r },
+ # CTRL-R, insert register
+ "\x12" => { map => undef, func => \&insert_ctrl_r },
};
# index into the history list (for j,k)
@@ -288,118 +496,9 @@ sub script_is_loaded {
vim_mode_init();
-# vi-operators like d, c, y
-my $operators
- = {
- 'c' => { func => \&cmd_operator_c },
- 'd' => { func => \&cmd_operator_d },
- 'y' => { func => \&cmd_operator_y },
- };
-
-# vi-moves like w,b; they move the cursor and may get combined with an
-# operator; also things like i/I are listed here, not entirely correct but
-# they work in a similar way
-#
-# Each function returns two values, an updated $cur_pos (see
-# handle_command_cmd()) and the new cursor position. If undef is returned in
-# either place, the position isn't changed.
-my $movements
- = {
- # arrow like movement
- 'h' => { func => \&cmd_movement_h },
- 'l' => { func => \&cmd_movement_l },
- ' ' => { func => \&cmd_movement_space },
- # history movement
- 'j' => { func => \&cmd_movement_j },
- 'k' => { func => \&cmd_movement_k },
- 'G' => { func => \&cmd_movement_G },
- # char movement, take an additional parameter and use $movement
- 'f' => { func => \&cmd_movement_f },
- 't' => { func => \&cmd_movement_t },
- 'F' => { func => \&cmd_movement_F },
- 'T' => { func => \&cmd_movement_T },
- ';' => { func => \&cmd_movement_semicolon },
- ',' => { func => \&cmd_movement_comma },
- # word movement
- 'w' => { func => \&cmd_movement_w },
- 'b' => { func => \&cmd_movement_b },
- 'e' => { func => \&cmd_movement_e },
- 'W' => { func => \&cmd_movement_W },
- 'B' => { func => \&cmd_movement_B },
- 'E' => { func => \&cmd_movement_E },
- # text-objects
- 'i_' => { func => \&cmd_movement_i_ },
- 'a_' => { func => \&cmd_movement_a_ },
- # line movement
- '0' => { func => \&cmd_movement_0 },
- '^' => { func => \&cmd_movement_caret },
- '$' => { func => \&cmd_movement_dollar },
- # delete chars
- 'x' => { func => \&cmd_movement_x },
- 'X' => { func => \&cmd_movement_X },
- # insert mode
- 'i' => { func => \&cmd_movement_i },
- 'I' => { func => \&cmd_movement_I },
- 'a' => { func => \&cmd_movement_a },
- 'A' => { func => \&cmd_movement_A },
- # replace mode
- 'r' => { func => \&cmd_movement_r },
- # paste
- 'p' => { func => \&cmd_movement_p },
- 'P' => { func => \&cmd_movement_P },
- # to end of line
- 'C' => { func => \&cmd_movement_dollar },
- 'D' => { func => \&cmd_movement_dollar },
- # scrolling
- "\x04" => { func => \&cmd_movement_ctrl_d }, # half screen down
- "\x15" => { func => \&cmd_movement_ctrl_u }, # half screen up
- "\x06" => { func => \&cmd_movement_ctrl_f }, # screen down
- "\x02" => { func => \&cmd_movement_ctrl_b }, # screen up
- # window switching
- "\x17" => { func => \&cmd_movement_ctrl_w },
- "\x1e" => { func => \&cmd_movement_ctrl_6 },
- # misc
- '~' => { func => \&cmd_movement_tilde },
- '.' => {},
- '"' => { func => \&cmd_movement_register },
- 'g' => { func => \&cmd_movement_g }, # g does many things
- # undo
- 'u' => { func => \&cmd_undo },
- "\x12" => { func => \&cmd_redo }, # ctrl-r
- };
-
-# special movements which take an additional key
-my $movements_multiple =
- {
- 'f' => undef,
- 't' => undef,
- 'F' => undef,
- 'T' => undef,
- 'r' => undef,
- '"' => undef,
- 'g' => undef,
- "\x17" => undef, # ctrl-w
- };
+# INSERT MODE COMMANDS
-# "movements" which can be repeated (additional to operators of course).
-my $movements_repeatable
- = {
- 'x' => undef,
- 'X' => undef,
- 'i' => undef,
- 'a' => undef,
- 'I' => undef,
- 'A' => undef,
- 'r' => undef,
- 'p' => undef,
- 'P' => undef,
- 'C' => undef,
- 'D' => undef,
- '~' => undef,
- };
-
-
-sub cmd_insert_ctrl_r {
+sub insert_ctrl_r {
my ($key) = @_;
my $char = chr($key);
@@ -410,12 +509,15 @@ sub cmd_insert_ctrl_r {
}
+# COMMAND MODE OPERATORS
+
sub cmd_operator_c {
- my ($old_pos, $new_pos, $move, $repeat) = @_;
+ my ($old_pos, $new_pos, $move_cmd, $repeat) = @_;
# Changing a word or WORD doesn't delete the last space before a word, but
# not if we are on that whitespace before the word.
- if ($move eq 'w' or $move eq 'W') {
+ if ($move_cmd and ($move_cmd == $commands->{w} or
+ $move_cmd == $commands->{W})) {
my $input = _input();
if ($new_pos - $old_pos > 1 and
substr($input, $new_pos - 1, 1) =~ /\s/) {
@@ -423,7 +525,7 @@ sub cmd_operator_c {
}
}
- cmd_operator_d($old_pos, $new_pos, $move, $repeat, 1);
+ cmd_operator_d($old_pos, $new_pos, $move_cmd, $repeat, 1);
if (!$repeat) {
_update_mode(M_INS);
@@ -434,9 +536,9 @@ sub cmd_operator_c {
}
}
sub cmd_operator_d {
- my ($old_pos, $new_pos, $move, $repeat, $change) = @_;
+ my ($old_pos, $new_pos, $move_cmd, $repeat, $change) = @_;
- my ($pos, $length) = _get_pos_and_length($old_pos, $new_pos, $move);
+ my ($pos, $length) = _get_pos_and_length($old_pos, $new_pos, $move_cmd);
# Remove the selected string from the input.
my $input = _input();
@@ -457,9 +559,9 @@ sub cmd_operator_d {
_input_pos($pos);
}
sub cmd_operator_y {
- my ($old_pos, $new_pos, $move, $repeat) = @_;
+ my ($old_pos, $new_pos, $move_cmd, $repeat) = @_;
- my ($pos, $length) = _get_pos_and_length($old_pos, $new_pos, $move);
+ my ($pos, $length) = _get_pos_and_length($old_pos, $new_pos, $move_cmd);
# Extract the selected string and put it in the " register.
my $input = _input();
@@ -470,12 +572,21 @@ sub cmd_operator_y {
} else {
$registers->{$register} = $string;
print "Yanked into $register: ", $registers->{$register} if DEBUG;
+ if ($register eq '"') {
+ $registers->{0} = $string;
+ print "Yanked into 0: ", $registers->{0} if DEBUG;
+ }
}
- _input_pos($old_pos);
+ # Always move to the lower position.
+ if ($old_pos > $new_pos) {
+ _input_pos($new_pos);
+ } else {
+ _input_pos($old_pos);
+ }
}
sub _get_pos_and_length {
- my ($old_pos, $new_pos, $move) = @_;
+ my ($old_pos, $new_pos, $move_cmd) = @_;
my $length = $new_pos - $old_pos;
# We need a positive length and $old_pos must be smaller.
@@ -484,31 +595,25 @@ sub _get_pos_and_length {
$length *= -1;
}
- # Strip leading a_ or i_ if a text-object was used.
- if ($move =~ /^[ai]_(.)/) {
- $move = $1;
- }
-
- # Most movement commands don't move one character after the deletion area
- # (which is what we need). For those increase length to support proper
- # selection/deletion.
- if ($move ne 'w' and $move ne 'W' and $move ne 'x' and $move ne 'X' and
- $move ne 'B' and $move ne 'h' and $move ne 'l') {
+ # Some commands don't move one character after the deletion area which is
+ # necessary for all commands moving to the right. Fix it.
+ if ($move_cmd->{selection_needs_move_right}) {
$length += 1;
}
return ($old_pos, $length);
}
+# COMMAND MODE COMMANDS
-sub cmd_movement_h {
+sub cmd_h {
my ($count, $pos, $repeat) = @_;
$pos -= $count;
$pos = 0 if $pos < 0;
return (undef, $pos);
}
-sub cmd_movement_l {
+sub cmd_l {
my ($count, $pos, $repeat) = @_;
my $length = _input_len();
@@ -516,13 +621,13 @@ sub cmd_movement_l {
$pos = _fix_input_pos($pos, $length);
return (undef, $pos);
}
-sub cmd_movement_space {
+sub cmd_space {
my ($count, $pos, $repeat) = @_;
- return cmd_movement_l($count, $pos);
+ return cmd_l($count, $pos);
}
# later history (down)
-sub cmd_movement_j {
+sub cmd_j {
my ($count, $pos, $repeat) = @_;
if (Irssi::version < 20090117) {
@@ -561,7 +666,7 @@ sub cmd_movement_j {
return (undef, undef);
}
# earlier history (up)
-sub cmd_movement_k {
+sub cmd_k {
my ($count, $pos, $repeat) = @_;
if (Irssi::version < 20090117) {
@@ -592,10 +697,11 @@ sub cmd_movement_k {
}
return (undef, undef);
}
-sub cmd_movement_G {
+sub cmd_G {
my ($count, $pos, $repeat) = @_;
if (Irssi::version < 20090117) {
+ _warn("G and gg not supported in irssi < 0.8.13");
return;
}
@@ -628,8 +734,13 @@ sub cmd_movement_G {
return (undef, undef);
}
+sub cmd_gg {
+ my ($count, $pos, $repeat) = @_;
-sub cmd_movement_f {
+ return cmd_G(1, $pos, $repeat);
+}
+
+sub cmd_f {
my ($count, $pos, $repeat, $char) = @_;
$pos = _next_occurrence(_input(), $char, $count, $pos);
@@ -637,7 +748,7 @@ sub cmd_movement_f {
$last_ftFT = { type => 'f', char => $char };
return (undef, $pos);
}
-sub cmd_movement_t {
+sub cmd_t {
my ($count, $pos, $repeat, $char) = @_;
$pos = _next_occurrence(_input(), $char, $count, $pos);
@@ -648,7 +759,7 @@ sub cmd_movement_t {
$last_ftFT = { type => 't', char => $char };
return (undef, $pos);
}
-sub cmd_movement_F {
+sub cmd_F {
my ($count, $pos, $repeat, $char) = @_;
my $input = reverse _input();
@@ -660,7 +771,7 @@ sub cmd_movement_F {
$last_ftFT = { type => 'F', char => $char };
return (undef, $pos);
}
-sub cmd_movement_T {
+sub cmd_T {
my ($count, $pos, $repeat, $char) = @_;
my $input = reverse _input();
@@ -685,17 +796,17 @@ sub _next_occurrence {
return $pos;
}
-sub cmd_movement_semicolon {
+sub cmd_semicolon {
my ($count, $pos, $repeat) = @_;
return (undef, undef) if not defined $last_ftFT->{type};
(undef, $pos)
- = $movements->{$last_ftFT->{type}}
- ->{func}($count, $pos, $repeat, $last_ftFT->{char});
+ = $commands->{$last_ftFT->{type}}
+ ->{func}($count, $pos, $repeat, $last_ftFT->{char});
return (undef, $pos);
}
-sub cmd_movement_comma {
+sub cmd_comma {
my ($count, $pos, $repeat) = @_;
return (undef, undef) if not defined $last_ftFT->{type};
@@ -706,14 +817,14 @@ sub cmd_movement_comma {
$type =~ tr/ftFT/FTft/;
(undef, $pos)
- = $movements->{$type}
- ->{func}($count, $pos, $repeat, $last_ftFT->{char});
+ = $commands->{$type}
+ ->{func}($count, $pos, $repeat, $last_ftFT->{char});
# Restore type as the move functions overwrites it.
$last_ftFT->{type} = $save;
return (undef, $pos);
}
-sub cmd_movement_w {
+sub cmd_w {
my ($count, $pos, $repeat) = @_;
my $input = _input();
@@ -721,7 +832,7 @@ sub cmd_movement_w {
$pos = _fix_input_pos($pos, length $input);
return (undef, $pos);
}
-sub cmd_movement_b {
+sub cmd_b {
my ($count, $pos, $repeat) = @_;
my $input = reverse _input();
@@ -733,7 +844,7 @@ sub cmd_movement_b {
$pos = 0 if ($pos < 0);
return (undef, $pos);
}
-sub cmd_movement_e {
+sub cmd_e {
my ($count, $pos, $repeat) = @_;
my $input = _input();
@@ -741,6 +852,19 @@ sub cmd_movement_e {
$pos = _fix_input_pos($pos, length $input);
return (undef, $pos);
}
+sub cmd_ge {
+ my ($count, $pos, $repeat, $char) = @_;
+
+ my $input = reverse _input();
+ $pos = length($input) - $pos - 1;
+ $pos = 0 if ($pos < 0);
+
+ $pos = _beginning_of_word($input, $count, $pos);
+ $pos = length($input) - $pos - 1;
+ $pos = 0 if ($pos < 0);
+
+ return (undef, $pos);
+}
# Go to the beginning of $count-th word, like vi's w.
sub _beginning_of_word {
my ($input, $count, $pos) = @_;
@@ -796,7 +920,7 @@ sub _end_of_word {
return $pos;
}
-sub cmd_movement_W {
+sub cmd_W {
my ($count, $pos, $repeat) = @_;
my $input = _input();
@@ -804,27 +928,40 @@ sub cmd_movement_W {
$pos = _fix_input_pos($pos, length $input);
return (undef, $pos);
}
-sub cmd_movement_B {
+sub cmd_B {
my ($count, $pos, $repeat) = @_;
my $input = reverse _input();
$pos = _end_of_WORD($input, $count, length($input) - $pos - 1);
if ($pos == -1) {
- return cmd_movement_0();
+ return cmd_0();
} else {
return (undef, length($input) - $pos - 1);
}
}
-sub cmd_movement_E {
+sub cmd_E {
my ($count, $pos, $repeat) = @_;
$pos = _end_of_WORD(_input(), $count, $pos);
if ($pos == -1) {
- return cmd_movement_dollar();
+ return cmd_dollar();
} else {
return (undef, $pos);
}
}
+sub cmd_gE {
+ my ($count, $pos, $repeat, $char) = @_;
+
+ my $input = reverse _input();
+ $pos = _beginning_of_WORD($input, $count, length($input) - $pos - 1);
+ if ($pos == -1 or length($input) - $pos - 1 == -1) {
+ return cmd_0();
+ } else {
+ $pos = length($input) - $pos - 1;
+ }
+
+ return (undef, $pos);
+}
# Go to beginning of $count-th WORD, like vi's W.
sub _beginning_of_WORD {
my ($input, $count, $pos) = @_;
@@ -866,13 +1003,13 @@ sub _end_of_WORD {
return $pos;
}
-sub cmd_movement_i_ {
+sub cmd__i {
my ($count, $pos, $repeat, $char) = @_;
_warn("i_ not implemented yet");
return (undef, undef);
}
-sub cmd_movement_a_ {
+sub cmd__a {
my ($count, $pos, $repeat, $char) = @_;
my $cur_pos;
@@ -987,10 +1124,10 @@ sub _find_regex_before {
}
}
-sub cmd_movement_0 {
+sub cmd_0 {
return (undef, 0);
}
-sub cmd_movement_caret {
+sub cmd_caret {
my $input = _input();
my $pos;
# No whitespace at all.
@@ -1005,29 +1142,41 @@ sub cmd_movement_caret {
}
return (undef, $pos);
}
-sub cmd_movement_dollar {
+sub cmd_dollar {
my $length = _input_len();
return (undef, _fix_input_pos($length, $length));
}
-sub cmd_movement_x {
+sub cmd_x {
my ($count, $pos, $repeat) = @_;
- cmd_operator_d($pos, $pos + $count, 'x');
+ cmd_operator_d($pos, $pos + $count, $commands->{x}, $repeat);
return (undef, undef);
}
-sub cmd_movement_X {
+sub cmd_X {
my ($count, $pos, $repeat) = @_;
return (undef, undef) if $pos == 0;
my $new = $pos - $count;
$new = 0 if $new < 0;
- cmd_operator_d($pos, $new, 'X');
+ cmd_operator_d($pos, $new, $commands->{X}, $repeat);
return (undef, undef);
}
+sub cmd_s {
+ my ($count, $pos, $repeat) = @_;
-sub cmd_movement_i {
+ $operator = $commands->{c};
+ return (undef, $pos + 1);
+}
+sub cmd_S {
+ my ($count, $pos, $repeat) = @_;
+
+ $operator = $commands->{c};
+ return (0, _input_len());
+}
+
+sub cmd_i {
my ($count, $pos, $repeat) = @_;
if (!$repeat) {
@@ -1037,10 +1186,10 @@ sub cmd_movement_i {
}
return (undef, $pos);
}
-sub cmd_movement_I {
+sub cmd_I {
my ($count, $pos, $repeat) = @_;
- $pos = cmd_movement_caret();
+ $pos = cmd_caret();
if (!$repeat) {
_update_mode(M_INS);
} else {
@@ -1048,11 +1197,11 @@ sub cmd_movement_I {
}
return (undef, $pos);
}
-sub cmd_movement_a {
+sub cmd_a {
my ($count, $pos, $repeat) = @_;
- # Move after current character. Can't use cmd_movement_l() because we need
- # to mover after last character at the end of the line.
+ # Move after current character. Can't use cmd_l() because we need to mover
+ # after last character at the end of the line.
my $length = _input_len();
$pos += 1;
$pos = $length if $pos > $length;
@@ -1064,7 +1213,7 @@ sub cmd_movement_a {
}
return (undef, $pos);
}
-sub cmd_movement_A {
+sub cmd_A {
my ($count, $pos, $repeat) = @_;
$pos = _input_len();
@@ -1099,7 +1248,7 @@ sub _insert_at_position {
return $pos - 1 + length $string;
}
-sub cmd_movement_r {
+sub cmd_r {
my ($count, $pos, $repeat, $char) = @_;
my $input = _input();
@@ -1112,12 +1261,12 @@ sub cmd_movement_r {
return (undef, $pos + $count - 1);
}
-sub cmd_movement_p {
+sub cmd_p {
my ($count, $pos, $repeat) = @_;
$pos = _paste_at_position($count, $pos + 1);
return (undef, $pos);
}
-sub cmd_movement_P {
+sub cmd_P {
my ($count, $pos, $repeat) = @_;
$pos = _paste_at_position($count, $pos);
return (undef, $pos);
@@ -1129,7 +1278,20 @@ sub _paste_at_position {
return _insert_at_position($registers->{$register}, $count, $pos);
}
-sub cmd_movement_ctrl_d {
+sub cmd_C {
+ my ($count, $pos, $repeat) = @_;
+
+ $operator = $commands->{c};
+ return (undef, _input_len());
+}
+sub cmd_D {
+ my ($count, $pos, $repeat) = @_;
+
+ $operator = $commands->{d};
+ return (undef, _input_len());
+}
+
+sub cmd_ctrl_d {
my ($count, $pos, $repeat) = @_;
my $window = Irssi::active_win();
@@ -1138,9 +1300,11 @@ sub cmd_movement_ctrl_d {
$count = $window->{height} / 2;
}
$window->view()->scroll($count);
+
+ Irssi::statusbar_items_redraw('more');
return (undef, undef);
}
-sub cmd_movement_ctrl_u {
+sub cmd_ctrl_u {
my ($count, $pos, $repeat) = @_;
my $window = Irssi::active_win();
@@ -1149,43 +1313,50 @@ sub cmd_movement_ctrl_u {
$count = $window->{height} / 2;
}
$window->view()->scroll($count * -1);
+
+ Irssi::statusbar_items_redraw('more');
return (undef, undef);
}
-sub cmd_movement_ctrl_f {
+sub cmd_ctrl_f {
my ($count, $pos, $repeat) = @_;
my $window = Irssi::active_win();
$window->view()->scroll($count * $window->{height});
+
+ Irssi::statusbar_items_redraw('more');
return (undef, undef);
}
-sub cmd_movement_ctrl_b {
+sub cmd_ctrl_b {
my ($count, $pos, $repeat) = @_;
- cmd_movement_ctrl_f($count * -1, $pos, $repeat);
- return (undef, undef);
+ return cmd_ctrl_f($count * -1, $pos, $repeat);
}
-sub cmd_movement_ctrl_w {
- my ($count, $pos, $repeat, $char) = @_;
+sub cmd_ctrl_wj {
+ my ($count, $pos, $repeat) = @_;
- if ($char eq 'j') {
- while ($count -- > 0) {
- Irssi::command('window down');
- }
- } elsif ($char eq 'k') {
- while ($count -- > 0) {
- Irssi::command('window up');
- }
+ while ($count-- > 0) {
+ Irssi::command('window down');
+ }
+
+ return (undef, undef);
+}
+sub cmd_ctrl_wk {
+ my ($count, $pos, $repeat) = @_;
+
+ while ($count-- > 0) {
+ Irssi::command('window up');
}
+
return (undef, undef);
}
-sub cmd_movement_ctrl_6 {
+sub cmd_ctrl_6 {
# like :b#
Irssi::command('window last');
return (undef, undef);
}
-sub cmd_movement_tilde {
+sub cmd_tilde {
my ($count, $pos, $repeat) = @_;
my $input = _input();
@@ -1197,7 +1368,7 @@ sub cmd_movement_tilde {
return (undef, _fix_input_pos($pos + $count, length $input));
}
-sub cmd_movement_register {
+sub cmd_register {
my ($count, $pos, $repeat, $char) = @_;
if (not exists $registers->{$char} and not exists $registers->{lc $char}) {
@@ -1221,37 +1392,6 @@ sub cmd_movement_register {
return (undef, undef);
}
-sub cmd_movement_g {
- my ($count, $pos, $repeat, $char) = @_;
-
- my $input = _input();
- # ge
- if ($char eq 'e') {
- $input = reverse $input;
- $pos = length($input) - $pos - 1;
- $pos = 0 if ($pos < 0);
-
- $pos = _beginning_of_word($input, $count, $pos);
- $pos = length($input) - $pos - 1;
- $pos = 0 if ($pos < 0);
- # gE
- } elsif ($char eq 'E') {
- $input = reverse $input;
- $pos = _beginning_of_WORD($input, $count, length($input) - $pos - 1);
- if ($pos == -1 or length($input) - $pos - 1 == -1) {
- return cmd_movement_0();
- } else {
- $pos = length($input) - $pos - 1;
- }
- # gg
- } elsif ($char eq 'g') {
- cmd_movement_G(1, $pos, $repeat);
- $pos = undef;
- }
-
- return (undef, $pos);
-}
-
sub cmd_undo {
print "Undo!" if DEBUG;
@@ -1296,8 +1436,26 @@ sub _fix_input_pos {
}
+# EX MODE COMMANDS
+
sub cmd_ex_command {
my $arg_str = join '', @ex_buf;
+
+ if ($arg_str !~ /^([a-z]+)/) {
+ return _warn("Invalid Ex-mode command!");
+ }
+
+ if (not exists $commands_ex->{$1}) {
+ return _warn("Ex-mode $1 doesn't exist!");
+ }
+
+ $commands_ex->{$1}->{func}($arg_str);
+}
+
+sub ex_substitute {
+ my ($arg_str) = @_;
+
+ # :s///
if ($arg_str =~ m|^s/(.+)/(.*)/([ig]*)|) {
my ($search, $replace, $flags) = ($1, $2, $3);
print "Searching for $search, replace: $replace, flags; $flags"
@@ -1325,17 +1483,25 @@ sub cmd_ex_command {
print "New line is: $line" if DEBUG;
_input($line);
- # :bn
- } elsif ($arg_str eq 'bn') {
- Irssi::command('window next');
- # :bp
- } elsif ($arg_str eq 'bp') {
- Irssi::command('window previous');
- # :bd
- } elsif ($arg_str eq 'bd') {
- Irssi::command('window close');
+ } else {
+ _warn_ex('s');
+ }
+}
+
+sub ex_bnext {
+ Irssi::command('window next');
+}
+sub ex_bprev {
+ Irssi::command('window previous');
+}
+sub ex_bdelete {
+ Irssi::command('window close');
+}
+sub ex_buffer {
+ my ($arg_str) = @_;
+
# :b[buffer] {args}
- } elsif ($arg_str =~ m|^b(?:uffer)?\s*(.+)$|) {
+ if ($arg_str =~ m|^b(?:uffer)?\s*(.+)$|) {
my $window;
my $item;
my $buffer = $1;
@@ -1361,9 +1527,16 @@ sub cmd_ex_command {
$item->set_active();
}
}
+ } else {
+ _warn_ex('buffer');
+ }
+}
+
+sub ex_registers {
+ my ($arg_str) = @_;
# :reg[isters] {arg} and :di[splay] {arg}
- } elsif ($arg_str =~ /^(?:reg(?:isters)?|di(?:splay)?)(?:\s+(.+)$)?/) {
+ if ($arg_str =~ /^(?:reg(?:isters)?|di(?:splay)?)(?:\s+(.+)$)?/) {
my @regs;
if ($1) {
my $regs = $1;
@@ -1372,6 +1545,11 @@ sub cmd_ex_command {
} else {
@regs = keys %$registers;
}
+
+ # Update "+ and "* registers so correct values are displayed.
+ $registers->{'+'} = Irssi::parse_special('$U');
+ $registers->{'*'} = $registers->{'+'};
+
my $active_window = Irssi::active_win;
foreach my $key (sort @regs) {
next if $key eq '_'; # skip black hole
@@ -1379,14 +1557,149 @@ sub cmd_ex_command {
$active_window->print("register $key: $registers->{$key}");
}
}
- # :ls and :buffers
- } elsif ($arg_str eq 'ls' or $arg_str eq 'buffers') {
- Irssi::command('window list');
- } elsif ($arg_str eq 'undol' or $arg_str eq 'undolist') {
- _print_undo_buffer();
+ } else {
+ _warn_ex(':registers');
}
}
+sub ex_buffers {
+ Irssi::command('window list');
+}
+
+sub ex_undolist {
+ _print_undo_buffer();
+}
+
+sub ex_map {
+ my ($arg_str) = @_;
+
+ # :map {lhs} {rhs}
+ if ($arg_str =~ /^map (\S+) (\S.+)$/) {
+ my $lhs = _parse_mapping($1);
+ my $rhs = $2;
+
+ if (not defined $lhs) {
+ return _warn_ex('map', 'invalid {lhs}');
+ }
+
+ # Add new mapping.
+ 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");
+ } else {
+ $command = $commands_ex->{$rhs};
+ }
+ # Irssi command
+ } elsif (index($rhs, '/') == 0) {
+ $command = { char => $rhs,
+ func => substr($rhs, 1),
+ type => C_IRSSI,
+ };
+ # command-mode command
+ } else {
+ $rhs = _parse_mapping($2);
+ if (not defined $rhs) {
+ return _warn_ex('map', 'invalid {rhs}');
+ } elsif (not exists $commands->{$rhs}) {
+ return _warn_ex('map', "$2 not found");
+ } else {
+ $command = $commands->{$rhs};
+ }
+ }
+ add_map($lhs, $command);
+
+ # :map
+ } elsif ($arg_str eq 'map') {
+ 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
+ $active_window->print(sprintf "%-15s %s", $map->{char},
+ $cmd->{char});
+ }
+ }
+ } else {
+ _warn_ex('map');
+ }
+}
+sub _parse_mapping {
+ my ($string) = @_;
+
+ $string =~ s/<([^>]+)>/_parse_mapping_bracket($1)/ge;
+ if (index($string, '<invalid>') != -1) {
+ return undef;
+ }
+ return $string;
+}
+sub _parse_mapping_bracket {
+ my ($string) = @_;
+
+ $string = lc $string;
+
+ # <C-X>, get corresponding CTRL char.
+ if ($string =~ /^c-([a-z])$/i) {
+ $string = chr(ord($1) - 96);
+ # <C-6> and <C-^>
+ } elsif ($string =~ /^c-[6^]$/i) {
+ $string = chr(30);
+ # <Space>
+ } elsif ($string eq 'space') {
+ $string = ' ';
+ # <CR>
+ } elsif ($string eq 'cr') {
+ $string = "\n";
+ # Invalid char, return special string to recognize the error.
+ } else {
+ $string = '<invalid>';
+ }
+ return $string;
+}
+sub _parse_mapping_reverse {
+ my ($string) = @_;
+
+ # Convert char to <char-name>.
+ $string =~ s/ /<Space>/g;
+ $string =~ s/\n/<CR>/g;
+ # Convert Ctrl-X to <C-X>.
+ $string =~ s/([\x01-\x1A])/"<C-" . chr(ord($1) + 64) . ">"/ge;
+ # Convert Ctrl-6 and Ctrl-^ to <C-^>.
+ $string =~ s/\x1E/<C-^>/g;
+
+ return $string;
+}
+
+sub ex_source {
+ # :so[urce], but only loads the vim_moderc file at the moment
+
+ open my $file, '<', Irssi::get_irssi_dir() . '/vim_moderc' or return;
+
+ while (my $line = <$file>) {
+ next if $line =~ /^\s*$/ or $line =~ /^\s*"/;
+
+ chomp $line;
+ # :map {lhs} {rhs}, keep in sync with ex_map()
+ if ($line =~ /^\s*map (\S+) (\S.+)$/) {
+ ex_map($line);
+ } else {
+ _warn_ex('source', "command not supported: $line");
+ }
+ }
+}
+
+sub _warn_ex {
+ my ($command, $description) = @_;
+ my $message = "Error in ex-mode command $command";
+ if (defined $description) {
+ $message .= ": $description";
+ }
+ _warn($message);
+}
+
sub _matching_windows {
my ($buffer) = @_;
@@ -1437,6 +1750,8 @@ sub _matching_windows {
}
+# STATUS ITEMS
+
# vi mode status item.
sub vim_mode_cb {
my ($sb_item, $get_size_only) = @_;
@@ -1456,10 +1771,10 @@ sub vim_mode_cb {
$mode_str .= $numeric_prefix;
}
if ($operator) {
- $mode_str .= $operator;
+ $mode_str .= $operator->{char};
}
if ($movement) {
- $mode_str .= $movement;
+ $mode_str .= $movement->{char};
}
$mode_str .= ')';
}
@@ -1488,6 +1803,8 @@ sub b_windows_cb {
}
+# INPUT HANDLING
+
sub got_key {
my ($key) = @_;
@@ -1537,7 +1854,7 @@ sub got_key {
$input_buf_enabled = 1;
push @input_buf, $key;
$input_buf_timer
- = Irssi::timeout_add_once(500, \&flush_input_buffer, undef);
+ = Irssi::timeout_add_once(1000, \&flush_input_buffer, undef);
_stop();
return;
@@ -1547,7 +1864,7 @@ sub got_key {
} elsif ($key == 127) {
@insert_buf = ();
# All other entered characters need to be stored to allow repeat of
- # insert mode. Ignore delete and ctrl characters.
+ # insert mode. Ignore delete and control characters.
} elsif ($key > 31) {
push @insert_buf, chr($key);
}
@@ -1560,7 +1877,10 @@ sub got_key {
}
if ($mode == M_CMD) {
- handle_command_cmd($key);
+ my $should_stop = handle_command_cmd($key);
+ _stop() if $should_stop;
+ Irssi::statusbar_items_redraw("vim_mode");
+
} elsif ($mode == M_EX) {
handle_command_ex($key);
}
@@ -1620,6 +1940,18 @@ sub flush_input_buffer {
$imap = undef;
}
+sub flush_pending_map {
+ my ($old_pending_map) = @_;
+
+ print "flush_pending_map(): ", $pending_map, ' ', $old_pending_map
+ if DEBUG;
+
+ return if not defined $pending_map or
+ $pending_map ne $old_pending_map;
+
+ handle_command_cmd(undef);
+}
+
sub handle_numeric_prefix {
my ($char) = @_;
my $num = 0+$char;
@@ -1635,51 +1967,109 @@ sub handle_numeric_prefix {
sub handle_command_cmd {
my ($key) = @_;
- my $should_stop = 1;
+ my $pending_map_flushed = 0;
- my $char = chr($key);
+ my $char;
+ if (defined $key) {
+ $char = chr($key);
+ # We were called from flush_pending_map().
+ } else {
+ $char = $pending_map;
+ $key = 0;
+ $pending_map_flushed = 1;
+ }
- # We need to treat $movements_multiple specially as they need another
- # argument.
+ # Counts
+ if (!$movement 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()
+ }
+
+ if (defined $pending_map and not $pending_map_flushed) {
+ $pending_map = $pending_map . $char;
+ $char = $pending_map;
+ }
+
+ my $map;
if ($movement) {
- $movement .= $char;
+ $map = { char => $movement->{char},
+ cmd => $movement,
+ maps => {},
+ };
+
+ } elsif (exists $maps->{$char}) {
+ $map = $maps->{$char};
+
+ # We have multiple mappings starting with this key sequence.
+ if (!$pending_map_flushed and scalar keys %{$map->{maps}} > 0) {
+ if (not defined $pending_map) {
+ $pending_map = $char;
+ }
+
+ # The current key sequence has a command mapped to it, run if
+ # after a timeout.
+ if (defined $map->{cmd}) {
+ Irssi::timeout_add_once(1000, \&flush_pending_map,
+ $pending_map);
+ }
+ return 1; # call _stop()
+ }
+
+ } else {
+ print "No mapping found for $char" if DEBUG;
+ $pending_map = undef;
+ return 1; # call _stop()
}
- # s is an alias for cl.
- if (!$movement and !$operator and $char eq 's') {
- print "Changing s to cl" if DEBUG;
- $char = 'l';
- $operator = 'c';
- # S is an alias for cc.
- } elsif (!$movement and !$operator and $char eq 'S') {
- print "Changing S to cc" if DEBUG;
- $char = 'c';
- $operator = 'c';
+ $pending_map = undef;
+
+ my $cmd = $map->{cmd};
+
+ # Make sure we have a valid $cmd.
+ if (not defined $cmd) {
+ print "Bug in pending_map_flushed() $map->{char}" if DEBUG;
+ return 1; # call _stop()
}
- if (!$movement && ($char =~ m/[1-9]/ ||
- ($numeric_prefix && $char =~ m/[0-9]/))) {
- print "Processing numeric prefix: $char" if DEBUG;
- handle_numeric_prefix($char);
+ # Ex-mode commands can also be bound in command mode. Works only if the
+ # ex-mode command doesn't need any additional arguments.
+ if ($cmd->{type} == C_EX) {
+ print "Processing ex-command: $map->{char} ($cmd->{char})" if DEBUG;
+ $cmd->{func}->($cmd->{char});
+ 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});
+ return 1; # call _stop();
+ }
# text-objects (i a) are simulated with $movement
- } elsif (!$movement && (exists $movements_multiple->{$char}
- or $operator and ($char eq 'i' or $char eq 'a'))) {
- print "Processing movement: $char" if DEBUG;
- $movement = $char;
-
- } elsif (!$movement && exists $operators->{$char}) {
- print "Processing operator: $char" if DEBUG;
+ if (!$movement and ($cmd->{type} == C_NEEDSKEY or
+ ($operator and ($char eq 'i' or $char eq 'a')))) {
+ print "Processing movement: $map->{char} ($cmd->{char})" if DEBUG;
+ if ($char eq 'i') {
+ $movement = $commands->{_i};
+ } elsif ($char eq 'a') {
+ $movement = $commands->{_a};
+ } else {
+ $movement = $cmd;
+ }
+ } elsif (!$movement and $cmd->{type} == C_OPERATOR) {
+ print "Processing operator: $map->{char} ($cmd->{char})" if DEBUG;
# Abort operator if we already have one pending.
if ($operator) {
# But allow cc/dd/yy.
- if ($operator eq $char) {
- print "Processing operator: ", $operator, $char if DEBUG;
+ if ($operator == $cmd) {
+ print "Processing line operator: $map->{char} ($cmd->{char})"
+ if DEBUG;
my $pos = _input_pos();
- $operators->{$operator}->{func}->(0, _input_len(), '', 0);
+ $cmd->{func}->(0, _input_len(), undef, 0);
# Restore position for yy.
- if ($char eq 'y') {
+ if ($cmd == $commands->{y}) {
_input_pos($pos);
}
if ($register ne '"') {
@@ -1692,18 +2082,34 @@ sub handle_command_cmd {
$movement = undef;
# Set new operator.
} else {
- $operator = $char;
+ $operator = $cmd;
+ }
+
+ # Start Ex mode.
+ } elsif ($cmd == $commands->{':'}) {
+ if (not script_is_loaded('prompt_info')) {
+ _warn("Warning: Ex mode requires the 'prompt_info' script. " .
+ "Please load it and try again.");
+ } else {
+ _update_mode(M_EX);
+ _set_prompt(':');
}
- } elsif ($movement || exists $movements->{$char}) {
- print "Processing movement command: $char" if DEBUG;
+ # Enter key sends the current input line in command mode as well.
+ } elsif ($key == 10) {
+ _commit_line();
+ return 0; # don't call _stop()
+
+ } else {
+ print "Processing command: $map->{char} ($cmd->{char})" if DEBUG;
my $skip = 0;
my $repeat = 0;
if (!$movement) {
# . repeats the last command.
- if ($char eq '.' and defined $last->{char}) {
+ if ($cmd == $commands->{'.'} and defined $last->{cmd}) {
+ $cmd = $last->{cmd};
$char = $last->{char};
# If . is given a count then it replaces original count.
if (not defined $numeric_prefix) {
@@ -1713,19 +2119,9 @@ sub handle_command_cmd {
$movement = $last->{movement};
$register = $last->{register};
$repeat = 1;
- } elsif ($char eq '.') {
+ } elsif ($cmd == $commands->{'.'}) {
print '. pressed but $last->{char} not set' if DEBUG;
$skip = 1;
-
- # Ignore invalid operator/char combinations.
- } elsif ($operator and ($char eq 'j' or $char eq 'k')) {
- print "Invalid operator/char: $operator $char" if DEBUG;
- $skip = 1;
- # C and D force the matching operator
- } elsif ($char eq 'C') {
- $operator = 'c';
- } elsif ($char eq 'D') {
- $operator = 'd';
}
}
@@ -1734,9 +2130,7 @@ sub handle_command_cmd {
} else {
# Make sure count is at least 1 except for functions which need to
# know if no count was used.
- if (not $numeric_prefix and $char ne "\x04" # ctrl-d
- and $char ne "\x15" # ctrl-u
- and $char ne 'G') {
+ if (not $numeric_prefix and not $cmd->{needs_count}) {
$numeric_prefix = 1;
}
@@ -1749,20 +2143,11 @@ sub handle_command_cmd {
# Execute the movement (multiple times).
if (not $movement) {
($old_pos, $new_pos)
- = $movements->{$char}->{func}
- ->($numeric_prefix, $cur_pos, $repeat);
+ = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat);
} else {
- # Use the real movement command (like t or f) for operator
- # below.
- $char = substr $movement, 0, 1;
- # i_ and a_ represent text-objects.
- if ($char eq 'i' or $char eq 'a') {
- $char .= '_';
- }
($old_pos, $new_pos)
- = $movements->{$char}->{func}
- ->($numeric_prefix, $cur_pos, $repeat,
- substr $movement, 1);
+ = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat,
+ $char);
}
if (defined $old_pos) {
print "Changing \$cur_pos from $cur_pos to $old_pos" if DEBUG;
@@ -1777,8 +2162,8 @@ sub handle_command_cmd {
# Update input position of last undo entry so that undo/redo
# restores correct position.
if (@undo_buffer and _input() eq $undo_buffer[0]->[0] and
- ((defined $operator and $operator eq 'd') or
- exists $movements_repeatable->{$char} or $char eq '.')) {
+ ((defined $operator and $operator == $commands->{d}) or
+ $cmd->{repeatable})) {
print "Updating history position: $undo_buffer[0]->[0]"
if DEBUG;
$undo_buffer[0]->[1] = $cur_pos;
@@ -1788,29 +2173,23 @@ sub handle_command_cmd {
# But only if the movement changed the position (this prevents
# problems with e.g. f when the search string doesn't exist).
if ($operator and $cur_pos != $new_pos) {
- print "Processing operator: ", $operator if DEBUG;
- # If text-objects are used the real move character must also
- # be passed to the operator.
- my $tmp_char = $char;
- if ($char eq 'i_' or $char eq 'a_') {
- $tmp_char .= substr $movement, 1;
- }
- $operators->{$operator}->{func}->($cur_pos, $new_pos,
- $tmp_char, $repeat);
+ print "Processing operator: ", $operator->{char} if DEBUG;
+ $operator->{func}->($cur_pos, $new_pos, $cmd, $repeat);
}
# Save an undo checkpoint here for operators, all repeatable
# movements, operators and repetition.
- if ((defined $operator and $operator eq 'd') or
- exists $movements_repeatable->{$char} or $char eq '.') {
- # TODO: why do histpry entries still show up in undo
+ if ((defined $operator and $operator == $commands->{d}) or
+ $cmd->{repeatable}) {
+ # TODO: why do history entries still show up in undo
# buffer? Is avoiding the commands here insufficient?
_add_undo_entry(_input(), _input_pos());
}
# Store command, necessary for .
- if ($operator or exists $movements_repeatable->{$char}) {
+ if ($operator or $cmd->{repeatable}) {
+ $last->{cmd} = $cmd;
$last->{char} = $char;
$last->{numeric_prefix} = $numeric_prefix;
$last->{operator} = $operator;
@@ -1822,36 +2201,20 @@ sub handle_command_cmd {
# Reset the count unless we go into insert mode, _update_mode() needs
# to know it when leaving insert mode to support insert with counts
# (like 3i).
- if ($repeat or ($char ne 'i' and $char ne 'I' and $char ne 'a' and $char ne 'A')) {
+ if ($repeat or $cmd->{type} != C_INSERT) {
$numeric_prefix = undef;
}
$operator = undef;
$movement = undef;
- if ($char ne '"' and $register ne '"') {
+ if ($cmd != $commands->{'"'} and $register ne '"') {
print 'Changing register to "' if DEBUG;
$register = '"';
}
- # Start Ex mode.
- } elsif ($char eq ':') {
- if (not script_is_loaded('prompt_info')) {
- _warn("Warning: Ex mode requires the 'prompt_info' script. " .
- "Please load it and try again.");
- } else {
- _update_mode(M_EX);
- _set_prompt(':');
- }
-
- # Enter key sends the current input line in command mode as well.
- } elsif ($key == 10) {
- $should_stop = 0;
- _commit_line();
}
- Irssi::statusbar_items_redraw("vim_mode");
-
- _stop() if $should_stop;
+ return 1; # call _stop()
}
sub handle_command_ex {
@@ -1860,8 +2223,13 @@ sub handle_command_ex {
# DEL key - remove last character
if ($key == 127) {
print "Delete" if DEBUG;
- pop @ex_buf;
- _set_prompt(':' . join '', @ex_buf);
+ if (scalar @ex_buf > 0) {
+ pop @ex_buf;
+ _set_prompt(':' . join '', @ex_buf);
+ # Backspacing over : exits ex-mode.
+ } else {
+ _update_mode(M_CMD);
+ }
# Return key - execute command
} elsif ($key == 10) {
@@ -1869,6 +2237,10 @@ sub handle_command_ex {
cmd_ex_command();
_update_mode(M_CMD);
+ # Ignore control characters for now.
+ } elsif ($key < 32) {
+ # TODO: use them later, e.g. completion
+
# Append entered key
} else {
push @ex_buf, chr $key;
@@ -1892,6 +2264,9 @@ sub vim_mode_init {
Irssi::settings_add_bool('vim_mode', 'vim_mode_utf8', 1);
Irssi::settings_add_int('vim_mode', 'vim_mode_max_undo_lines', 50);
+ # Load the vim_moderc file if it exists.
+ ex_source('source');
+
setup_changed();
_reset_undo_buffer();
}
@@ -2008,6 +2383,38 @@ sub _reset_undo_buffer {
}
+sub add_map {
+ my ($keys, $command) = @_;
+
+ # To allow multiple mappings starting with the same key (like gg, ge, gE)
+ # also create maps for the keys "leading" to this key (g in this case, but
+ # can be longer for this like ,ls). When looking for the mapping these
+ # "leading" maps are followed.
+ my $tmp = $keys;
+ while (length $tmp > 1) {
+ my $map = substr $tmp, -1, 1, '';
+ if (not exists $maps->{$tmp}) {
+ $maps->{$tmp} = { char => _parse_mapping_reverse($tmp),
+ cmd => undef,
+ maps => {}
+ };
+ }
+ if (not exists $maps->{$tmp}->{maps}->{$tmp . $map}) {
+ $maps->{$tmp}->{maps}->{$tmp . $map} = undef;
+ }
+ }
+
+ if (not exists $maps->{$keys}) {
+ $maps->{$keys} = { char => undef,
+ cmd => undef,
+ maps => {}
+ };
+ }
+ $maps->{$keys}->{char} = _parse_mapping_reverse($keys);
+ $maps->{$keys}->{cmd} = $command;
+}
+
+
sub _commit_line {
_update_mode(M_INS);
_reset_undo_buffer('', 0);
@@ -2098,10 +2505,10 @@ sub _update_mode {
_add_undo_entry(_input(), _input_pos());
# Change mode to i to support insert mode repetition. This doesn't affect
- # commands like i/a/I/A because handle_command_cmd() sets $last->{char}.
- # It's necessary when pressing enter.
+ # commands like i/a/I/A because handle_command_cmd() sets $last->{cmd}.
+ # It's necessary when pressing enter so the next line can be repeated.
} elsif ($mode == M_CMD and $new_mode == M_INS) {
- $last->{char} = 'i';
+ $last->{cmd} = $commands->{i};
# Make sure prompt is cleared when leaving ex mode.
} elsif ($mode == M_EX and $new_mode != M_EX) {
_set_prompt('');
@@ -2120,6 +2527,8 @@ sub _update_mode {
$movement = undef;
$register = '"';
+ $pending_map = undef;
+
# Also clear ex-mode buffer.
@ex_buf = ();
}
@@ -2139,6 +2548,3 @@ sub _warn {
print '%_vim_mode: ', $warning, '%_';
}
-
-# TODO:
-# 10gg -> go to window 10 (prefix.gg -> win <prefix>)