Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 107 additions & 25 deletions vim-mode/vim_mode.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 => '<BS>', func => \&cmd_h, type => C_NORMAL },
"\x7F" => { char => '<BS>', func => \&cmd_h, type => C_NORMAL },
' ' => { char => '<Space>', func => \&cmd_l, type => C_NORMAL },
# history movement
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1633,18 +1651,41 @@ 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!");
}

my $count = $1;
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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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},
Expand All @@ -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;
Expand All @@ -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) = @_;

Expand All @@ -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) = @_;
Expand Down Expand Up @@ -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");
}
Expand All @@ -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};
Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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});
}
}
}
Expand Down