diff --git a/src/perl/matcher b/src/perl/matcher
index 935123f..b87df00 100644
--- a/src/perl/matcher
+++ b/src/perl/matcher
@@ -25,6 +25,29 @@ with the matched text as first argument.  The default configuration is
 suitable for matching URLs and launching a web browser, like the
 former "mark-urls" extension.
 
+If the C<matcher.launcher> resource starts with the 'perl:' prefix, it
+is evaluated as a perl expression. The following variables can be
+specified in the expression:
+
+=over 4
+
+=item C<$self>
+
+the extension object.
+
+=item C<@_>
+
+A list where the first element is the extension object, and the
+subsequent elements are the substrings matched by the capturing groups
+in the matcher pattern.
+
+=back
+
+Instead, if the C<matcher.launcher> resource starts with the 'shell:'
+prefix, the occurrences of C<$1, $2, ...> in the launcher string are
+replaced with the substring matched by the corresponding capturing
+group in the matcher pattern.
+
 The default pattern to match URLs can be overridden with the
 C<matcher.pattern.0> resource, and additional patterns can be specified
 with numbered patterns, in a manner similar to the "selection" extension.
@@ -88,6 +111,20 @@ Example: use a custom configuration.
     URxvt.matcher.pattern.2:  \\B(/\\S+?):(\\d+)(?=:|$)
     URxvt.matcher.launcher.2: gvim +$2 $1
 
+Example: use a perl expression for the launcher
+
+    URxvt.perl-ext:           matcher,inplace_vim
+    URxvt.matcher.button:     2
+    URxvt.matcher.pattern.1:  ^([^ ]+):(\\d+)
+    URxvt.matcher.launcher.1: perl:&urxvt::ext::inplace_vim::launch
+
+Where the "inplace_vim" extension is as follows:
+
+    sub launch {
+       my ($self, $file, $line) = @_;
+       $self->tt_write ($self->locale_encode ("vim $file +$line\n"));
+    }
+
 =cut
 
 my $url =
@@ -100,6 +137,18 @@ my $url =
       )+
    }x;
 
+sub run_launcher {
+   my ($self, $type, @args) = @_;
+   if ($type eq 'perl') {
+      my $expr = shift @args;
+      local @_ = @args;
+      eval $expr;
+      warn $@ if $@;
+   } else {
+      return $self->exec_async (@args);
+   }
+}
+
 sub matchlist_key_press {
    my ($self, $event, $keysym, $octets) = @_;
 
@@ -109,7 +158,7 @@ sub matchlist_key_press {
    my $i = ($keysym == 96 ? 0 : $keysym - 48);
    if ($i >= 0 && $i < @{ $self->{matches} }) {
       my @exec = @{ $self->{matches}[$i] };
-      $self->exec_async (@exec[5 .. $#exec]);
+      run_launcher ($self, @exec[5 .. $#exec]);
    }
 
    1
@@ -203,7 +252,7 @@ sub most_recent {
       $row = $line->beg - 1;
    }
    if(@exec) {
-      return $self->exec_async (@exec);
+      return run_launcher ($self, @exec);
    }
    ()
 }
@@ -309,24 +358,33 @@ sub find_matches {
    my @matches;
    for my $matcher (@{$self->{matchers}}) {
       my $launcher = $matcher->[1] || $self->{launcher};
+      my $type = 'exec';
+      if ($launcher =~ /^perl:(.*)/) {
+         $launcher = $1;
+         $type = 'perl';
+      } elsif ($launcher =~ /^(?:shell:)?(.*\$.*)/) {
+         $launcher = $1;
+         $type = 'shell';
+      }
       while ($text =~ /$matcher->[0]/g) {
-         my $match = substr $text, $-[0], $+[0] - $-[0];
          my @begin = @-;
          my @end = @+;
+         my @captured = map { substr $text, $begin[$_], $end[$_] - $begin[$_] } (0 .. $#begin);
          my @exec;
 
          if (!defined($off) || ($-[0] <= $off && $+[0] >= $off)) {
-            if ($launcher !~ /\$/) {
-               @exec = ($launcher, $match);
+            if ($type eq 'perl') {
+               @exec = ('perl', $launcher, @captured[1 .. $#captured]);
+            } elsif ($type eq 'shell') {
+               $launcher =~ s/\$(\d+)|\$\{(\d+)\}/
+                  $captured[$1 || $2]
+                  /egx;
+               @exec = ('exec', split /\s+/, $launcher);
             } else {
-               # It'd be nice to just access a list like ($&,$1,$2...),
-               # but alas, m//g behaves differently in list context.
-               @exec = map { s/\$(\d+)|\$\{(\d+)\}/
-                  substr $text, $begin[$1 || $2], $end[$1 || $2] - $begin[$1 || $2]
-                  /egx; $_ } split /\s+/, $launcher;
+               @exec = ('exec', $launcher, $captured[0]);
             }
 
-            push @matches, [ $line->coord_of ($begin[0]), $line->coord_of ($end[0]), $match, @exec ];
+            push @matches, [ $line->coord_of ($begin[0]), $line->coord_of ($end[0]), $captured[0], @exec ];
          }
       }
    }
@@ -376,7 +434,7 @@ sub on_button_release {
       && join("\x00", @$cmd) eq join("\x00", $self->command_for($row,$col))) {
       if($self->valid_button($event)) {
 
-         $self->exec_async (@$cmd);
+         run_launcher ($self, @$cmd);
 
       }
    }
@@ -455,7 +513,7 @@ sub select_key_press {
    if ($keysym == 0xff0d || $keysym == 0xff8d) { # enter
       if ($self->{matches}) {
          my @match = @{ $self->{matches}[$self->{id}] };
-         $self->exec_async (@match[5 .. $#match]);
+         run_launcher ($self, @match[5 .. $#match]);
       }
       $self->select_leave;
    } elsif ($keysym == 0x79) { # y
