diff options
| -rw-r--r-- | vim-mode/vim_mode.pl | 1040 | 
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(); | 
