ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/rxvt-unicode/src/perl/matcher
Revision: 1.8
Committed: Wed Jun 6 15:06:41 2012 UTC (11 years, 11 months ago) by root
Branch: MAIN
Changes since 1.7: +13 -9 lines
Log Message:
*** empty log message ***

File Contents

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