revert changes to url_matches lost in last couple of commits
[rxvt-unicode-script-mark-and-yank.git] / mark-and-yank
blobe51c12dbc459a39d00c610235b472fc851d8c0ad
1 #! perl
2 # ------------------------------------------------------------------------
3 # Author: Bart Trojanowski <bart@jukie.net>
5 # This script intends to provide functionality similar to screen's 
6 # copy-mode, but instead copy to the X clipboard.  In order to copy to
7 # the clipboard we need either the Clipboard.pm from CPAN or the xclip
8 # command line utility.
10 # More details here: http://www.jukie.net/~bart/blog/tag/urxvt
11
12 # This script is based on the mark-urls script from the rxvt-unicode
13 # distribution.
15 # ------------------------------------------------------------------------
16 # configuration
18 # Put this in your .Xdefaults
20 # URxvt.keysym.M-y: perl:mark-and-yank:activate_mark_mode
21 # URxvt.keysym.M-u: perl:mark-and-yank:activate_mark_url_mode
22 # URxvt.perl-lib: /home/jukie/bart/.urxvt/
23 # URxvt.perl-ext: mark-and-yank
24 # URxvt.urlLauncher: firefox
26 # you might have to edit the perl-lib line.
28 # ------------------------------------------------------------------------
30 use List::Util qw(first max maxstr min minstr reduce shuffle sum);
33 # same url as used in "selection"
34 my $url_matcher =
35    qr{(
36       (?:https?://|ftp://|news://|mailto:|file://)[ab-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27(),~#]+
37       [ab-zA-Z0-9\-\@;\/?:&=%\$_+!*\x27()~]   # exclude some trailing characters (heuristic)
38    )}x;
40 sub canonicalise_coordinates {
41     if ($_[0] > $_[2] || ($_[0] == $_[2] && $_[1] > $_[3])) {
42         (@_[2,3],@_[0,1])
43     } else {
44         (@_);
45     }
48 sub clamp { min( max( $_[0], $_[1] ), $_[2] ) }
50 sub on_start {
51     my ($term) = @_;
53     $term->{have_Clipboard} = eval { require Clipboard; };
54     if ($term->{have_Clipboard}) {
55         import Clipboard;
56     }
58     $term->{browser} = $term->x_resource ("urlLauncher") || "x-www-browser";
60     ()
63 sub open_url {
64     my ($term, $url) = @_;
66     $term->exec_async (split(/[[:blank:]]/, $term->{browser}), $url);
69 sub on_line_update {
70     my ($term, $row) = @_;
72     return unless ($term->x_resource ("underlineURLs") eq "true");
74     # fetch the line that has changed
75     my $line = $term->line ($row);
76     my $text = $line->t;
78     # find all urls (if any)
79     while ($text =~ /$url_matcher/g) {
80         my $rend = $line->r;
82         # mark all characters as underlined. we _must_ not toggle underline,
83         # as we might get called on an already-marked url.
84         $_ |= urxvt::RS_Uline
85         for @{$rend}[ $-[1] .. $+[1] - 1];
87         $line->r ($rend);
88     }
90     ()
93 sub on_button_release {
94     my ($term, $event) = @_;
96     my $mask = $term->ModLevel3Mask | $term->ModMetaMask
97     | urxvt::ShiftMask | urxvt::ControlMask;
99     if ($event->{button} == 2 && ($event->{state} & $mask) == 0) {
100         my $row = $event->{row};
101         my $col = $event->{col};
103         my $line = $term->line ($row);
104         my $text = $line->t;
106         while ($text =~ /$url_matcher/g) {
107             if ($-[1] <= $col && $+[1] >= $col) {
108                 open_url($term, $1);
109                 return 1;
110             }
111         }
112     }
114     ()
117 # ------------------------------------------------------------------------
119 my %key2mod = (
120     65505 => urxvt::ShiftMask,
121     65507 => urxvt::ControlMask,
122     65513 => urxvt::Mod1Mask,   # Alt
123     65514 => urxvt::Mod1Mask,   # Alt
124     65515 => urxvt::Mod4Mask,   # Super
125     65516 => urxvt::Mod4Mask,   # Super
127 my $mod = 0;
129 #my %mod = ( 'control' => 0, 'shift' => 0 );
131 my $mark_mode_active = 0;
132 my @backup_cursor = ();
133 my @visual_start = ();
134 my $visual_mode = 0;        # 'v', 'V', or '^v'
135 my @cursor = ();
136 my $url_selected = -1;
137 my @url_db = ();
138 my $first_mark_set = 0;
140 # ------------------------------------------------------------------------
142 sub do_scan_for_urls {
143     my ($term) = @_;
145     @url_db = ();
147     my $row_start = $term->top_row;
148     my $row_end = $term->nrow;
150     for (my $row=$row_start; $row<=$row_end; $row++) {
152         # fetch the line that has changed
153         my $line = $term->line ($row);
154         my $text = $line->t;
156         # find all urls (if any)
157         while ($text =~ /$url_matcher/g) {
158             my $url = $1;
160             my %h = ( 'row'      => $row,
161                       'col_from' => $-[1], 
162                       'col_to'   => $+[1] - 1,
163                       'url'      => $url);
164             push @url_db, \%h;
165         }
166     }
168     # 0 for none, positive count otherwise
169     return $#url_db + 1;
172 sub on_user_command {
173     my ($term, $cmd) = @_;
175     if ($cmd eq "mark-and-yank:activate_mark_mode") {
176         activate_mark_mode($term);
178     } elsif ($cmd eq "mark-and-yank:activate_mark_url_mode") {
179         activate_mark_url_mode($term);
180     }
182     ()
185 # ------------------------------------------------------------------------
187 sub on_key_press {
188     my ($term, $event, $keysym, $octets) = @_;
190     foreach my $key (keys %key2mod) {
191         if ($keysym == $key) {
192             $mod |= $key2mod{$key};
193             return ();
194         }
195     }
197     # ignore all input when we are active
198     $mark_mode_active && return 1;
200     ()
203 sub on_key_release {
204     my ($term, $event, $keysym) = @_;
206     foreach my $key (keys %key2mod) {
207         if ($keysym == $key) {
208             $mod &= ~$key2mod{$key};
209             return ();
210         }
211     }
213     return () unless ($mark_mode_active);
215     my $ch = chr($keysym);
217     if ($mod & urxvt::ShiftMask && $ch =~ m/[[:alpha:]]/) {
218         $ch = uc $ch;
219         $mod &= ~urxvt::ShiftMask;
220     }
222     if (!$mod && $keysym == 65307) {                     # <esc>
223         deactivate_mark_mode ($term);
224         visual_mode_disable ($term, @cursor);
226     } elsif (!$mod && $keysym == 65293) {                # <enter>
227         if ($first_mark_set) {
228             do_copy($term, @visual_start, @cursor);
230             deactivate_mark_mode ($term);
231             visual_mode_disable ($term, @cursor);
232         } else {
233             my %url = get_active_url($term);
235             if (not %url) {
236                 $first_mark_set = 1;
238                 visual_mode_enable ($term, 'v', @cursor);
239             } else {
240                 my $urltext = $url{url};
242                 $urltext =~ s/\(["|><&()]\)/\\$1/;
243                 open_url($term, $urltext);
245                 deactivate_mark_mode ($term);
246                 visual_mode_disable ($term, @cursor);
247             }
248         }
250     } elsif (!$mod && $keysym == 32) {                   # <space>
251         if ($first_mark_set) {
252             do_copy($term, @visual_start, @cursor);
254             deactivate_mark_mode ($term);
255             visual_mode_disable ($term, @cursor);
256         } else {
258             $first_mark_set = 1;
260             visual_mode_enable ($term, 'v', @cursor);
261         }
263     } elsif (!$mod && $ch eq 'o') {                      # o - go to other end of region
264         if ($first_mark_set) {
265             my @dest = @visual_start;
266             @visual_start = @cursor;
267             @cursor = @dest;
269             $term->screen_cur (@dest);
270             $term->want_refresh;
271         }
273     } elsif (($mod & urxvt::ControlMask) && $ch eq 'w') {# w - copy the word under the cursor
274         my ($y1, $x1, $y2, $x2) = (@cursor, @cursor);
276         --$x1 while substr($term->ROW_t($y1), $x1 - 1, 1) =~ m/\w/;
277         ++$x2 while substr($term->ROW_t($y2), $x2 + 1, 1) =~ m/\w/;
279         do_copy($term, $y1, $x1, $y2, $x2);
281         deactivate_mark_mode ($term);
282         visual_mode_disable ($term, @cursor);
284     } elsif ($ch eq 'Y') {                               # Y - yank from cursor to EOL
285         do_copy($term, @cursor, $cursor[0], $term->ROW_l($cursor[0]) - $cursor[0]);
287         deactivate_mark_mode ($term);
288         visual_mode_disable ($term, @cursor);
290     } elsif (!$mod && $ch eq 'y') {                      # y - yank selected
291         my %url = get_active_url ($term);
293         do_copy($term, $url{row}, $url{col_from}, $url{row}, $url{col_to}) if %url;
295         deactivate_mark_mode ($term);
296         visual_mode_disable ($term, @cursor);
298     } elsif (($mod & urxvt::ControlMask) && (($ch eq 'n') || ($ch eq 'p'))) {
299                                                 # ^n and ^p to cycle list
300         my $dir = ($ch eq 'n') ? 1 : -1;
301         move_url_highlight ($term, $dir);
302         visual_mode_disable ($term, @cursor);
304     } elsif (($mod & urxvt::ControlMask) && (($ch eq 'f') || ($ch eq 'b'))) {
305                                                 # ^f and ^b to scroll
306         my $ofs = ($ch eq 'f') ? 1 : -1;
307         visual_mode_update ($term, \@cursor, [$cursor[0] + $ofs*($term->nrow - 1), $cursor[1]]);
309     } elsif (!$mod && $ch eq 'h') {                       # left
310         if ($cursor[1] > 0) {
311             visual_mode_update ($term, \@cursor, [$cursor[0], $cursor[1] - 1]);
312         }
314     } elsif (!$mod && $ch eq 'j') {                  # down
315         if ($cursor[0] < $term->nrow) {
316             visual_mode_update ($term, \@cursor, [$cursor[0] + 1, $cursor[1]]);
317         }
319     } elsif (!$mod && $ch eq 'k') {                  # up
320         if ($cursor[0] > $term->top_row) {
321             visual_mode_update ($term, \@cursor, [$cursor[0] - 1, $cursor[1]]);
322         }
324     } elsif (!$mod && $ch eq 'l') {                  # right
325         if ($cursor[1] < ($term->ncol - 1)) {
326             visual_mode_update ($term, \@cursor, [$cursor[0], $cursor[1] + 1]);
327         }
329     } elsif ($ch eq 'H') {
330         visual_mode_update ($term, \@cursor, [0, $cursor[1]]);
332     } elsif ($ch eq 'M') {
333         visual_mode_update ($term, \@cursor, [$term->nrow / 2, $cursor[1]]);
335     } elsif ($ch eq 'L') {
336         visual_mode_update ($term, \@cursor, [$term->nrow, $cursor[1]]);
338     } elsif ($ch eq '^') {
339         $term->ROW_t($cursor[0]) =~ m/^\s*/;
340         visual_mode_update ($term, \@cursor, [$cursor[0], $+[0]]);
342     } elsif ($ch eq '0') {
343         visual_mode_update ($term, \@cursor, [$cursor[0], 0]);
345     } elsif ($ch eq '$') {
346         visual_mode_update ($term, \@cursor, [$cursor[0], $term->ROW_l($cursor[0]) - 1]);
348     } elsif ($ch eq 'g') {
349         visual_mode_update ($term, \@cursor, [$term->top_row, 0]);
351     } elsif ($ch eq 'G') {
352         visual_mode_update ($term, \@cursor, [$term->nrow - 1, $term->ROW_l($term->nrow - 1) - 1]);
354     } elsif ($ch eq 'w') {
355         my @dest = @cursor;
356         my $line = $term->ROW_t($dest[0]);
357         ++$dest[1] while substr($line, $dest[1], 1) =~ m/\w/;
358         until (substr($line, $dest[1], 1) =~ m/\w/) {
359             ++$dest[1];
360             next if $dest[1] <= $term->ROW_l($dest[0]);
361             ++$dest[0];
362             return 1 if $dest[0] >= $term->nrow;
363             $dest[1] = 0;
364             $line = $term->ROW_t($dest[0]);
365         }
367         visual_mode_update ($term, \@cursor, \@dest);
369     } elsif ($ch eq 'e') {
370         my @dest = @cursor;
371         my $line = $term->ROW_t($dest[0]);
372         ++$dest[1];
373         ++$dest[1] while substr($line, $dest[1], 1) =~ m/\W/;
374         until (substr($line, $dest[1], 1) =~ m/\w/) {
375             ++$dest[1];
376             next if $dest[1] <= $term->ROW_l($dest[0]);
377             ++$dest[0];
378             return 1 if $dest[0] >= $term->nrow;
379             $dest[1] = 0;
380             $line = $term->ROW_t($dest[0]);
381         }
382         ++$dest[1] while substr($line, $dest[1] + 1, 1) =~ m/\w/;
384         visual_mode_update ($term, \@cursor, \@dest);
386     } elsif ($ch eq 'b') {
387         my @dest = @cursor;
388         my $line = $term->ROW_t($dest[0]);
389         --$dest[1] while ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\w/);
390         until ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\w/) {
391             --$dest[1];
392             next if $dest[1] >= 0;
393             --$dest[0];
394             return 1 if $dest[0] <= 0;
395             $dest[1] = $term->ROW_l($dest[0]);
396             $line = $term->ROW_t($dest[0]);
397         }
398         --$dest[1] while $dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\w/;
400         visual_mode_update ($term, \@cursor, \@dest);
402     } elsif ($ch eq 'W') {
403         my @dest = @cursor;
404         my $line = $term->ROW_t($dest[0]);
405         ++$dest[1] while substr($line, $dest[1], 1) =~ m/\S/;
406         until (substr($line, $dest[1], 1) =~ m/\S/) {
407             ++$dest[1];
408             next if $dest[1] <= $term->ROW_l($dest[0]);
409             ++$dest[0];
410             return 1 if $dest[0] >= $term->nrow;
411             $dest[1] = 0;
412             $line = $term->ROW_t($dest[0]);
413         }
415         visual_mode_update ($term, \@cursor, \@dest);
417     } elsif ($ch eq 'E') {
418         my @dest = @cursor;
419         my $line = $term->ROW_t($dest[0]);
420         ++$dest[1];
421         ++$dest[1] while substr($line, $dest[1], 1) =~ m/\s/;
422         until (substr($line, $dest[1], 1) =~ m/\S/) {
423             ++$dest[1];
424             next if $dest[1] <= $term->ROW_l($dest[0]);
425             ++$dest[0];
426             return 1 if $dest[0] >= $term->nrow;
427             $dest[1] = 0;
428             $line = $term->ROW_t($dest[0]);
429         }
430         ++$dest[1] while substr($line, $dest[1] + 1, 1) =~ m/\S/;
432         visual_mode_update ($term, \@cursor, \@dest);
434     } elsif ($ch eq 'B') {
435         my @dest = @cursor;
436         my $line = $term->ROW_t($dest[0]);
437         --$dest[1] while ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\S/);
438         until ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\S/) {
439             --$dest[1];
440             next if $dest[1] >= 0;
441             --$dest[0];
442             return 1 if $dest[0] <= 0;
443             $dest[1] = $term->ROW_l($dest[0]);
444             $line = $term->ROW_t($dest[0]);
445         }
446         --$dest[1] while $dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\S/;
448         visual_mode_update ($term, \@cursor, \@dest);
449     }
451     return 1;
454 # ------------------------------------------------------------------------
456 sub get_active_url {
457     my ($term) = @_;
458     my $max = $#url_db + 1;
460     return if $url_selected < 0 || $url_selected >= $max;
461     return if not defined $url_db[$url_selected];
463     return %{$url_db[$url_selected]};
466 sub do_copy {
467     my $term = shift;
469     my ($y1, $x1, $y2, $x2) = canonicalise_coordinates(@_);
471     $term->selection_beg($y1, $x1);
472     $term->selection_end($y2, 1 + $x2);
473     $term->selection_make(urxvt::CurrentTime);
475     my $text = $term->selection();
476     $text =~ s/\\/\\\\/g;
477     $text =~ s/"/\\"/g;
479     if ($term->{have_Clipboard}) {
480         Clipboard->copy($text);
481     } else {
482         $text =~ s/\(["|><&()]\)/\\$1/;
483         system ("echo -n \"$text\" | xclip -i");
484     }
487 # ------------------------------------------------------------------------
489 sub move_url_highlight {
490     my ($term, $dir) = @_;
491     my $max = $#url_db + 1;
493     do_highlight ($term, 0);
494     
495     $url_selected = ($max + $url_selected + $dir) % $max;
496         
497     do_highlight ($term, 1);
499     $term->want_refresh;
502 sub do_highlight {
503     my ($term, $enable) = @_;
504     my $max = $#url_db + 1;
506     return if $url_selected < 0 || $url_selected >= $max;
507     return if not defined $url_db[$url_selected];
509     my $o = $url_db[$url_selected];
510     my %h = %$o;
512     my $row = $h{row};
513     my $line = $term->line ($row);
514     my $text = $line->t;
515     my $rend = $line->r;
517     if ($enable) {
518         $_ |= urxvt::RS_RVid
519         for @{$rend}[ $h{col_from} .. $h{col_to}];
521         # make it visible
522         $term->view_start ( $row < 0 ? $row : 0 );
524     } else {
525         $_ &= ~urxvt::RS_RVid
526         for @{$rend}[ $h{col_from} .. $h{col_to}];
527     }
529     $line->r ($rend);
532 # ------------------------------------------------------------------------
534 sub visual_mode_enable {
535     my ($term, $mode, @cur) = @_;
537     $visual_mode = $mode;
538     @visual_start = @cur;
541 sub visual_mode_disable {
542     my ($term, @cursor) = @_;
544     if ($visual_mode) {
545         $term->scr_xor_span(canonicalise_coordinates(@visual_start, @cursor));
547         $visual_mode = 0;
549         $term->want_refresh;
550     }
553 sub visual_mode_update {
554     my ($term, $oldref, $newref) = @_;
555     my @old = @$oldref;
556     my @new = @$newref;
558     @cursor = @new;
560     $cursor[0] = clamp $term->top_row, $cursor[0], $term->nrow - 1;
562     if ($visual_mode eq 'v') {
563         $term->scr_xor_span(canonicalise_coordinates(@old, @new));
564     }
566     $term->screen_cur (@cursor);
568     $term->view_start (clamp $cursor[0] - $term->nrow + 1,
569                              $term->view_start,
570                              min 0, $cursor[0]);
572     $term->want_refresh;
575 # ------------------------------------------------------------------------
577 sub activate_mark_mode {
578     my ($term) = @_;
580     print "title:     " . ($term->resource("title")) . "\n";
581     print "name:      " . ($term->resource("name")) . "\n";
582     print "term_name: " . ($term->resource("term_name")) . "\n";
583     print "color:     " . ($term->resource("color")) . "\n";
585     my ($row, $col) = $term->screen_cur;
586     print "cursor:    $row / $col \n";
588     $mark_mode_active = 1;
589     @backup_cursor = @cursor = $term->screen_cur;
592 sub activate_mark_url_mode {
593     my ($term) = @_;
595     if ($mark_mode_active) {
597         move_url_highlight ($term, -1);
599     } elsif ( do_scan_for_urls ($term) ) {
601         $term->{save_view_start} = $term->view_start;
603         move_url_highlight ($term, 0);
605         if ($url_selected > -1) {
606             $mark_mode_active = 1;
607             @backup_cursor = @cursor = $term->screen_cur;
608         }
609     }
612 sub deactivate_mark_mode {
613     my ($term) = @_;
615     do_highlight ($term, 0);
617     $mark_mode_active = 0;
618     $term->screen_cur (@backup_cursor);
619     $first_mark_set = 0;
620     $url_selected = -1;
622     $term->view_start ($term->{save_view_start});
623     $term->want_refresh;
626 # vim: set et ts=4 sw=4: