ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/rxvt-unicode/src/perl/matcher
Revision: 1.6
Committed: Fri Aug 19 23:08:35 2011 UTC (12 years, 8 months ago) by sf-tpope
Branch: MAIN
Changes since 1.5: +105 -5 lines
Log Message:
Keyboard accessible list of recent matches in matcher

File Contents

# Content
1 #! perl
2
3 # Author: Tim Pope <rxvt-unicodeNOSPAM@tpope.info>
4 # Bob Farrell <robertanthonyfarrell@gmail.com>
5
6 my $url =
7 qr{
8 (?:https?://|ftp://|news://|mailto:|file://|\bwww\.)
9 [a-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27,~#]*
10 (
11 \([a-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27,~#]*\)| # Allow a pair of matched parentheses
12 [a-zA-Z0-9\-\@;\/?:&=%\$_+*~] # exclude some trailing characters (heuristic)
13 )+
14 }x;
15
16 sub on_key_press {
17 my ($self, $event, $keysym, $octets) = @_;
18
19 if (! $self->{showing} ) {
20 return;
21 }
22
23 my $i = ($keysym == 96 ? 0 : $keysym - 48);
24 if (($i > scalar(@{$self->{urls}})) || ($i < 0)) {
25 $self->matchlist();
26 return;
27 }
28
29 my @args = ($self->{urls}[ -$i-1 ]);
30 $self->matchlist();
31
32 $self->exec_async( $self->{launcher}, @args );
33 }
34
35 sub on_user_command {
36 my ($self, $cmd) = @_;
37
38 if($cmd =~ s/^matcher:list\b//) {
39 $self->matchlist();
40 } else {
41 if($cmd =~ s/^matcher:last\b//) {
42 $self->most_recent;
43 }
44 # For backward compatibility
45 else {
46 if($cmd =~ s/^matcher\b//) {
47 $self->most_recent;
48 }
49 }
50 }
51 ()
52 }
53
54 sub matchlist {
55 my ($self) = @_;
56 if ( $self->{showing} ) {
57 $self->{url_overlay}->hide();
58 $self->{showing} = 0;
59 return;
60 }
61 @{$self->{urls}} = ();
62 my $line;
63 for (my $i = 0; $i < $self->nrow; $i ++) {
64 $line = $self->line($i);
65 next if ($line->beg != $i);
66 for my $url ($self->get_urls_from_line($line->t)) {
67 if (scalar(@{$self->{urls}}) == 10) {
68 shift @{$self->{urls}};
69 }
70 push @{$self->{urls}}, $url;
71 }
72 }
73
74 if (! scalar(@{$self->{urls}})) {
75 return;
76 }
77
78 my $max = 0;
79 my $i = scalar( @{$self->{urls}} ) - 1 ;;
80
81 my @temp = ();
82
83 for my $url (@{$self->{urls}}) {
84 my $url = "$i-$url";
85 my $xpos = 0;
86
87 if ($self->ncol + (length $url) >= $self->ncol) {
88 $url = substr( $url, 0, $self->ncol );
89 }
90
91 push @temp, $url;
92
93 if( length $url > $max ) {
94 $max = length $url;
95 }
96
97 $i--;
98 }
99
100 @temp = reverse @temp;
101
102 $self->{url_overlay} = $self->overlay(0, 0, $max, scalar( @temp ), urxvt::OVERLAY_RSTYLE, 2);
103 my $i = 0;
104 for my $url (@temp) {
105 $self->{url_overlay}->set( 0, $i, $url, [(urxvt::OVERLAY_RSTYLE) x length $url]);
106 $self->{showing} = 1;
107 $i++;
108 }
109
110 }
111
112 sub most_recent {
113 my ($self) = shift;
114 my $row = $self->nrow;
115 my @exec;
116 while($row-- > $self->top_row) {
117 @exec = $self->command_for($row);
118 last if(@exec);
119 }
120 if(@exec) {
121 return $self->exec_async (@exec);
122 }
123 ()
124 }
125
126 sub my_resource {
127 my $self = shift;
128 $self->x_resource ("$self->{name}.$_[0]");
129 }
130
131 # turn a rendition spec in the resource into a sub that implements it on $_
132 sub parse_rend {
133 my ($self, $str) = @_;
134 my ($mask, $fg, $bg, $failed) = $str ? urxvt::rend2mask($str)
135 : (urxvt::RS_Uline, undef, undef, []);
136 warn "Failed to parse rendition string: " . join(',', @$failed) if @$failed;
137 my @rend;
138 push @rend, sub { $_ |= $mask } if $mask;
139 push @rend, sub { $_ = urxvt::SET_FGCOLOR($_, $fg) } if defined $fg;
140 push @rend, sub { $_ = urxvt::SET_BGCOLOR($_, $bg) } if defined $bg;
141 sub {
142 for my $s ( @rend ) { &$s };
143 }
144 }
145
146 sub on_start {
147 my ($self) = @_;
148
149 ($self->{name} = __PACKAGE__) =~ s/.*:://;
150 $self->{name} =~ tr/_/-/;
151 $self->{launcher} = $self->my_resource("launcher") ||
152 $self->x_resource("urlLauncher") ||
153 "sensible-browser";
154
155 $self->{urls} = [];
156 $self->{showing} = 0;
157 $self->{button} = 2;
158 $self->{state} = 0;
159 if($self->{argv}[0] || $self->my_resource("button")) {
160 my @mods = split('', $self->{argv}[0] || $self->my_resource("button"));
161 for my $mod (@mods) {
162 if($mod =~ /^\d+$/) {
163 $self->{button} = $mod;
164 } elsif($mod eq "C") {
165 $self->{state} |= urxvt::ControlMask;
166 } elsif($mod eq "S") {
167 $self->{state} |= urxvt::ShiftMask;
168 } elsif($mod eq "M") {
169 $self->{state} |= $self->ModMetaMask;
170 } elsif($mod ne "-" && $mod ne " ") {
171 warn("$mod is invalid in $self->{name}<$self->{argv}[0]>\n");
172 }
173 }
174 }
175
176 my @defaults = ($url);
177 my @matchers;
178 for (my $idx = 0; defined (my $res = $self->my_resource("pattern.$idx") || $defaults[$idx]); $idx++) {
179 $res = $self->locale_decode ($res);
180 utf8::encode $res;
181 my $launcher = $self->my_resource("launcher.$idx");
182 $launcher =~ s/\$&|\$\{&\}/\${0}/g if ($launcher);
183 my $rend = $self->parse_rend($self->my_resource("rend.$idx"));
184 unshift @matchers, [qr($res)x,$launcher,$rend];
185 }
186 $self->{matchers} = \@matchers;
187
188 ()
189 }
190
191 sub get_urls_from_line {
192 my ($self, $line) = @_;
193 my @urls;
194 for my $matcher (@{$self->{matchers}}) {
195 while ($line =~ /$matcher->[0]/g) {
196 push @urls, substr( $line, $-[0], $+[0] - $-[0] );
197 }
198 }
199 return @urls;
200 }
201
202 sub on_line_update {
203 my ($self, $row) = @_;
204
205 # fetch the line that has changed
206 my $line = $self->line ($row);
207 my $text = $line->t;
208 my $i = 0;
209
210 # find all urls (if any)
211 for my $matcher (@{$self->{matchers}}) {
212 while ($text =~ /$matcher->[0]/g) {
213 #print "$&\n";
214 my $rend = $line->r;
215
216 # mark all characters as underlined. we _must_ not toggle underline,
217 # as we might get called on an already-marked url.
218 &{$matcher->[2]}
219 for @{$rend}[ $-[0] .. $+[0] - 1];
220
221 $line->r ($rend);
222 }
223 }
224
225 ()
226 }
227
228 sub valid_button {
229 my ($self, $event) = @_;
230 my $mask = $self->ModLevel3Mask | $self->ModMetaMask
231 | urxvt::ShiftMask | urxvt::ControlMask;
232 return ($event->{button} == $self->{button} &&
233 ($event->{state} & $mask) == $self->{state});
234 }
235
236 sub command_for {
237 my ($self, $row, $col) = @_;
238 my $line = $self->line ($row);
239 my $text = $line->t;
240
241 for my $matcher (@{$self->{matchers}}) {
242 my $launcher = $matcher->[1] || $self->{launcher};
243 while (($text =~ /$matcher->[0]/g)) {
244 my $match = $&;
245 my @begin = @-;
246 my @end = @+;
247 if (!defined($col) || ($-[0] <= $col && $+[0] >= $col)) {
248 if ($launcher !~ /\$/) {
249 return ($launcher,$match);
250 } else {
251 # It'd be nice to just access a list like ($&,$1,$2...),
252 # but alas, m//g behaves differently in list context.
253 my @exec = map { s/\$(\d+)|\$\{(\d+)\}/
254 substr($text,$begin[$1||$2],$end[$1||$2]-$begin[$1||$2])
255 /egx; $_ } split(/\s+/, $launcher);
256 return @exec;
257 }
258 }
259 }
260 }
261
262 ()
263 }
264
265 sub on_button_press {
266 my ($self, $event) = @_;
267 if($self->valid_button($event)
268 && (my @exec = $self->command_for($event->{row},$event->{col}))) {
269 $self->{row} = $event->{row};
270 $self->{col} = $event->{col};
271 $self->{cmd} = \@exec;
272 return 1;
273 } else {
274 delete $self->{row};
275 delete $self->{col};
276 delete $self->{cmd};
277 }
278
279 ()
280 }
281
282 sub on_button_release {
283 my ($self, $event) = @_;
284
285 my $row = delete $self->{row};
286 my $col = delete $self->{col};
287 my $cmd = delete $self->{cmd};
288
289 return if !defined $row;
290
291 if($row == $event->{row} && abs($col-$event->{col}) < 2
292 && join("\x00", @$cmd) eq join("\x00", $self->command_for($row,$col))) {
293 if($self->valid_button($event)) {
294
295 $self->exec_async (@$cmd);
296
297 }
298 }
299
300 1;
301 }
302
303 # vim:set sw=3 sts=3 et: