diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl index 7d898ab..753539d 100644 --- a/vim-mode/vim_mode.pl +++ b/vim-mode/vim_mode.pl @@ -76,7 +76,10 @@ # * Mappings: :map - display custom mappings # :map {lhs} - display mappings starting with {lhs} # :map {lhs} {rhs} - add mapping +# :cmap - same as :map but for ex-mode, e.g. +# use :cmap LS :ls and not :cmap LS ls! # :unm[ap] {lhs} - remove custom mapping +# :cunm[ap] {lhs} - remove custom mapping in ex-mode # * Save mappings: :mkv[imrc][!] - like in Vim, but [file] not supported # * Substitute: :s/// - i and g are supported as flags, only /// can be # used as separator, uses Perl regex instead of @@ -312,6 +315,7 @@ # arrow like movement h => { char => 'h', func => \&cmd_h, type => C_NORMAL }, l => { char => 'l', func => \&cmd_l, type => C_NORMAL }, + "\x08" => { char => '', func => \&cmd_h, type => C_NORMAL }, "\x7F" => { char => '', func => \&cmd_h, type => C_NORMAL }, ' ' => { char => '', func => \&cmd_l, type => C_NORMAL }, # history movement @@ -463,6 +467,12 @@ type => C_EX }, unm => { char => ':unm', func => \&ex_unmap, type => C_EX }, + cmap => { char => ':cmap', func => \&ex_cmap, + type => C_EX }, + cunmap => { char => ':cunmap', func => \&ex_cunmap, + type => C_EX }, + cunm => { char => ':cunm', func => \&ex_cunmap, + type => C_EX }, source => { char => ':source', func => \&ex_source, type => C_EX }, so => { char => ':so', func => \&ex_source, @@ -502,7 +512,15 @@ # Add all default mappings. foreach my $char (keys %$commands) { next if $char =~ /^_/; # skip private commands (text-objects for now) - add_map($char, $commands->{$char}); + add_map($maps, $char, $commands->{$char}); +} + +# default extended mode mappings +my $ex_maps = {}; + +# Add all default mappings. +foreach my $char (keys %$commands_ex) { + add_map($ex_maps, $char, $commands_ex->{$char}); } # GLOBAL VARIABLES @@ -1633,10 +1651,14 @@ sub cmd_ex_command { return _warn("Invalid Ex-mode command!"); } - # Abort if command doesn't exist or used with count for unsupported - # commands. - if (not exists $commands_ex->{$2} or - ($1 ne '' and not $commands_ex->{$2}->{uses_count})) { + # Abort if command doesn't exist + if (not exists $ex_maps->{$2}) { + return _warn("Ex-mode $1$2 doesn't exist!"); + } + + my $cmd = $ex_maps->{$2}->{cmd}; + # Abort if used with count for unsupported commands. + if ($1 ne '' and not $cmd->{uses_count}) { return _warn("Ex-mode $1$2 doesn't exist!"); } @@ -1644,7 +1666,26 @@ sub cmd_ex_command { if ($count eq '') { $count = undef; } - $commands_ex->{$2}->{func}($arg_str, $count); + + # Ex-mode commands are either bound to ex commands or... + if ($cmd->{type} == C_EX) { + print "Processing ex-command: $2 ($cmd->{char})" if DEBUG; + + $cmd->{func}($arg_str, $count); + # ...irssi commands. + } elsif ($cmd->{type} == C_IRSSI) { + print "Processing irssi-command: $2 ($cmd->{char})" if DEBUG; + + # TODO: fix me more better (general server/win/none context?) + my $server = Irssi::active_server; + if (defined $server) { + $server->command($cmd->{func}); + } else { + Irssi::command($cmd->{func}); + } + } else { + return _warn("Ex-mode command of unsupported type $cmd->{type} bound to :$2!"); + } } sub ex_substitute { @@ -1927,16 +1968,16 @@ sub ex_undolist { _print_undo_buffer(); } -sub ex_map { - my ($arg_str, $count) = @_; +sub _ex_map { + my ($cmd_name, $maps, $arg_str, $count) = @_; # :map {lhs} {rhs} - if ($arg_str =~ /^map (\S+) (\S.*)$/) { + if ($arg_str =~ /^$cmd_name (\S+) (\S.*)$/) { my $lhs = _parse_mapping($1); my $rhs = $2; if (not defined $lhs) { - return _warn_ex('map', 'invalid {lhs}'); + return _warn_ex($cmd_name, 'invalid {lhs}'); } # Add new mapping. @@ -1945,7 +1986,7 @@ sub ex_map { if (index($rhs, ':') == 0) { $rhs =~ /^:(\S+)(\s.+)?$/; if (not exists $commands_ex->{$1}) { - return _warn_ex('map', "$rhs not found"); + return _warn_ex($cmd_name, "$rhs not found"); } else { $command = { char => $rhs, func => $commands_ex->{$1}->{func}, @@ -1968,17 +2009,17 @@ sub ex_map { } else { $rhs = _parse_mapping($2); if (not defined $rhs) { - return _warn_ex('map', 'invalid {rhs}'); + return _warn_ex($cmd_name, 'invalid {rhs}'); } elsif (not exists $commands->{$rhs}) { - return _warn_ex('map', "$2 not found"); + return _warn_ex($cmd_name, "$2 not found"); } else { $command = $commands->{$rhs}; } } - add_map($lhs, $command); + add_map($maps, $lhs, $command); # :map [lhs] - } elsif ($arg_str =~ m/^map\s*$/ or $arg_str =~ m/^map (\S+)$/) { + } elsif ($arg_str =~ m/^$cmd_name\s*$/ or $arg_str =~ m/^$cmd_name (\S+)$/) { # Necessary for case insensitive matchings. lc alone won't work. my $search = $1; $search = '' if not defined $search; @@ -1996,9 +2037,13 @@ sub ex_map { } } } else { - _warn_ex('map'); + _warn_ex($cmd_name); } } +sub ex_map { + my ($arg_str, $count) = @_; + return _ex_map('map', $maps, $arg_str, $count); +} sub ex_unmap { my ($arg_str, $count) = @_; @@ -2016,7 +2061,30 @@ sub ex_unmap { return _warn_ex('unmap', "$1 not found"); } - delete_map($lhs); + delete_map($maps, $lhs); +} +sub ex_cmap { + my ($arg_str, $count) = @_; + return _ex_map('cmap', $ex_maps, $arg_str, $count); +} +sub ex_cunmap { + my ($arg_str, $count) = @_; + + # :cunm[ap] {lhs} + if ($arg_str !~ /^cunm(?:ap)? (\S+)$/) { + return _warn_ex('cunmap'); + } + + my $lhs = _parse_mapping($1); + if (not defined $lhs) { + return _warn_ex('cunmap', 'invalid {lhs}'); + # Prevent unmapping of unknown or default mappings. + } elsif (not exists $ex_maps->{$lhs} or not defined $ex_maps->{$lhs}->{cmd} or + ($commands_ex->{$lhs} and $ex_maps->{$lhs}->{cmd} == $commands_ex->{$lhs})) { + return _warn_ex('cunmap', "$1 not found"); + } + + delete_map($ex_maps, $lhs); } sub _parse_mapping { my ($string) = @_; @@ -2092,6 +2160,9 @@ sub ex_source { # :map {lhs} {rhs}, keep in sync with ex_map() if ($line =~ /^\s*map (\S+) (\S.*)$/) { ex_map($line); + # :cmap {lhs} {rhs}, keep in sync with ex_cmap() + } elsif ($line =~ /^\s*cmap (\S+) (\S.*)$/) { + ex_cmap($line); } else { _warn_ex('source', "command not supported: $line"); } @@ -2110,7 +2181,7 @@ sub ex_mkvimrc { open my $file, '>', $vim_moderc or return; - # copied from ex_map() + # copied from _ex_map() foreach my $key (sort keys %$maps) { my $map = $maps->{$key}; my $cmd = $map->{cmd}; @@ -2120,6 +2191,17 @@ sub ex_mkvimrc { } } + # copied from _ex_map() + foreach my $key (sort keys %$ex_maps) { + my $map = $ex_maps->{$key}; + my $cmd = $map->{cmd}; + if (defined $cmd) { + # Remember $cmd->{char} starts with a colon (:)! + next if ":$map->{char}" eq $cmd->{char}; # skip default mappings + print $file "cmap $map->{char} $cmd->{char}\n"; + } + } + close $file; } @@ -2347,9 +2429,9 @@ sub got_key { _stop(); return; - # Pressing delete resets insert mode repetition. + # Pressing delete resets insert mode repetition (8 = BS, 127 = DEL). # TODO: maybe allow it - } elsif ($key == 127) { + } 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. @@ -2743,8 +2825,8 @@ sub handle_command_cmd { sub handle_command_ex { my ($key) = @_; - # DEL key - remove last character - if ($key == 127) { + # BS key (8) or DEL key (127) - remove last character. + if ($key == 8 || $key == 127) { print "Delete" if DEBUG; if (scalar @ex_buf > 0) { pop @ex_buf; @@ -2934,7 +3016,7 @@ sub _reset_undo_buffer { } sub add_map { - my ($keys, $command) = @_; + my ($maps, $keys, $command) = @_; # To allow multiple mappings starting with the same key (like gg, ge, gE) # also create maps for the keys "leading" to this key (g in this case, but @@ -2965,7 +3047,7 @@ sub add_map { } sub delete_map { - my ($keys) = @_; + my ($maps, $keys) = @_; # Abort for non-existent mappings or placeholder mappings. return if not exists $maps->{$keys} or not defined $maps->{$keys}->{cmd}; @@ -2999,7 +3081,7 @@ sub delete_map { # key. foreach my $key (@add) { if (exists $commands->{$key}) { - add_map($key, $commands->{$key}); + add_map($maps, $key, $commands->{$key}); } } }