#! perl # Author: Chip Camden my $re_url = qr{ (?:https?://|ftp://|news://|mailto:|file://|\bwww\.) [\w\-\@;\/?:&=%\$.+!*\x27,~#]* ( \([\w\-\@;\/?:&=%\$.+!*\x27,~#]*\)| # Allow a pair of matched parentheses [\w\-\@;\/?:&=%\$+*~] # exclude some trailing characters (heuristic) )+ }x; sub find_matches { my ($text, $rowmap) = @_; my @overlays; while ($text =~ /$re_url/g) { my $ndx = $-[0]; my $href = $&; my $col = 0; my $row = 0; for my $key (keys %$rowmap) { my $value = $rowmap->{$key}; my ($start, $end) = @$value; if (($start <= $ndx) && ($end >= $ndx)) { $row = $key; $col = $ndx - $start; last; } } my @ov = ($col, $row, $href); push(@overlays, \@ov) if ($row >= 0); } return @overlays; } sub build_overlays { my ($self, $text, $rowmap) = @_; my $label_rend = $self->get_rend("label", urxvt::OVERLAY_RSTYLE); my $num = 0; my $labels = {}; my $hrefs = {}; my @overlays = find_matches($text, $rowmap); @overlays = reverse @overlays if ($self->{descending}); for my $ov (@overlays) { my ($col, $row, $href) = @$ov; $num++; my $overlay = $self->overlay($col, $row, $self->strwidth($num), 1, $label_rend, 0); $overlay->set(0, 0, $num); $labels->{$num} = $overlay; $hrefs->{$num} = $href; } return ($num, $labels, $hrefs) }; sub on_user_command { my ($self, $cmd) = @_; if ($cmd =~ s/^reselect\b//) { my $rowmap = {}; my $row = 0; my $base_col = 0; my $text = ''; my ($brow, $bcol) = $self->selection_beg(); my ($erow, $ecol) = $self->selection_end(); my $issel = ($ecol > $bcol) || ($erow > $brow); if ($issel) { # restrict to selection if one exists ($row, $base_col) = ($brow - $self->view_start, $bcol); for (split(/\n/, $self->selection())) { my $start = length($text) - $base_col; $text .= $_; $rowmap->{$row} = [$start, (length($text)-1)]; $base_col = 0; $row++; } } else { # no selection, use visible terminal for (0..($self->nrow - 1)) { $row = $_; my $start = length($text); $text .= $self->ROW_t($row + $self->view_start); $rowmap->{$row} = [$start, (length($text)-1)]; } } my ($num, $labels, $hrefs) = $self->build_overlays($text, $rowmap); if ($num < 1) { my $desc = $issel ? "in visible selected text" : "on visible screen"; $self->status_msg("url-picker: no URLs found $desc"); } else { my $url_picker = {}; $url_picker->{prompt} = $self->overlay( 0, -1, 8, 1, $self->get_rend("prompt", urxvt::OVERLAY_RSTYLE), 0 ); $url_picker->{prompt}->set(0,0,"Follow:"); $url_picker->{labels} = $labels; $url_picker->{hrefs} = $hrefs; $url_picker->{num} = $num; $url_picker->{buffer} = ''; my ($crow,$ccol) = $self->screen_cur; $url_picker->{crow} = $crow; $url_picker->{ccol} = $ccol; $self->{url_picker} = $url_picker; $self->update($url_picker); } } () } sub on_key_press { my ($self, $event, $keysym) = @_; if ($self->{url_picker_msg}) { $self->{url_picker_msg} = (); } my $p = $self->{url_picker}; if ($p) { if ($keysym == 0xff1b || $keysym == 113) { # escape or q $self->screen_cur($p->{crow},$p->{ccol}); $self->{url_picker} = (); } elsif ($keysym == 0xff08) { # backspace if (length($p->{buffer}) > 0) { $p->{buffer} = substr($p->{buffer},0,-1); $self->update($p); } } elsif (($keysym >= 48) && ($keysym <= 57)) { $p->{buffer} = $p->{buffer} . ($keysym - 48); $self->update($p); } elsif ($keysym == 0xff0d) { # CR my $num = $p->{buffer}; my $hrefs = $p->{hrefs}; if (($num > 0) && ($num <= $p->{num})) { my $href = $hrefs->{$num}; $self->launch($href); } } return 1; } () } sub update { my ($self, $p) = @_; $p->{typing} = $self->overlay( 8, -1, length($p->{buffer}), 1, $self->get_rend("input", urxvt::DEFAULT_RSTYLE), 0 ); $p->{typing}->set(0,0,$p->{buffer}); my $ndx = 0; my $labels = $p->{labels}; my $hrefs = $p->{hrefs}; my $len = length($p->{buffer}); my $size = $p->{num}; my @matches; while (++$ndx <= $size) { my $overlay = $labels->{$ndx}; if (($len == 0) || (($len <= length($ndx)) && (substr($ndx,0,$len) eq $p->{buffer}))) { $overlay->show; unshift @matches,$hrefs->{$ndx}; } else { $overlay->hide; } } # Auto-launch a single url only if the launchsingle resource is true # (default), or if some digits were input if (scalar(@matches) == 1 && ($self->{launchsingle} || $len > 0)) { $self->launch(@matches[0]); } else { $self->screen_cur($self->nrow,8+$len); } } sub launch { my ($self, $href) = @_; my $p = $self->{url_picker}; $self->screen_cur($p->{crow},$p->{ccol}); $self->{url_picker} = (); my $launcher = $self->{launcher}; $self->status_msg($href); $self->exec_async ($launcher,$href); } sub status_msg { my ($self, $msg) = @_; $self->{url_picker_msg} = $self->overlay(0, -1, length($msg), 1, $self->get_rend("status",urxvt::OVERLAY_RSTYLE), 0); $self->{url_picker_msg}->set(0,0,$msg); $self->{url_picker_timer} = urxvt::timer ->new ->after (5) ->cb (sub { $self->{url_picker_msg} = (); $self->{url_pickertimer} = (); }); } sub get_rend { my ($self, $name, $default) = @_; urxvt::SET_COLOR $default, $self->my_resource("$name.foregroundColor") || urxvt::GET_BASEFG $default, $self->my_resource("$name.backgroundColor") || urxvt::GET_BASEBG $default; } sub on_key_release { my ($self, $event, $keysym) = @_; $self->{url_picker}; } sub my_resource { my ($self, $name) = @_; $self->x_resource ("$self->{name}.$name"); } sub on_start { my ($self) = @_; ($self->{name} = __PACKAGE__) =~ s/.*:://; $self->{name} =~ tr/_/-/; $self->{launcher} = $self->my_resource("launcher") || $self->x_resource("url-launcher") || "sensible-browser"; $self->{launchsingle} = ($self->my_resource("launchsingle") ne "false"); $self->{descending} = ($self->my_resource("order") eq "descending"); $self->{url_picker} = (); }