aboutsummaryrefslogtreecommitdiffstats
path: root/vim-mode/vim_mode.pl
diff options
context:
space:
mode:
authorTom Feist <shabble@metavore.org>2011-05-04 23:59:27 +0000
committerTom Feist <shabble@metavore.org>2011-05-04 23:59:27 +0000
commitbb8a56ba284990ed02909fc025090f2804197c59 (patch)
tree079fa1e8b3e3005ae83bad2701522ef81d5e3735 /vim-mode/vim_mode.pl
parentadded colour_test for testing implementations of 256-colour patches (diff)
downloadirssi-scripts-bb8a56ba284990ed02909fc025090f2804197c59.tar.gz
irssi-scripts-bb8a56ba284990ed02909fc025090f2804197c59.zip
vim-mode: new branch for fixing input code. Move it all to single location (end
of file), and rename got_key handler to something standard (sig_gui_keypress)
Diffstat (limited to '')
-rw-r--r--vim-mode/vim_mode.pl1040
1 files changed, 524 insertions, 516 deletions
diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl
index ee8ba2a..26887b7 100644
--- a/vim-mode/vim_mode.pl
+++ b/vim-mode/vim_mode.pl
@@ -2694,521 +2694,6 @@ sub b_windows_cb {
}
-# INPUT HANDLING
-
-sub got_key {
- my ($key) = @_;
-
- return if ($should_ignore);
-
- # Esc key
- if ($key == 27) {
- print "Esc seen, starting buffer" if DEBUG;
- $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)
- my $esc_buf_timeout = $settings->{esc_buf_timeout}->{value};
-
- $input_buf_timer
- = Irssi::timeout_add_once($esc_buf_timeout,
- \&handle_input_buffer, undef);
-
- print "Buffer Timer tag: $input_buf_timer" if DEBUG;
-
- } elsif ($mode == M_INS) {
-
- if ($key == 3) { # Ctrl-C enters command mode
- _update_mode(M_CMD);
- _stop();
- return;
-
- } elsif ($key == 10) { # enter.
- _commit_line();
-
- } elsif ($input_buf_enabled and $imap) {
- print "Imap $imap active" if DEBUG;
- my $map = $imaps->{$imap};
- if (not defined $map->{map} or chr($key) eq $map->{map}) {
- $map->{func}($key);
- # 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(1000, \&flush_input_buffer, undef);
-
- _stop();
- return;
-
- # Pressing delete resets insert mode repetition (8 = BS, 127 = DEL).
- # TODO: maybe allow it
- } elsif ($key == 8 || $key == 127) {
- @insert_buf = ();
- # All other entered characters need to be stored to allow repeat of
- # insert mode. Ignore delete and control characters.
- } elsif ($key > 31) {
- push @insert_buf, chr($key);
- }
- }
-
- if ($input_buf_enabled) {
- push @input_buf, $key;
- _stop();
- return;
- }
-
- if ($mode == M_CMD) {
- my $should_stop = handle_command_cmd($key);
- _stop() if $should_stop;
- Irssi::statusbar_items_redraw("vim_mode");
-
- } elsif ($mode == M_EX) {
-
- if ($key == 3) { # C-c cancels Ex mdoe as well.
- _update_mode(M_CMD);
- _stop();
- return;
- }
-
- handle_command_ex($key);
- }
-}
-
-# TODO: merge this with 'flush_input_buffer' below.
-
-sub handle_input_buffer {
-
- #Irssi::timeout_remove($input_buf_timer);
- $input_buf_timer = undef;
- # see what we've collected.
- print "Input buffer contains: ", join(", ", @input_buf) if DEBUG;
-
- if (@input_buf == 1 && $input_buf[0] == 27) {
-
- print "Enter Command Mode" if DEBUG;
- _update_mode(M_CMD);
-
- } else {
- # we have more than a single esc, implying an escape sequence
- # (meta-* or esc-*)
-
- # currently, we only extract escape sequences if:
- # a) we're in ex mode
- # b) they're arrow keys (for history control)
-
- if ($mode == M_EX) {
- # ex mode
- my $key_str = join '', map { chr } @input_buf;
- if ($key_str =~ m/^\e\[([ABCD])/) {
- my $arrow = $1;
- _debug( "Arrow key: $arrow");
- if ($arrow eq 'A') { # up
- ex_history_back();
- } elsif ($arrow eq 'B') { # down
- ex_history_fwd();
- } else {
- $arrow =~ s/C/right/;
- $arrow =~ s/D/left/;
- _debug("Arrow key $arrow not supported");
- }
- }
- } else {
- # otherwise, we just forward them to irssi.
- _emulate_keystrokes(@input_buf);
- }
-
- # Clear insert buffer, pressing "special" keys (like arrow keys)
- # resets it.
- @insert_buf = ();
- }
-
- @input_buf = ();
- $input_buf_enabled = 0;
-}
-
-sub flush_input_buffer {
- Irssi::timeout_remove($input_buf_timer) if defined $input_buf_timer;
- $input_buf_timer = undef;
- # see what we've collected.
- print "Input buffer flushed" if DEBUG;
-
- # Add the characters to @insert_buf so they can be repeated.
- push @insert_buf, map chr, @input_buf;
-
- _emulate_keystrokes(@input_buf);
-
- @input_buf = ();
- $input_buf_enabled = 0;
-
- $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);
- Irssi::statusbar_items_redraw("vim_mode");
-}
-
-sub handle_numeric_prefix {
- my ($char) = @_;
- my $num = 0+$char;
-
- if (defined $numeric_prefix) {
- $numeric_prefix *= 10;
- $numeric_prefix += $num;
- } else {
- $numeric_prefix = $num;
- }
-}
-
-sub handle_command_cmd {
- my ($key) = @_;
-
- my $pending_map_flushed = 0;
-
- 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;
- }
-
- # Counts
- 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()
- }
-
- if (defined $pending_map and not $pending_map_flushed) {
- $pending_map = $pending_map . $char;
- $char = $pending_map;
- }
-
- my $map;
- if ($movement) {
- $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;
- $numeric_prefix = undef;
- return 1; # call _stop()
- }
-
- $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()
- }
-
- # 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}->(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;
-
- _command_with_context($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();
- }
-
- # text-objects (i a) are simulated with $movement
- 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 == $cmd) {
- 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.
- if ($cmd == $commands->{y}) {
- _input_pos($pos);
- # And save undo for other operators.
- } else {
- _add_undo_entry(_input(), _input_pos());
- }
- if ($register ne '"') {
- print 'Changing register to "' if DEBUG;
- $register = '"';
- }
- }
- $numeric_prefix = undef;
- $operator = undef;
- $movement = undef;
- # Set new operator.
- } else {
- $operator = $cmd;
- }
-
- # Start Ex mode.
- } elsif ($cmd == $commands->{':'}) {
-
- 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);
- _set_prompt(':');
- }
-
- # 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 ($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) {
- $numeric_prefix = $last->{numeric_prefix};
- }
- $operator = $last->{operator};
- $movement = $last->{movement};
- $register = $last->{register};
- $repeat = 1;
- } elsif ($cmd == $commands->{'.'}) {
- print '. pressed but $last->{char} not set' if DEBUG;
- $skip = 1;
- }
- }
-
- # 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 {
- # Make sure count is at least 1 except for functions which need to
- # know if no count was used.
- if (not $numeric_prefix and not $cmd->{needs_count}) {
- $numeric_prefix = 1;
- }
-
- my $cur_pos = _input_pos();
-
- # If defined $cur_pos will be changed to this.
- my $old_pos;
- # Position after the move.
- my $new_pos;
- # Execute the movement (multiple times).
- if (not $movement) {
- ($old_pos, $new_pos)
- = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat);
- } else {
- ($old_pos, $new_pos)
- = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat,
- $char);
- }
- if (defined $old_pos) {
- print "Changing \$cur_pos from $cur_pos to $old_pos" if DEBUG;
- $cur_pos = $old_pos;
- }
- if (defined $new_pos) {
- _input_pos($new_pos);
- } else {
- $new_pos = _input_pos();
- }
-
- # 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 == $commands->{d}) or
- $cmd->{repeatable})) {
- print "Updating history position: $undo_buffer[0]->[0]"
- if DEBUG;
- $undo_buffer[0]->[1] = $cur_pos;
- }
-
- # If we have an operator pending then run it on the handled text.
- # 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->{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 == $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 $cmd->{repeatable}) {
- $last->{cmd} = $cmd;
- $last->{char} = $char;
- $last->{numeric_prefix} = $numeric_prefix;
- $last->{operator} = $operator;
- $last->{movement} = $movement;
- $last->{register} = $register;
- }
- }
-
- # 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 $cmd->{type} != C_INSERT) {
- $numeric_prefix = undef;
- }
- $operator = undef;
- $movement = undef;
-
- if ($cmd != $commands->{'"'} and $register ne '"') {
- print 'Changing register to "' if DEBUG;
- $register = '"';
- }
-
- }
-
- return 1; # call _stop()
-}
-
-sub handle_command_ex {
- my ($key) = @_;
-
- # BS key (8) or DEL key (127) - remove last character.
- if ($key == 8 || $key == 127) {
- print "Delete" if DEBUG;
- if (@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) {
- print "Run ex-mode command" if DEBUG;
- cmd_ex_command();
- _update_mode(M_CMD);
-
- } elsif ($key == 9) { # TAB
- print "Tab pressed" if DEBUG;
- print "Ex buf contains: " . join('', @ex_buf) if DEBUG;
- @tab_candidates = _tab_complete(join('', @ex_buf), [keys %$commands_ex]);
- _debug("Candidates: " . join(", ", @tab_candidates));
- if (@tab_candidates == 1) {
- @ex_buf = ( split('', $tab_candidates[0]), ' ');
- _set_prompt(':' . join '', @ex_buf);
- }
- # Ignore control characters for now.
- } elsif ($key > 0 && $key < 32) {
- # TODO: use them later, e.g. completion
-
- # Append entered key
- } else {
- if ($key != -1) {
- # check we're not called from an ex_history_* function
- push @ex_buf, chr $key;
- }
- _set_prompt(':' . join '', @ex_buf);
- }
-
- Irssi::statusbar_items_redraw("vim_windows");
-
- _stop();
-}
sub _tab_complete {
my ($input, $source) = @_;
@@ -3223,7 +2708,7 @@ sub _tab_complete {
}
sub vim_mode_init {
- Irssi::signal_add_first 'gui key pressed' => \&got_key;
+ Irssi::signal_add_first 'gui key pressed' => \&sig_gui_keypress;
Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb');
Irssi::statusbar_item_register ('vim_windows', 0, 'b_windows_cb');
@@ -3758,4 +3243,527 @@ sub ex_history_show {
$win->print("$i " . $ex_history[$i] . $flag);
}
}
+
+
+################################################################
+# INPUT HANDLING #
+################################################################
+
+
+sub sig_gui_keypress {
+ my ($key) = @_;
+
+ return if ($should_ignore);
+
+ # Esc key
+ if ($key == 27) {
+ print "Esc seen, starting buffer" if DEBUG;
+ $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)
+ my $esc_buf_timeout = $settings->{esc_buf_timeout}->{value};
+
+ $input_buf_timer
+ = Irssi::timeout_add_once($esc_buf_timeout,
+ \&handle_input_buffer, undef);
+
+ print "Buffer Timer tag: $input_buf_timer" if DEBUG;
+
+ } elsif ($mode == M_INS) {
+
+ if ($key == 3) { # Ctrl-C enters command mode
+ _update_mode(M_CMD);
+ _stop();
+ return;
+
+ } elsif ($key == 10) { # enter.
+ _commit_line();
+
+ } elsif ($input_buf_enabled and $imap) {
+ print "Imap $imap active" if DEBUG;
+ my $map = $imaps->{$imap};
+ if (not defined $map->{map} or chr($key) eq $map->{map}) {
+ $map->{func}($key);
+ # 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(1000, \&flush_input_buffer, undef);
+
+ _stop();
+ return;
+
+ # Pressing delete resets insert mode repetition (8 = BS, 127 = DEL).
+ # TODO: maybe allow it
+ } elsif ($key == 8 || $key == 127) {
+ @insert_buf = ();
+ # All other entered characters need to be stored to allow repeat of
+ # insert mode. Ignore delete and control characters.
+ } elsif ($key > 31) {
+ push @insert_buf, chr($key);
+ }
+ }
+
+ if ($input_buf_enabled) {
+ push @input_buf, $key;
+ _stop();
+ return;
+ }
+
+ if ($mode == M_CMD) {
+ my $should_stop = handle_command_cmd($key);
+ _stop() if $should_stop;
+ Irssi::statusbar_items_redraw("vim_mode");
+
+ } elsif ($mode == M_EX) {
+
+ if ($key == 3) { # C-c cancels Ex mdoe as well.
+ _update_mode(M_CMD);
+ _stop();
+ return;
+ }
+
+ handle_command_ex($key);
+ }
+}
+
+# TODO: merge this with 'flush_input_buffer' below.
+
+sub handle_input_buffer {
+
+ #Irssi::timeout_remove($input_buf_timer);
+ $input_buf_timer = undef;
+ # see what we've collected.
+ print "Input buffer contains: ", join(", ", @input_buf) if DEBUG;
+
+ if (@input_buf == 1 && $input_buf[0] == 27) {
+
+ print "Enter Command Mode" if DEBUG;
+ _update_mode(M_CMD);
+
+ } else {
+ # we have more than a single esc, implying an escape sequence
+ # (meta-* or esc-*)
+
+ # currently, we only extract escape sequences if:
+ # a) we're in ex mode
+ # b) they're arrow keys (for history control)
+
+ if ($mode == M_EX) {
+ # ex mode
+ my $key_str = join '', map { chr } @input_buf;
+ if ($key_str =~ m/^\e\[([ABCD])/) {
+ my $arrow = $1;
+ _debug( "Arrow key: $arrow");
+ if ($arrow eq 'A') { # up
+ ex_history_back();
+ } elsif ($arrow eq 'B') { # down
+ ex_history_fwd();
+ } else {
+ $arrow =~ s/C/right/;
+ $arrow =~ s/D/left/;
+ _debug("Arrow key $arrow not supported");
+ }
+ }
+ } else {
+ # otherwise, we just forward them to irssi.
+ _emulate_keystrokes(@input_buf);
+ }
+
+ # Clear insert buffer, pressing "special" keys (like arrow keys)
+ # resets it.
+ @insert_buf = ();
+ }
+
+ @input_buf = ();
+ $input_buf_enabled = 0;
+}
+
+sub flush_input_buffer {
+ Irssi::timeout_remove($input_buf_timer) if defined $input_buf_timer;
+ $input_buf_timer = undef;
+ # see what we've collected.
+ print "Input buffer flushed" if DEBUG;
+
+ # Add the characters to @insert_buf so they can be repeated.
+ push @insert_buf, map chr, @input_buf;
+
+ _emulate_keystrokes(@input_buf);
+
+ @input_buf = ();
+ $input_buf_enabled = 0;
+
+ $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);
+ Irssi::statusbar_items_redraw("vim_mode");
+}
+
+sub handle_numeric_prefix {
+ my ($char) = @_;
+ my $num = 0+$char;
+
+ if (defined $numeric_prefix) {
+ $numeric_prefix *= 10;
+ $numeric_prefix += $num;
+ } else {
+ $numeric_prefix = $num;
+ }
+}
+
+sub handle_command_cmd {
+ my ($key) = @_;
+
+ my $pending_map_flushed = 0;
+
+ 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;
+ }
+
+ # Counts
+ 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()
+ }
+
+ if (defined $pending_map and not $pending_map_flushed) {
+ $pending_map = $pending_map . $char;
+ $char = $pending_map;
+ }
+
+ my $map;
+ if ($movement) {
+ $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;
+ $numeric_prefix = undef;
+ return 1; # call _stop()
+ }
+
+ $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()
+ }
+
+ # 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}->(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;
+
+ _command_with_context($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();
+ }
+
+ # text-objects (i a) are simulated with $movement
+ 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 == $cmd) {
+ 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.
+ if ($cmd == $commands->{y}) {
+ _input_pos($pos);
+ # And save undo for other operators.
+ } else {
+ _add_undo_entry(_input(), _input_pos());
+ }
+ if ($register ne '"') {
+ print 'Changing register to "' if DEBUG;
+ $register = '"';
+ }
+ }
+ $numeric_prefix = undef;
+ $operator = undef;
+ $movement = undef;
+ # Set new operator.
+ } else {
+ $operator = $cmd;
+ }
+
+ # Start Ex mode.
+ } elsif ($cmd == $commands->{':'}) {
+
+ 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);
+ _set_prompt(':');
+ }
+
+ # 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 ($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) {
+ $numeric_prefix = $last->{numeric_prefix};
+ }
+ $operator = $last->{operator};
+ $movement = $last->{movement};
+ $register = $last->{register};
+ $repeat = 1;
+ } elsif ($cmd == $commands->{'.'}) {
+ print '. pressed but $last->{char} not set' if DEBUG;
+ $skip = 1;
+ }
+ }
+
+ # 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 {
+ # Make sure count is at least 1 except for functions which need to
+ # know if no count was used.
+ if (not $numeric_prefix and not $cmd->{needs_count}) {
+ $numeric_prefix = 1;
+ }
+
+ my $cur_pos = _input_pos();
+
+ # If defined $cur_pos will be changed to this.
+ my $old_pos;
+ # Position after the move.
+ my $new_pos;
+ # Execute the movement (multiple times).
+ if (not $movement) {
+ ($old_pos, $new_pos)
+ = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat);
+ } else {
+ ($old_pos, $new_pos)
+ = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat,
+ $char);
+ }
+ if (defined $old_pos) {
+ print "Changing \$cur_pos from $cur_pos to $old_pos" if DEBUG;
+ $cur_pos = $old_pos;
+ }
+ if (defined $new_pos) {
+ _input_pos($new_pos);
+ } else {
+ $new_pos = _input_pos();
+ }
+
+ # 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 == $commands->{d}) or
+ $cmd->{repeatable})) {
+ print "Updating history position: $undo_buffer[0]->[0]"
+ if DEBUG;
+ $undo_buffer[0]->[1] = $cur_pos;
+ }
+
+ # If we have an operator pending then run it on the handled text.
+ # 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->{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 == $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 $cmd->{repeatable}) {
+ $last->{cmd} = $cmd;
+ $last->{char} = $char;
+ $last->{numeric_prefix} = $numeric_prefix;
+ $last->{operator} = $operator;
+ $last->{movement} = $movement;
+ $last->{register} = $register;
+ }
+ }
+
+ # 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 $cmd->{type} != C_INSERT) {
+ $numeric_prefix = undef;
+ }
+ $operator = undef;
+ $movement = undef;
+
+ if ($cmd != $commands->{'"'} and $register ne '"') {
+ print 'Changing register to "' if DEBUG;
+ $register = '"';
+ }
+
+ }
+
+ return 1; # call _stop()
+}
+
+sub handle_command_ex {
+ my ($key) = @_;
+
+ # BS key (8) or DEL key (127) - remove last character.
+ if ($key == 8 || $key == 127) {
+ print "Delete" if DEBUG;
+ if (@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) {
+ print "Run ex-mode command" if DEBUG;
+ cmd_ex_command();
+ _update_mode(M_CMD);
+
+ } elsif ($key == 9) { # TAB
+ print "Tab pressed" if DEBUG;
+ print "Ex buf contains: " . join('', @ex_buf) if DEBUG;
+ @tab_candidates = _tab_complete(join('', @ex_buf), [keys %$commands_ex]);
+ _debug("Candidates: " . join(", ", @tab_candidates));
+ if (@tab_candidates == 1) {
+ @ex_buf = ( split('', $tab_candidates[0]), ' ');
+ _set_prompt(':' . join '', @ex_buf);
+ }
+ # Ignore control characters for now.
+ } elsif ($key > 0 && $key < 32) {
+ # TODO: use them later, e.g. completion
+
+ # Append entered key
+ } else {
+ if ($key != -1) {
+ # check we're not called from an ex_history_* function
+ push @ex_buf, chr $key;
+ }
+ _set_prompt(':' . join '', @ex_buf);
+ }
+
+ Irssi::statusbar_items_redraw("vim_windows");
+
+ _stop();
+}
+
+
+
vim_mode_init();