aboutsummaryrefslogtreecommitdiffstats
path: root/vim-mode
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vim-mode/vim_mode.pl176
1 files changed, 144 insertions, 32 deletions
diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl
index 273ff93..7503aea 100644
--- a/vim-mode/vim_mode.pl
+++ b/vim-mode/vim_mode.pl
@@ -23,7 +23,6 @@
# * /,?,n to search through history (like history_search.pl)
# * u = undo (how many levels, branching?!) redo?
# * use irssi settings for some of the features (esp. debug)
-# * history movement should keep track of the 'active' input line and restore it
# Known bugs:
# * count with insert mode: 3iabc<esc> doesn't work
@@ -83,10 +82,11 @@ my $non_word = '[^a-zA-Z0-9_\s]';
# GLOBAL VARIABLES
-# buffer to keep track of the last N keystrokes following an Esc character.
-my @esc_buf;
-my $esc_buf_timer;
-my $esc_buf_enabled = 0;
+# buffer to keep track of the last N keystrokes, used for Esc detection and
+# insert mode mappings
+my @input_buf;
+my $input_buf_timer;
+my $input_buf_enabled = 0;
# flag to allow us to emulate keystrokes without re-intercepting them
my $should_ignore = 0;
@@ -106,18 +106,28 @@ my $last
'char' => undef,
'numeric_prefix' => undef,
'operator' => undef,
- 'movement' => undef
+ 'movement' => undef,
+ 'register' => undef,
};
# what Vi mode we're in. We start in insert mode.
my $mode = M_INS;
-# vi registers, at the moment only the default yank register (") is used
+# current active register
+my $register = '"';
+
+# vi registers, " is the default register
my $registers
= {
'"' => ''
};
+# current imap still pending (first character entered)
+my $imap = undef;
+
+# maps for insert mode
+my $imaps = {};
+
# index into the history list (for j,k)
my $history_index = undef;
# current line, necessary for j,k or the current input line gets destroyed
@@ -184,6 +194,7 @@ my $movements
'$' => { 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 },
@@ -202,6 +213,7 @@ my $movements
# misc
'~' => { func => \&cmd_movement_tilde },
'.' => {},
+ '"' => { func => \&cmd_movement_register },
# undo
'u' => { func => \&cmd_undo },
"\x12" => { func => \&cmd_redo },
@@ -216,6 +228,7 @@ my $movements_multiple =
'F' => undef,
'T' => undef,
'r' => undef,
+ '"' => undef,
};
@@ -249,9 +262,9 @@ sub cmd_operator_d {
# Remove the selected string from the input.
my $input = _input();
- $registers->{'"'} = substr $input, $pos, $length, '';
+ $registers->{$register} = substr $input, $pos, $length, '';
_input($input);
- print "Deleted: " . $registers->{'"'} if DEBUG;
+ print "Deleted into $register: " . $registers->{$register} if DEBUG;
# Move the cursor at the right position.
_input_pos($pos);
@@ -263,8 +276,8 @@ sub cmd_operator_y {
# Extract the selected string and put it in the " register.
my $input = _input();
- $registers->{'"'} = substr $input, $pos, $length;
- print "Yanked: " . $registers->{'"'} if DEBUG;
+ $registers->{$register} = substr $input, $pos, $length;
+ print "Yanked into $register: " . $registers->{$register} if DEBUG;
_input_pos($old_pos);
}
@@ -278,10 +291,10 @@ sub _get_pos_and_length {
$length *= -1;
}
- # w, x, h, l are the only movements which move one character after the
+ # w, x, X, h, l are the only movements which move one character after the
# deletion area (which is what we need), all other commands need one
# character more for correct deletion.
- if ($move ne 'w' and $move ne 'x' and $move ne 'h' and $move ne 'l') {
+ if ($move ne 'w' and $move ne 'x' and $move ne 'X' and $move ne 'h' and $move ne 'l') {
$length += 1;
}
@@ -459,6 +472,10 @@ sub _end_of_word {
$pos += $+[0] + 1;
$skipped = 1;
}
+ elsif (substr($input, $pos) =~ /^\s+/) {
+ $pos += $+[0];
+ $skipped = 1;
+ }
# We are inside a word/non-word, skip to the end of it.
if (substr($input, $pos) =~ /^$word{2,}/ or
substr($input, $pos) =~ /^$non_word{2,}/) {
@@ -555,6 +572,15 @@ sub cmd_movement_x {
cmd_operator_d($pos, $pos + $count, 'x');
}
+sub cmd_movement_X {
+ my ($count, $pos) = @_;
+
+ return if $pos == 0;
+
+ my $new = $pos - $count;
+ $new = 0 if $new < 0;
+ cmd_operator_d($pos, $new, 'X');
+}
sub cmd_movement_i {
_update_mode(M_INS);
@@ -592,9 +618,9 @@ sub cmd_movement_P {
sub _paste_at_position {
my ($count, $pos) = @_;
- return if not $registers->{'"'};
+ return if not $registers->{$register};
- my $string = $registers->{'"'} x $count;
+ my $string = $registers->{$register} x $count;
my $input = _input();
# Check if we are not at the end of the line to prevent substr outside of
@@ -635,6 +661,13 @@ sub cmd_movement_tilde {
_input_pos($pos + $count);
}
+sub cmd_movement_register {
+ my ($count, $pos, $char) = @_;
+
+ $register = $char;
+ print "Changing register to $register" if DEBUG;
+}
+
sub cmd_ex_command {
my $arg_str = join '', @ex_buf;
if ($arg_str =~ m|s/(.+)/(.*)/([ig]*)|) {
@@ -678,8 +711,11 @@ sub vim_mode_cb {
$mode_str = '%_Ex%_';
} else {
$mode_str = '%_Command%_';
- if ($numeric_prefix or $operator or $movement) {
+ if ($register ne '"' or $numeric_prefix or $operator or $movement) {
$mode_str .= ' (';
+ if ($register ne '"') {
+ $mode_str .= '"' . $register;
+ }
if ($numeric_prefix) {
$mode_str .= $numeric_prefix;
}
@@ -704,13 +740,13 @@ sub got_key {
# Esc key
if ($key == 27) {
print "Esc seen, starting buffer" if DEBUG;
- $esc_buf_enabled = 1;
+ $input_buf_enabled = 1;
# NOTE: this timeout might be too low on laggy systems, but
# it comes at the cost of keystroke latency for things that
# contain escape sequences (arrow keys, etc)
- $esc_buf_timer
- = Irssi::timeout_add_once(10, \&handle_esc_buffer, undef);
+ $input_buf_timer
+ = Irssi::timeout_add_once(10, \&handle_input_buffer, undef);
} elsif ($mode == M_INS) {
if ($key == 3) { # Ctrl-C enter command mode
@@ -722,11 +758,39 @@ sub got_key {
_commit_line(_input());
@undo_buffer = ();
$undo_index = undef;
+
+ } elsif ($input_buf_enabled and $imap) {
+ print "Imap $imap active" if DEBUG;
+ my $map = $imaps->{$imap};
+ if (chr($key) eq $map->{map}) {
+ $map->{func}();
+ # Clear the buffer so the imap is not printed.
+ @input_buf = ();
+ } else {
+ push @input_buf, $key;
+ }
+ flush_input_buffer();
+ _stop();
+ $imap = undef;
+ return;
+ } elsif (exists $imaps->{chr($key)}) {
+ print "Imap " . chr($key) . " seen, starting buffer" if DEBUG;
+
+ # start imap pending mode
+ $imap = chr($key);
+
+ $input_buf_enabled = 1;
+ push @input_buf, $key;
+ $input_buf_timer
+ = Irssi::timeout_add_once(500, \&flush_input_buffer, undef);
+
+ _stop();
+ return;
}
}
- if ($esc_buf_enabled) {
- push @esc_buf, $key;
+ if ($input_buf_enabled) {
+ push @input_buf, $key;
_stop();
return;
}
@@ -736,36 +800,57 @@ sub got_key {
}
}
-sub handle_esc_buffer {
+sub handle_input_buffer {
- Irssi::timeout_remove($esc_buf_timer);
- $esc_buf_timer = undef;
+ Irssi::timeout_remove($input_buf_timer);
+ $input_buf_timer = undef;
# see what we've collected.
- print "Esc buffer contains: ", join(", ", @esc_buf) if DEBUG;
+ print "Input buffer contains: ", join(", ", @input_buf) if DEBUG;
- if (@esc_buf == 1 && $esc_buf[0] == 27) {
+ if (@input_buf == 1 && $input_buf[0] == 27) {
print "Enter Command Mode" if DEBUG;
_update_mode(M_CMD);
+ # Reset every command mode related setting as a fallback in case
+ # something goes wrong.
+ $numeric_prefix = undef;
+ $operator = undef;
+ $movement = undef;
+ $register = '"';
+
} else {
# we need to identify what we got, and either replay it
# or pass it off to the command handler.
if ($mode == M_CMD) {
# command
- my $key_str = join '', map { chr } @esc_buf;
+ my $key_str = join '', map { chr } @input_buf;
if ($key_str =~ m/^\e\[([ABCD])/) {
print "Arrow key: $1" if DEBUG;
} else {
print "Dunno what that is." if DEBUG;
}
} else {
- _emulate_keystrokes(@esc_buf);
+ _emulate_keystrokes(@input_buf);
}
}
- @esc_buf = ();
- $esc_buf_enabled = 0;
+ @input_buf = ();
+ $input_buf_enabled = 0;
+}
+
+sub flush_input_buffer {
+ Irssi::timeout_remove($input_buf_timer);
+ $input_buf_timer = undef;
+ # see what we've collected.
+ print "Input buffer flushed" if DEBUG;
+
+ _emulate_keystrokes(@input_buf);
+
+ @input_buf = ();
+ $input_buf_enabled = 0;
+
+ $imap = undef;
}
sub handle_numeric_prefix {
@@ -867,6 +952,7 @@ sub handle_command {
}
$operator = $last->{operator};
$movement = $last->{movement};
+ $register = $last->{register};
} elsif ($char eq '.') {
$skip = 1;
}
@@ -909,12 +995,17 @@ sub handle_command {
$operators->{$operator}->{func}->($cur_pos, $new_pos, $char);
}
- # Store command, necessary for . But ignore movements only.
- if ($operator) {
+ # Store command, necessary for . But ignore movements and
+ # registers.
+ if ($operator or $char eq 'x' or $char eq 'X' or $char eq 'r'
+ or $char eq 'p' or $char eq 'P' or
+ $char eq 'C' or $char eq 'D' or
+ $char eq '~' or $char eq '"') {
$last->{char} = $char;
$last->{numeric_prefix} = $numeric_prefix;
$last->{operator} = $operator;
$last->{movement} = $movement;
+ $last->{register} = $register;
}
}
@@ -922,6 +1013,11 @@ sub handle_command {
$operator = undef;
$movement = undef;
+ if ($char ne '"' and $register ne '"') {
+ print 'Changing register to "' if DEBUG;
+ $register = '"';
+ }
+
# Start Ex mode.
} elsif ($char eq ':') {
_update_mode(M_EX);
@@ -941,7 +1037,22 @@ sub handle_command {
sub vim_mode_init {
Irssi::signal_add_first 'gui key pressed' => \&got_key;
+ Irssi::signal_add 'setup changed' => \&setup_changed;
Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb');
+
+ Irssi::settings_add_bool('vim_mode', 'vim_mode_jj', 0);
+
+ setup_changed();
+}
+
+sub setup_changed {
+ if (Irssi::settings_get_bool('vim_mode_jj')) {
+ $imaps->{j} = { 'map' => 'j',
+ 'func' => sub { _update_mode(M_CMD) }
+ };
+ } else {
+ delete $imaps->{j};
+ }
}
sub UNLOAD {
@@ -1042,6 +1153,7 @@ sub _update_mode {
$mode = $new_mode;
if ($mode == M_INS) {
$history_index = undef;
+ $register = '"';
}
Irssi::statusbar_items_redraw("vim_mode");
}