From 99f8930b74962760a9164c16c8d62d1876ed7cee Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Sat, 25 Sep 2010 23:50:27 +0200 Subject: vim_mode: Implement vi-operators. Also switch back to custom move commands instead of using irssi's. Some simplifications. Ex-mode is used as mode now. --- vim-mode/vim_mode.pl | 420 ++++++++++++++++++++++++--------------------------- 1 file changed, 201 insertions(+), 219 deletions(-) (limited to 'vim-mode') diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl index e29b1c0..9ca2cda 100644 --- a/vim-mode/vim_mode.pl +++ b/vim-mode/vim_mode.pl @@ -4,7 +4,7 @@ # # * Insert/Command mode. Escape enter command mode. # * cursor motion with: h, l -# * cursor word motion with: w, b +# * cursor word motion with: w, b, e # * delete at cursor: x # * Insert mode at pos: i # * Insert mode at start: I @@ -47,11 +47,6 @@ $VERSION = "1.0.1"; sub DEBUG () { 1 } #sub DEBUG () { 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 :?) @@ -66,18 +61,15 @@ 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 +# ex mode buffer +my @ex_buf; # for commands like 10x my $numeric_prefix = undef; +# vi operators like d, c, .. +my $operator = undef; + # what Vi mode we're in. We start in insert mode. my $mode = M_INS; @@ -100,91 +92,162 @@ unless (script_is_loaded('prompt_info')) { } -my $commands +# vi-operators like d, c; they don't move the cursor +my $operators + = { + 'c' => { func => \&cmd_operator_c }, + 'd' => { func => \&cmd_operator_d }, + }; + +# 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 +my $movements = { - 'i' => { command => 'insert at cur', - func => \&cmd_insert, - args => { type => A_NON }, - params => {pos => sub { _input_pos() }}, - }, - - 'A' => { command => 'insert at end', - func => \&cmd_insert, - args => { type => A_NON }, - params => {pos => sub { _input_len() }}, - }, - - 'I' => { 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() } - }, - }, - 'd' => { command => 'delete', - func => \&cmd_delete, - args => { type => A_NUM, num => 1 }, - params => { 'pos' => sub { _input_pos() } }, - }, - - 'x' => { command => 'delete char forward', - func => \&cmd_delete_char, - args => { type => A_NON }, - params => { 'dir' => 'fwd', - 'pos' => sub { _input_pos() } - }, - }, - ':' => { command => 'Ex command', - func => \&cmd_ex_command, - args => { type => A_RET }, - params => { 'pos' => sub { _input_pos() } }, - }, + # arrow like movement + 'h' => { func => \&cmd_movement_h }, + 'l' => { func => \&cmd_movement_l }, + #'j' => { func => \&cmd_movement_j }, + #'k' => { func => \&cmd_movement_k }, + # word movement + 'w' => { func => \&cmd_movement_w }, + 'b' => { func => \&cmd_movement_b }, + 'e' => { func => \&cmd_movement_e }, + # line movement + '0' => { func => \&cmd_movement_0 }, + '$' => { func => \&cmd_movement_dollar }, + # delete chars + 'x' => { func => \&cmd_movement_x }, + # insert mode + 'i' => { func => \&cmd_movement_i }, + 'I' => { func => \&cmd_movement_I }, + 'A' => { func => \&cmd_movement_A }, }; +sub cmd_operator_c { + my ($old_pos, $new_pos) = @_; + + cmd_operator_d($old_pos, $new_pos); + _update_mode(M_INS); +} + +sub cmd_operator_d { + my ($old_pos, $new_pos) = @_; + + my $length = $new_pos - $old_pos; + # We need a positive length and $old_pos must be smaller. + if ($length < 0) { + my $tmp = $old_pos; + $old_pos = $new_pos; + $new_pos = $tmp; + $length *= -1; + } + + # Remove the selected string from the input. + my $input = _input(); + substr $input, $old_pos, $length, ''; + _input($input); + + # Move the cursor at the right position. + _input_pos($old_pos); +} + +sub cmd_movement_h { + my ($count, $pos) = @_; + + $pos -= $count; + $pos = 0 if $pos < 0; + _input_pos($pos); +} +sub cmd_movement_l { + my ($count, $pos) = @_; + + my $length = _input_len(); + $pos += $count; + $pos = $length if $pos > $length; + _input_pos($pos); +} + +sub cmd_movement_w { + my ($count, $pos) = @_; + + my $input = _input(); + while ($count-- > 0) { + $pos = index $input, ' ', $pos + 1; + if ($pos == -1) { + return cmd_movement_dollar(); + } + $pos++; + } + _input_pos($pos); +} +sub cmd_movement_b { + my ($count, $pos) = @_; -sub cmd_delete { - my ($params) = @_; - my @args = @{$params->{args}}; - my $arg = $args[0] // ''; + my $input = reverse _input(); + $pos = _end_of_word($input, $count, length($input) - $pos - 1); + if ($pos == -1) { + cmd_movement_0(); + } else { + _input_pos(length($input) - $pos - 1); + } +} +sub cmd_movement_e { + my ($count, $pos) = @_; - if ($arg eq '$') { # end of line - } elsif ($arg eq '^') { #start of line + $pos = _end_of_word(_input(), $count, $pos); + if ($pos == -1) { + cmd_movement_dollar(); } else { - # dunno + _input_pos($pos); } } -sub cmd_replace { - my ($params) = @_; +# Go to the end of $count-th word, like vi's e. +sub _end_of_word { + my ($input, $count, $pos) = @_; + + # We are already at the end of one a word, ignore the following space so + # we can skip over it. + if (index $input, ' ', $pos + 1 == $pos + 1) { + $pos++; + } + + while ($count-- > 0) { + $pos = index $input, ' ', $pos + 1; + if ($pos == -1) { + return -1; + } + } + return $pos - 1; +} + +sub cmd_movement_0 { + _input_pos(0); +} +sub cmd_movement_dollar { + _input_pos(_input_len()); +} + +sub cmd_movement_x { + my ($count, $pos) = @_; + + cmd_operator_d($pos, $pos + $count); +} + +sub cmd_movement_i { + _update_mode(M_INS); +} +sub cmd_movement_I { + cmd_movement_0(); + _update_mode(M_INS); +} +sub cmd_movement_A { + cmd_movement_dollar(); + _update_mode(M_INS); } sub cmd_ex_command { - my ($params) = @_; - my $args = $params->{arg_buf}; - my $arg_str = join '', @$args; + my $arg_str = join '', @ex_buf; if ($arg_str =~ m|s/(.+)/(.*)/([ig]*)|) { my ($search, $replace, $flags) = ($1, $2, $3); print "Searching for $search, replace: $replace, flags; $flags" @@ -215,56 +278,6 @@ sub cmd_ex_command { } } -sub cmd_delete_char { - my ($params) = @_; - my $pos = $params->{pos}->(); - my $direction = $params->{dir}; - print "Sending keystrokes for delete-char" if DEBUG; - _stop(); - my @buf = (4); - _emulate_keystrokes(@buf); - -} - -sub cmd_jump_word { - my ($params) = @_; - my $pos = $params->{pos}->(); - my $direction = $params->{dir}; - _stop(); - my @buf; - if ($direction eq 'fwd') { - push @buf, (27, 102); - } else { - push @buf, (27, 98); - } - _emulate_keystrokes(@buf); -} - -sub cmd_insert { - my ($params) = @_; - my $pos = $params->{pos}->(); - - _input_pos($pos); - - _update_mode(M_INS); - - _stop(); -} - -sub cmd_move { - my ($params) = @_; - my $dir = $params->{dir}; - my $current_pos = _input_pos(); - _stop(); - my @buf = (27, 91); - if ($dir eq 'left') { - push @buf, 68; - } else { - push @buf, 67; - } - my $count = $params->{prefix} // 1; - _emulate_keystrokes(@buf) for 1..$count; -} sub vim_mode_cb { my ($sb_item, $get_size_only) = @_; @@ -307,8 +320,7 @@ sub got_key { return; } - if ($mode == M_CMD) { - # command mode + if ($mode == M_CMD || $mode == M_EX) { handle_command($key); } } @@ -346,39 +358,6 @@ sub handle_esc_buffer { $esc_buf_enabled = 0; } -sub collect_args { - my $key = shift; - - print "Collecting arguments" if DEBUG; - - if ($key == 127) { # DEL key - remove last argument - print "Delete" if DEBUG; - pop @args_buf; - _set_prompt(':' . join '', @args_buf); - return; - } - - if ($args_type == A_NUM) { - push @args_buf, chr $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, chr $key; - _set_prompt(':' . join '', @args_buf); - } - } -} - sub handle_numeric_prefix { my ($char) = @_; my $num = 0+$char; @@ -394,68 +373,71 @@ sub handle_numeric_prefix { sub handle_command { my ($key) = @_; - if ($collecting_args) { - - collect_args($key); - _stop(); + if ($mode == M_EX) { + # DEL key - remove last character + if ($key == 127) { + print "Delete" if DEBUG; + pop @ex_buf; + _set_prompt(':' . join '', @ex_buf); + + # Return key - execute command + } elsif ($key == 10) { + print "Run ex-mode command" if DEBUG; + cmd_ex_command(); + _set_prompt(''); + @ex_buf = (); + _update_mode(M_CMD); + + # Append entered key + } else { + push @ex_buf, chr $key; + _set_prompt(':' . join '', @ex_buf); + } } else { my $char = chr($key); - if ($char =~ m/[0-9]/) { + if ($char =~ m/[1-9]/ || ($numeric_prefix && $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}; + } elsif (exists $operators->{$char}) { + print "Processing operator: $char" if DEBUG; - if ($args_type == A_NON) { + # Abort operator if we already have one pending. + if ($operator) { + $operator = undef; + # Set new operator. + } else { + $operator = $char; + } - # we can dispatch straight away - $pending_command = undef; - dispatch_command($cmd); + } elsif (exists $movements->{$char}) { + print "Processing movement command: $char" if DEBUG; - } elsif ($args_type == A_NUM) { + $numeric_prefix = 1 if not $numeric_prefix; - @args_buf = (); - $args_num = $cmd->{args}->{num}; - $collecting_args = 1; - $pending_command = $commands->{$char}; - _stop(); - } elsif ($args_type == A_RET) { + # Execute the movement (multiple times). + my $cur_pos = _input_pos(); + $movements->{$char}->{func}->($numeric_prefix, $cur_pos); + my $new_pos = _input_pos(); - $collecting_args = 1; - $pending_command = $commands->{$char}; - _stop(); - } - } else { - _stop(); # disable everything else + # If we have an operator pending then run it on the handled text. + if ($operator) { + print "Processing operator: ", $operator if DEBUG; + $operators->{$operator}->{func}->($cur_pos, $new_pos); + $operator = undef; } - } - } -} -sub dispatch_command { - my ($cmd, @args) = @_; - $collecting_args = 0; - $pending_command = undef; - @args_buf = (); - _set_prompt(''); + $numeric_prefix = undef; - if (defined $numeric_prefix) { - $cmd->{params}->{prefix} = $numeric_prefix; - print "Commadn has numeric prefix: $numeric_prefix" if DEBUG; - $numeric_prefix = undef; + # Start Ex mode. + } elsif ($char eq ':') { + _update_mode(M_EX); + _set_prompt(':'); + } } - print "Dispatchign command with args: " . join(", ", @args) if DEBUG; - $cmd->{params}->{arg_buf} = \@args; - - # actually call the function - $cmd->{func}->($cmd->{params} ); + _stop(); } sub vim_mode_init { -- cgit v1.2.3