Hi Why don't you send this to the urxvt mailing list. I for example will be glad if this comes up in the next release and it will be a shame if it gets lost in this mailing list. rxvt-unic...@lists.schmorp.de seems to be the mailing list on the official page - you can try there.
Best, Nikola On Wed, Oct 24, 2012 at 08:38:02PM +0300, Alex Efros wrote: > Hi! > > On Wed, Oct 24, 2012 at 06:09:03AM +0300, Alex Efros wrote: > > Looks like mark-urls was removed and replaced by matcher in current urxvt > > version. Anyway, both doesn't support urls wrapped in several lines. > > Chip Camden already mention this in previous email, but I've just tested > > both and confirm this. Probably matcher can be modified (as Chip Camden > > suggest) to support wrapped lines, but right now it doesn't work in this > > way. > > I've implemented this feature in matcher plugin. Modified version and > patch attached (I'm using rxvt-unicode-9.15). > > I've implemented only url-highlighting and clicking support. It looks like > matcher also support some "list" feature, but I've no idea what is it for > and thus I didn't tried to add multiline match support for it. > > It would be nice to have some notification when url clicked and opened in > browser, because this happens in background and inside mutt I didn't know > is it was successfully opened or I clicked on wrong place and nothing > happens - probably some status bar notification pop-up for 1 sec is the > best. Is it possible to implement this in urxvt? > > > Is it possible to somehow install modified version of matcher in my home > dir instead of replacing system-wide version? > > If there is recommended way to send patches to urxvt please let me know. > > Now, I have to figure out how to configure urxvt in same way as my xterm > was configured… > > -- > WBR, Alex. > #! perl > > # Author: Tim Pope <rxvt-unicodenos...@tpope.org> > # Bob Farrell <robertanthonyfarr...@gmail.com> > > my $url = > qr{ > (?:https?://|ftp://|news://|mailto:|file://|\bwww\.) > [a-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27,~#]* > ( > \([a-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27,~#]*\)| # Allow a pair of > matched parentheses > [a-zA-Z0-9\-\@;\/?:&=%\$_+*~] # exclude some trailing characters > (heuristic) > )+ > }x; > > sub on_key_press { > my ($self, $event, $keysym, $octets) = @_; > > if (! $self->{showing} ) { > return; > } > > my $i = ($keysym == 96 ? 0 : $keysym - 48); > if (($i > scalar(@{$self->{urls}})) || ($i < 0)) { > $self->matchlist(); > return; > } > > my @args = ($self->{urls}[ -$i-1 ]); > $self->matchlist(); > > $self->exec_async( $self->{launcher}, @args ); > } > > sub on_user_command { > my ($self, $cmd) = @_; > > if($cmd =~ s/^matcher:list\b//) { > $self->matchlist(); > } else { > if($cmd =~ s/^matcher:last\b//) { > $self->most_recent; > } > # For backward compatibility > else { > if($cmd =~ s/^matcher\b//) { > $self->most_recent; > } > } > } > () > } > > sub matchlist { > my ($self) = @_; > if ( $self->{showing} ) { > $self->{url_overlay}->hide(); > $self->{showing} = 0; > return; > } > @{$self->{urls}} = (); > my $line; > for (my $i = 0; $i < $self->nrow; $i ++) { > $line = $self->line($i); > next if ($line->beg != $i); > for my $url ($self->get_urls_from_line($line->t)) { > if (scalar(@{$self->{urls}}) == 10) { > shift @{$self->{urls}}; > } > push @{$self->{urls}}, $url; > } > } > > if (! scalar(@{$self->{urls}})) { > return; > } > > my $max = 0; > my $i = scalar( @{$self->{urls}} ) - 1 ;; > > my @temp = (); > > for my $url (@{$self->{urls}}) { > my $url = "$i-$url"; > my $xpos = 0; > > if ($self->ncol + (length $url) >= $self->ncol) { > $url = substr( $url, 0, $self->ncol ); > } > > push @temp, $url; > > if( length $url > $max ) { > $max = length $url; > } > > $i--; > } > > @temp = reverse @temp; > > $self->{url_overlay} = $self->overlay(0, 0, $max, scalar( @temp ), > urxvt::OVERLAY_RSTYLE, 2); > my $i = 0; > for my $url (@temp) { > $self->{url_overlay}->set( 0, $i, $url, [(urxvt::OVERLAY_RSTYLE) x > length $url]); > $self->{showing} = 1; > $i++; > } > > } > > sub most_recent { > my ($self) = shift; > my $row = $self->nrow; > my @exec; > while($row-- > $self->top_row) { > @exec = $self->command_for($row); > last if(@exec); > } > if(@exec) { > return $self->exec_async (@exec); > } > () > } > > sub my_resource { > my $self = shift; > $self->x_resource ("$self->{name}.$_[0]"); > } > > # turn a rendition spec in the resource into a sub that implements it on $_ > sub parse_rend { > my ($self, $str) = @_; > my ($mask, $fg, $bg, $failed) = $str ? urxvt::rend2mask($str) > : (urxvt::RS_Uline, undef, undef, []); > warn "Failed to parse rendition string: " . join(',', @$failed) if > @$failed; > my @rend; > push @rend, sub { $_ |= $mask } if $mask; > push @rend, sub { $_ = urxvt::SET_FGCOLOR($_, $fg) } if defined $fg; > push @rend, sub { $_ = urxvt::SET_BGCOLOR($_, $bg) } if defined $bg; > sub { > for my $s ( @rend ) { &$s }; > } > } > > sub on_start { > my ($self) = @_; > > ($self->{name} = __PACKAGE__) =~ s/.*:://; > $self->{name} =~ tr/_/-/; > $self->{launcher} = $self->my_resource("launcher") || > $self->x_resource("urlLauncher") || > "sensible-browser"; > > $self->{urls} = []; > $self->{showing} = 0; > $self->{button} = 2; > $self->{state} = 0; > if($self->{argv}[0] || $self->my_resource("button")) { > my @mods = split('', $self->{argv}[0] || $self->my_resource("button")); > for my $mod (@mods) { > if($mod =~ /^\d+$/) { > $self->{button} = $mod; > } elsif($mod eq "C") { > $self->{state} |= urxvt::ControlMask; > } elsif($mod eq "S") { > $self->{state} |= urxvt::ShiftMask; > } elsif($mod eq "M") { > $self->{state} |= $self->ModMetaMask; > } elsif($mod ne "-" && $mod ne " ") { > warn("$mod is invalid in $self->{name}<$self->{argv}[0]>\n"); > } > } > } > > my @defaults = ($url); > my @matchers; > for (my $idx = 0; defined (my $res = $self->my_resource("pattern.$idx") || > $defaults[$idx]); $idx++) { > $res = $self->locale_decode ($res); > utf8::encode $res; > my $launcher = $self->my_resource("launcher.$idx"); > $launcher =~ s/\$&|\$\{&\}/\${0}/g if ($launcher); > my $rend = $self->parse_rend($self->my_resource("rend.$idx")); > unshift @matchers, [qr($res)x,$launcher,$rend]; > } > $self->{matchers} = \@matchers; > > () > } > > sub get_urls_from_line { > my ($self, $line) = @_; > my @urls; > for my $matcher (@{$self->{matchers}}) { > while ($line =~ /$matcher->[0]/g) { > push @urls, substr( $line, $-[0], $+[0] - $-[0] ); > } > } > return @urls; > } > > sub on_line_update { > my ($self, $row) = @_; > > # fetch the line (enlarged to adjoining lines) that has changed > my ($text, $prev_cols, $next_cols, @lines) = $self->enlarge($row); > > # find all urls (if any) > for my $matcher (@{$self->{matchers}}) { > $self->match($matcher->[0], $text, $prev_cols, $next_cols, \@lines, sub > { > for (@_) { > my ($line, $from, $to) = @$_; > my $rend = $line->r; > # mark all characters as underlined. we _must_ not toggle underline, > # as we might get called on an already-marked url. > &{$matcher->[2]} > for @{$rend}[ $from .. $to - 1]; > $line->r($rend); > } > }); > } > > () > } > > sub valid_button { > my ($self, $event) = @_; > my $mask = $self->ModLevel3Mask | $self->ModMetaMask > | urxvt::ShiftMask | urxvt::ControlMask; > return ($event->{button} == $self->{button} && > ($event->{state} & $mask) == $self->{state}); > } > > sub command_for { > my ($self, $row, $col) = @_; > > # fetch the line (enlarged to adjoining lines) that has changed > my ($text, $prev_cols, $next_cols, @lines) = $self->enlarge($row); > > for my $matcher (@{$self->{matchers}}) { > my $launcher = $matcher->[1] || $self->{launcher}; > my @exec; > $self->match($matcher->[0], $text, $prev_cols, $next_cols, \@lines, sub > { > my $hit = 0; > my $match = q{}; > for (@_) { > my ($line, $from, $to) = @$_; > my $text = $line->t; > $match .= substr $text, $from, $to-$from; > if ($line->beg <= $row && $row <= $line->end) { > $hit ||= !defined $col; > if ($row < $line->end) { > $hit ||= 1; > } else { > $hit ||= $from <= $col && $col < $to; > } > } > } > if ($hit) { > if ($launcher !~ /\$/) { > @exec = ($launcher,$match); > } else { > $match =~ /$matcher->[0]/; > my @begin = @-; > my @end = @+; > # 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); > } > } > }); > return @exec if @exec; > } > > () > } > > sub on_button_press { > my ($self, $event) = @_; > if($self->valid_button($event) > && (my @exec = $self->command_for($event->{row},$event->{col}))) { > $self->{row} = $event->{row}; > $self->{col} = $event->{col}; > $self->{cmd} = \@exec; > return 1; > } else { > delete $self->{row}; > delete $self->{col}; > delete $self->{cmd}; > } > > () > } > > sub on_button_release { > my ($self, $event) = @_; > > my $row = delete $self->{row}; > my $col = delete $self->{col}; > my $cmd = delete $self->{cmd}; > > return if !defined $row; > > if($row == $event->{row} && abs($col-$event->{col}) < 2 > && join("\x00", @$cmd) eq join("\x00", $self->command_for($row,$col))) { > if($self->valid_button($event)) { > > $self->exec_async (@$cmd); > > } > } > > 1; > } > > sub enlarge { > my ($self, $row) = @_; > > my $line = $self->line($row); > my $text = $line->t; > > # enlarge this line with prev&next lines up to nearest line with space char > my ($prev_cols, $next_cols) = (0, 0); > my (@prev_lines,@next_lines); > if ($line->l && $text !~ /\A\s/ms) { > for my $prev_row (reverse 0 .. $row-1) { > my $l = $self->line($prev_row); > my $t = $l->t; > last if $l->l < $self->ncol; > unshift @prev_lines, $l; > $prev_cols += $l->l; > $text = $t . $text; > last if $t =~ /\s/ms; > } > } > if ($line->l == $self->ncol && $text !~ /\s\z/ms) { > for my $next_row ($row+1 .. $self->nrow-1) { > my $l = $self->line($next_row); > my $t = $l->t; > push @next_lines, $l; > $next_cols += $l->l; > $text .= $t; > last if $l->l < $self->ncol; > last if $t =~ /\s/ms; > } > } > > my @lines = (@prev_lines, $line, @next_lines); > return ($text, $prev_cols, $next_cols, @lines); > } > > sub match { > my ($self, $re, $text, $prev_cols, $next_cols, $lines, $cb) = @_; > while ($text =~ /$re/g) { > my ($beg, $end) = ($-[0], $+[0]); > # skip matches outside this line > next if $end <= $prev_cols; > next if $beg >= (length $text) - $next_cols; > # detect match boundaries over lines and send them to user's callback > my @parts; > for my $line (@$lines) { > if ($beg < $line->l && 0 < $end) { > my $from = $beg < 0 ? 0 : $beg; > my $to = $line->l < $end ? $line->l : $end; > push @parts, [$line, $from, $to]; > } > $beg -= $line->l; > $end -= $line->l; > } > $cb->(@parts); > } > return; > } > > # vim:set sw=3 sts=3 et: