url-picker (6691B)
1 #! perl 2 3 # Author: Chip Camden <sterling@camdensoftware.com> 4 5 my $url = 6 qr{ 7 (?:https?://|ftp://|news://|mailto:|file://|\bwww\.) 8 [a-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27,~#]* 9 ( 10 # Allow a pair of matched parentheses 11 \([a-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27,~#]*\)| 12 # exclude some trailing characters (heuristic) 13 [a-zA-Z0-9\-\@;\/?:&=%\$_+*~] 14 )+ 15 }x; 16 17 sub on_user_command { 18 my ($self, $cmd) = @_; 19 if ($cmd =~ s/^url-picker\b//) { 20 my $labels = {}; 21 my $hrefs = {}; 22 my $rowmap = {}; 23 my $num = 0; 24 my $row = 0; 25 my $base_col = 0; 26 my $text = ''; 27 my $label_rend = $self->get_rend("label", urxvt::OVERLAY_RSTYLE); 28 my $label_urls = sub { 29 my @overlays; 30 while ($text =~ /$url/g) { 31 my $ndx = $-[0]; 32 my $href = $&; 33 my $col = 0; 34 for my $key (keys %$rowmap) { 35 my $value = $rowmap->{$key}; 36 my ($start, $end) = @$value; 37 if (($start <= $ndx) && ($end >= $ndx)) { 38 $row = $key; 39 $col = $ndx - $start; 40 last; 41 } 42 } 43 my @ov = ($col, $row, $href); 44 push(@overlays, \@ov) if ($row >= 0); 45 } 46 @overlays = reverse @overlays if ($self->{descending}); 47 for my $ov (@overlays) { 48 my ($col, $row, $href) = @$ov; 49 $num++; 50 my $overlay = $self->overlay( 51 $col, $row, $self->strwidth($num), 1, $label_rend, 0 52 ); 53 $overlay->set(0,0,$num); 54 $labels->{$num} = $overlay; 55 $hrefs->{$num} = $href; 56 } 57 }; 58 my ($brow, $bcol) = $self->selection_beg(); 59 my ($erow, $ecol) = $self->selection_end(); 60 my $issel = ($ecol > $bcol) || ($erow > $brow); 61 if ($issel) { # restrict to selection if one exists 62 ($row, $base_col) = ($brow - $self->view_start, $bcol); 63 for (split(/\n/, $self->selection())) { 64 my $start = length($text) - $base_col; 65 $text .= $_; 66 $rowmap->{$row} = [$start, (length($text)-1)]; 67 $base_col = 0; 68 $row++; 69 } 70 } else { # no selection, use visible terminal 71 for (0..($self->nrow - 1)) { 72 $row = $_; 73 my $start = length($text); 74 $text .= $self->ROW_t($row + $self->view_start); 75 $rowmap->{$row} = [$start, (length($text)-1)]; 76 } 77 } 78 $label_urls->(); 79 80 if ($num < 1) { 81 my $desc = $issel ? "in visible selected text" : "on visible screen"; 82 $self->status_msg("url-picker: no URLs found $desc"); 83 } else { 84 my $url_picker = {}; 85 $url_picker->{prompt} = $self->overlay( 86 0, -1, 8, 1, $self->get_rend("prompt", urxvt::OVERLAY_RSTYLE), 0 87 ); 88 $url_picker->{prompt}->set(0,0,"Follow:"); 89 $url_picker->{labels} = $labels; 90 $url_picker->{hrefs} = $hrefs; 91 $url_picker->{num} = $num; 92 $url_picker->{buffer} = ''; 93 my ($crow,$ccol) = $self->screen_cur; 94 $url_picker->{crow} = $crow; 95 $url_picker->{ccol} = $ccol; 96 $self->{url_picker} = $url_picker; 97 $self->update($url_picker); 98 } 99 } 100 () 101 } 102 103 sub on_key_press { 104 my ($self, $event, $keysym) = @_; 105 my $p = $self->{url_picker}; 106 if ($p) { 107 if ($keysym == 0xff1b) { # escape 108 $self->screen_cur($p->{crow},$p->{ccol}); 109 $self->{url_picker} = (); 110 } elsif ($keysym == 0xff08) { # backspace 111 if (length($p->{buffer}) > 0) { 112 $p->{buffer} = substr($p->{buffer},0,-1); 113 $self->update($p); 114 } 115 } elsif (($keysym >= 48) && ($keysym <= 57)) { 116 $p->{buffer} = $p->{buffer} . ($keysym - 48); 117 $self->update($p); 118 } elsif ($keysym == 0xff0d) { # CR 119 my $num = $p->{buffer}; 120 my $hrefs = $p->{hrefs}; 121 if (($num > 0) && ($num <= $p->{num})) { 122 my $href = $hrefs->{$num}; 123 $self->launch($href); 124 } 125 } 126 return 1; 127 } 128 () 129 } 130 131 sub update { 132 my ($self, $p) = @_; 133 $p->{typing} = $self->overlay( 134 8, -1, length($p->{buffer}), 1, $self->get_rend("input", urxvt::DEFAULT_RSTYLE), 0 135 ); 136 $p->{typing}->set(0,0,$p->{buffer}); 137 my $ndx = 0; 138 my $labels = $p->{labels}; 139 my $hrefs = $p->{hrefs}; 140 my $len = length($p->{buffer}); 141 my $size = $p->{num}; 142 my @matches; 143 while (++$ndx <= $size) { 144 my $overlay = $labels->{$ndx}; 145 if (($len == 0) || 146 (($len <= length($ndx)) && (substr($ndx,0,$len) eq $p->{buffer}))) { 147 $overlay->show; 148 unshift @matches,$hrefs->{$ndx}; 149 } else { 150 $overlay->hide; 151 } 152 } 153 if (scalar(@matches) == 1) { 154 $self->launch(@matches[0]); 155 } else { 156 $self->screen_cur($self->nrow,8+$len); 157 } 158 } 159 160 sub launch { 161 my ($self, $href) = @_; 162 my $p = $self->{url_picker}; 163 $self->screen_cur($p->{crow},$p->{ccol}); 164 $self->{url_picker} = (); 165 my $launcher = $self->{launcher}; 166 $self->status_msg($href); 167 $self->exec_async ($launcher,$href); 168 } 169 170 sub status_msg { 171 my ($self, $msg) = @_; 172 $self->{url_picker_msg} = $self->overlay(0,-1,length($msg),1,$self->get_rend("status",urxvt::OVERLAY_RSTYLE),0); 173 $self->{url_picker_msg}->set(0,0,$msg); 174 $self->{url_picker_timer} = urxvt::timer 175 ->new 176 ->after (5) 177 ->cb (sub { 178 $self->{url_picker_msg} = (); 179 $self->{url_pickertimer} = (); 180 }); 181 } 182 183 sub get_rend { 184 my ($self, $name, $default) = @_; 185 urxvt::SET_COLOR $default, 186 $self->my_resource("$name.foregroundColor") || urxvt::GET_BASEFG $default, 187 $self->my_resource("$name.backgroundColor") || urxvt::GET_BASEBG $default; 188 } 189 190 sub on_key_release { 191 my ($self, $event, $keysym) = @_; 192 $self->{url_picker}; 193 } 194 195 sub my_resource { 196 my ($self, $name) = @_; 197 $self->x_resource ("$self->{name}.$name"); 198 } 199 200 sub on_start { 201 my ($self) = @_; 202 203 ($self->{name} = __PACKAGE__) =~ s/.*:://; 204 $self->{name} =~ tr/_/-/; 205 $self->{launcher} = $self->my_resource("launcher") || 206 $self->x_resource("url-launcher") || 207 "sensible-browser"; 208 $self->{descending} = ($self->my_resource("order") eq "descending"); 209 $self->{url_picker} = (); 210 } 211 212 # vim:set sw=3 sts=3 et: