diff options
| -rw-r--r-- | vim-mode/vim_mode.pl | 241 | 
1 files changed, 204 insertions, 37 deletions
| diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl index 97105d1..6fd88cd 100644 --- a/vim-mode/vim_mode.pl +++ b/vim-mode/vim_mode.pl @@ -29,7 +29,7 @@ use strict;  use warnings;  use Irssi; -use Irssi::TextUI; # for sbar_items_redraw +use Irssi::TextUI;              # for sbar_items_redraw  use vars qw($VERSION %IRSSI); @@ -44,56 +44,102 @@ $VERSION = "1.0.1";     changed         => "20/9/2010"    ); -#sub DEBUG () { 1 } -sub DEBUG () { 0 } +sub DEBUG () { 1 } +#sub DEBUG () { 0 } -# circular buffer to keep track of the last N keystrokes. -my @key_buf; -my $buf_idx = 0; -my $key_buf_timer; -my $key_buf_enabled = 0; -my $should_ignore = 0; +sub A_NUM() { 0 } # expects a specific number of args +sub A_RET() { 1 } # expects a return to indicate end of args. +sub A_NON() { 2 } # expects zero args. +# TODO: do we need a A_FUN that calls a function to check if we've got all of them?  sub M_CMD() { 1 } # command mode  sub M_INS() { 0 } # insert mode +sub M_EX () { 2 } # extended mode (after a :?) + + +#  buffer to keep track of the last N keystrokes following an Esc character. +my @esc_buf; +my $esc_buf_idx = 0; +my $esc_buf_timer; +my $esc_buf_enabled = 0; + +# flag to allow us to emulate keystrokes without re-intercepting them +my $should_ignore = 0; +my $pending_command; + +# argument handling. +my @args_buf; +my $collecting_args = 0; # if we're actively collecting them. +my $args_type = A_NON;   # what type of args (constants above) +my $args_num = 0;        # how many args we expect + +# for commands like 10x +my $numeric_prefix = undef; + +# what Vi mode we're in. We start in insert mode.  my $mode = M_INS; +sub script_is_loaded { +    my $name = shift; +    print "Checking if $name is loaded" if DEBUG; +    no strict 'refs'; +    my $retval = defined %{ "Irssi::Script::${name}::" }; +    use strict 'refs'; + +    return $retval; +} + +unless (script_is_loaded('prompt_info')) { +    die "This script requires 'prompt_info' in order to work. " +      . "Please load it and try again"; +} else { +    vim_mode_init(); +} + +  my $commands    = {       'i' => { command => 'insert at cur',                func => \&cmd_insert, +              args => { type => A_NON },                params => {pos => sub { _input_pos() }},              },       'I' => { command => 'insert at end',                func => \&cmd_insert, +              args => { type => A_NON },                params => {pos => sub { _input_len() }},              },       'A' => { command => 'insert at start',                func => \&cmd_insert, +              args => { type => A_NON },                params => { pos => sub { 0 } },              },       'h' => { command => 'move left',                func => \&cmd_move, +              args => { type => A_NON },                params => { 'dir' => 'left' },              },       'l' => { command => 'move right',                func => \&cmd_move, +              args => { type => A_NON },                params => { 'dir' => 'right' },              },       'w' => { command => 'move forward word',                func => \&cmd_jump_word, +              args => { type => A_NON },                params => { 'dir' => 'fwd',                            'pos' => sub { _input_pos() }                          },              },       'b' => { command => 'move backward word',                func => \&cmd_jump_word, +              args => { type => A_NON },                params => { 'dir' => 'back',                            'pos' => sub { _input_pos() }                          }, @@ -101,17 +147,34 @@ my $commands       'x' => { command => 'delete char forward',                func => \&cmd_delete_char, +              args => { type => A_NON },                params => { 'dir' => 'fwd',                            'pos' => sub { _input_pos() }                          },              }, +     ':' => { command => 'search/replace', +              func => \&cmd_replace, +              args => { type => A_RET }, +              params => { 'dir' => 'fwd', +                          'pos' => sub { _input_pos() } +                        }, +            }      }; +sub cmd_replace { +    my ($params) = @_; +} + +sub cmd_ex_mode { +    my ($params) = @_; +    _set_prompt(":"); +} +  sub cmd_delete_char {      my ($params) = @_;      my $pos = $params->{pos}->();      my $direction = $params->{dir}; -    print "Sending keystrokes for delete-char"; +    print "Sending keystrokes for delete-char" if DEBUG;      _stop();      my @buf = (4);      _emulate_keystrokes(@buf); @@ -177,19 +240,26 @@ sub got_key {      return if ($should_ignore);      if ($key == 27) { -        print "Esc seen, starting buffer"; -        $key_buf_enabled = 1; +        print "Esc seen, starting buffer" if DEBUG; +        $esc_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) -        $key_buf_timer -          = Irssi::timeout_add_once(10, \&handle_key_buffer, undef); +        $esc_buf_timer +          = Irssi::timeout_add_once(10, \&handle_esc_buffer, undef); + +    } elsif ($key == 3 && $mode == M_INS) { +        $mode = M_CMD; +        _update_mode(); +        _stop(); +        return;      } -    if ($key_buf_enabled) { -        $key_buf[$buf_idx++] = $key; +    if ($esc_buf_enabled) { +        $esc_buf[$esc_buf_idx++] = $key;          _stop(); +        return;      }      if ($mode == M_CMD) { @@ -198,16 +268,16 @@ sub got_key {      }  } -sub handle_key_buffer { +sub handle_esc_buffer { -    Irssi::timeout_remove($key_buf_timer); -    $key_buf_timer = undef; +    Irssi::timeout_remove($esc_buf_timer); +    $esc_buf_timer = undef;      # see what we've collected. -    print "Key buffer contains: ", join(", ", @key_buf); +    print "Esc buffer contains: ", join(", ", @esc_buf) if DEBUG; -    if (@key_buf == 1 && $key_buf[0] == 27) { +    if (@esc_buf == 1 && $esc_buf[0] == 27) { -        print "Command Mode"; +        print "Enter Command Mode" if DEBUG;          $mode = M_CMD;          _update_mode(); @@ -216,36 +286,125 @@ sub handle_key_buffer {          # or pass it off to the command handler.          if ($mode == M_CMD) {              # command -            my $key_str = join '', map { chr } @key_buf; +            my $key_str = join '', map { chr } @esc_buf;              if ($key_str =~ m/^\e\[([ABCD])/) { -                print "Arrow key: $1"; +                print "Arrow key: $1" if DEBUG;              } else { -                print "Dunno what that is." +                print "Dunno what that is." if DEBUG;              }          } else { -            _emulate_keystrokes(@key_buf); +            _emulate_keystrokes(@esc_buf);          }      } -    @key_buf = (); -    $buf_idx = 0; -    $key_buf_enabled = 0; +    @esc_buf = (); +    $esc_buf_idx = 0; +    $esc_buf_enabled = 0; +} + +sub collect_args { +    my $key = shift; + +    print "Collecting arguments" if DEBUG; + +    if ($args_type == A_NUM) { +        push @args_buf, $key; + +        print "numbered args, expect: $args_num, got: " .scalar(@args_buf) +          if DEBUG; + +        if (scalar @args_buf == $args_num) { +            dispatch_command($pending_command, @args_buf); +        } +    } elsif ($args_type == A_RET) { +        print "ret terminated args. Key is $key" if DEBUG; + +        if ($key == 10) { +            dispatch_command($pending_command, @args_buf); +        } else { +            push @args_buf, $key; +        } +    } +} + +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 {      my ($key) = @_; -    my $char = chr($key); -    if (exists $commands->{$char}) { -        my $cmd = $commands->{$char}; -        # print "Going to execute command: ", $cmd->{command}; -        $cmd->{func}->( $cmd->{params} ); + +    if ($collecting_args) { + +        collect_args($key); +        _stop(); +      } else { -        _stop(); # disable everything else +        my $char = chr($key); +        if ($char =~ m/[0-9]/) { +            print "Processing numeric prefix: $char" if DEBUG; +            handle_numeric_prefix($char); +            _stop(); +        } else { +            print "Processing new command: $char" if DEBUG; +            if (exists $commands->{$char}) { + +                my $cmd = $commands->{$char}; +                $args_type = $cmd->{args}->{type}; + +                if ($args_type == A_NON) { + +                    # we can dispatch straight away +                    $pending_command = undef; +                    dispatch_command($cmd); + +                } elsif ($args_type == A_NUM) { + +                    @args_buf = (); +                    $args_num = $cmd->{args}->{num}; +                    $collecting_args = 1; +                    $pending_command = $commands->{$char}; +                    _stop(); +                } elsif ($args_type == A_RET) { + +                    $collecting_args = 1; +                    $pending_command = $commands->{$char}; +                    _stop(); +                } +            } else { +                _stop(); # disable everything else +            } +        } +    } +} + +sub dispatch_command { +    my ($cmd, @args) = @_; +    $collecting_args = 0; + +    if (defined $numeric_prefix) { +        $cmd->{params}->{prefix} = $numeric_prefix; +        print "Commadn has numeric prefix: $numeric_prefix" if DEBUG; +        $numeric_prefix = undef;      } + +    print "Dispatchign command with args: " . join(", ", @args) if DEBUG; +    $cmd->{params}->{arg_buf} = \@args; +    $cmd->{func}->($cmd->{params} );  } -Irssi::signal_add_first 'gui key pressed' => \&got_key; -Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb'); +sub vim_mode_init { +    Irssi::signal_add_first 'gui key pressed' => \&got_key; +    Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb'); +}  sub _input {      my ($data) = @_; @@ -282,6 +441,7 @@ sub _emulate_keystrokes {      }      $should_ignore = 0;  } +  sub _stop() {      Irssi::signal_stop();  } @@ -289,3 +449,10 @@ sub _stop() {  sub _update_mode() {      Irssi::statusbar_items_redraw("vim_mode");  } + +sub _set_prompt { +    my $msg = shift; +    # add a leading space unless we're trying to clear it entirely. +    $msg = ' ' . $msg if length $msg; +    Irssi::signal_emit('change prompt', $msg); +} | 
