ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/rxvt-unicode/src/perl/matcher
Revision: 1.9
Committed: Sun Jun 10 13:58:05 2012 UTC (11 years, 11 months ago) by root
Branch: MAIN
Changes since 1.8: +2 -5 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 root 1.9 $_[0]->x_resource ("%.$_[1]")
134 root 1.1 }
135    
136 tpope 1.2 # turn a rendition spec in the resource into a sub that implements it on $_
137     sub parse_rend {
138     my ($self, $str) = @_;
139 sf-tpope 1.6 my ($mask, $fg, $bg, $failed) = $str ? urxvt::rend2mask($str)
140 tpope 1.2 : (urxvt::RS_Uline, undef, undef, []);
141     warn "Failed to parse rendition string: " . join(',', @$failed) if @$failed;
142     my @rend;
143     push @rend, sub { $_ |= $mask } if $mask;
144     push @rend, sub { $_ = urxvt::SET_FGCOLOR($_, $fg) } if defined $fg;
145     push @rend, sub { $_ = urxvt::SET_BGCOLOR($_, $bg) } if defined $bg;
146     sub {
147     for my $s ( @rend ) { &$s };
148     }
149     }
150    
151 root 1.1 sub on_start {
152     my ($self) = @_;
153    
154 root 1.8 $self->{launcher} = $self->my_resource ("launcher") || $self->x_resource("url-launcher") || "sensible-browser";
155 root 1.1
156 sf-tpope 1.6 $self->{urls} = [];
157     $self->{showing} = 0;
158 root 1.1 $self->{button} = 2;
159     $self->{state} = 0;
160 root 1.8 if($self->{argv}[0] || $self->my_resource ("button")) {
161     my @mods = split '', $self->{argv}[0] || $self->my_resource ("button");
162 root 1.1 for my $mod (@mods) {
163     if($mod =~ /^\d+$/) {
164     $self->{button} = $mod;
165     } elsif($mod eq "C") {
166     $self->{state} |= urxvt::ControlMask;
167     } elsif($mod eq "S") {
168     $self->{state} |= urxvt::ShiftMask;
169     } elsif($mod eq "M") {
170     $self->{state} |= $self->ModMetaMask;
171     } elsif($mod ne "-" && $mod ne " ") {
172 root 1.9 warn("$mod is invalid in $self->{_name}<$self->{argv}[0]>\n");
173 root 1.1 }
174     }
175     }
176    
177     my @defaults = ($url);
178     my @matchers;
179 root 1.8 for (my $idx = 0; defined (my $res = $self->my_resource ("pattern.$idx") || $defaults[$idx]); $idx++) {
180 root 1.1 $res = $self->locale_decode ($res);
181     utf8::encode $res;
182 root 1.8 my $launcher = $self->my_resource ("launcher.$idx");
183     $launcher =~ s/\$&|\$\{&\}/\${0}/g if $launcher;
184     my $rend = $self->parse_rend($self->my_resource ("rend.$idx"));
185 tpope 1.2 unshift @matchers, [qr($res)x,$launcher,$rend];
186 root 1.1 }
187     $self->{matchers} = \@matchers;
188    
189     ()
190     }
191    
192 sf-tpope 1.6 sub get_urls_from_line {
193     my ($self, $line) = @_;
194     my @urls;
195     for my $matcher (@{$self->{matchers}}) {
196     while ($line =~ /$matcher->[0]/g) {
197     push @urls, substr( $line, $-[0], $+[0] - $-[0] );
198     }
199     }
200     return @urls;
201     }
202    
203 root 1.1 sub on_line_update {
204     my ($self, $row) = @_;
205    
206     # fetch the line that has changed
207     my $line = $self->line ($row);
208     my $text = $line->t;
209     my $i = 0;
210    
211     # find all urls (if any)
212     for my $matcher (@{$self->{matchers}}) {
213     while ($text =~ /$matcher->[0]/g) {
214 tpope 1.2 #print "$&\n";
215 root 1.1 my $rend = $line->r;
216    
217     # mark all characters as underlined. we _must_ not toggle underline,
218     # as we might get called on an already-marked url.
219 tpope 1.2 &{$matcher->[2]}
220 root 1.1 for @{$rend}[ $-[0] .. $+[0] - 1];
221    
222     $line->r ($rend);
223     }
224     }
225    
226     ()
227     }
228    
229     sub valid_button {
230     my ($self, $event) = @_;
231     my $mask = $self->ModLevel3Mask | $self->ModMetaMask
232     | urxvt::ShiftMask | urxvt::ControlMask;
233     return ($event->{button} == $self->{button} &&
234     ($event->{state} & $mask) == $self->{state});
235     }
236    
237     sub command_for {
238     my ($self, $row, $col) = @_;
239     my $line = $self->line ($row);
240     my $text = $line->t;
241    
242     for my $matcher (@{$self->{matchers}}) {
243     my $launcher = $matcher->[1] || $self->{launcher};
244     while (($text =~ /$matcher->[0]/g)) {
245     my $match = $&;
246     my @begin = @-;
247     my @end = @+;
248 tpope 1.2 if (!defined($col) || ($-[0] <= $col && $+[0] >= $col)) {
249 root 1.1 if ($launcher !~ /\$/) {
250     return ($launcher,$match);
251     } else {
252     # It'd be nice to just access a list like ($&,$1,$2...),
253     # but alas, m//g behaves differently in list context.
254     my @exec = map { s/\$(\d+)|\$\{(\d+)\}/
255     substr($text,$begin[$1||$2],$end[$1||$2]-$begin[$1||$2])
256     /egx; $_ } split(/\s+/, $launcher);
257     return @exec;
258     }
259     }
260     }
261     }
262    
263     ()
264     }
265    
266     sub on_button_press {
267     my ($self, $event) = @_;
268 sf-tpope 1.5 if($self->valid_button($event)
269     && (my @exec = $self->command_for($event->{row},$event->{col}))) {
270 root 1.1 $self->{row} = $event->{row};
271     $self->{col} = $event->{col};
272 sf-tpope 1.5 $self->{cmd} = \@exec;
273     return 1;
274 root 1.1 } else {
275     delete $self->{row};
276     delete $self->{col};
277 sf-tpope 1.5 delete $self->{cmd};
278 root 1.1 }
279    
280     ()
281     }
282    
283     sub on_button_release {
284     my ($self, $event) = @_;
285    
286     my $row = delete $self->{row};
287     my $col = delete $self->{col};
288 sf-tpope 1.5 my $cmd = delete $self->{cmd};
289 root 1.1
290 sf-tpope 1.5 return if !defined $row;
291    
292     if($row == $event->{row} && abs($col-$event->{col}) < 2
293     && join("\x00", @$cmd) eq join("\x00", $self->command_for($row,$col))) {
294 root 1.1 if($self->valid_button($event)) {
295    
296 sf-tpope 1.5 $self->exec_async (@$cmd);
297 root 1.1
298     }
299     }
300    
301 sf-tpope 1.5 1;
302 root 1.1 }
303    
304     # vim:set sw=3 sts=3 et: