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
12 # This script is based on the mark-urls script from the rxvt-unicode
15 # ------------------------------------------------------------------------
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"
36 (?:https?://|ftp://|news://|mailto:|file://)[ab-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27(),~#]+
37 [ab-zA-Z0-9\-\@;\/?:&=%\$_+!*\x27()~] # exclude some trailing characters (heuristic)
40 sub canonicalise_coordinates {
41 if ($_[0] > $_[2] || ($_[0] == $_[2] && $_[1] > $_[3])) {
48 sub clamp { min( max( $_[0], $_[1] ), $_[2] ) }
53 $term->{have_Clipboard} = eval { require Clipboard; };
54 if ($term->{have_Clipboard}) {
58 $term->{browser} = $term->x_resource ("urlLauncher") || "x-www-browser";
64 my ($term, $url) = @_;
66 $term->exec_async (split(/[[:blank:]]/, $term->{browser}), $url);
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);
78 # find all urls (if any)
79 while ($text =~ /$url_matcher/g) {
82 # mark all characters as underlined. we _must_ not toggle underline,
83 # as we might get called on an already-marked url.
85 for @{$rend}[ $-[1] .. $+[1] - 1];
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);
106 while ($text =~ /$url_matcher/g) {
107 if ($-[1] <= $col && $+[1] >= $col) {
117 # ------------------------------------------------------------------------
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
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'
136 my $url_selected = -1;
138 my $first_mark_set = 0;
140 # ------------------------------------------------------------------------
142 sub do_scan_for_urls {
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);
156 # find all urls (if any)
157 while ($text =~ /$url_matcher/g) {
160 my %h = ( 'row' => $row,
162 'col_to' => $+[1] - 1,
168 # 0 for none, positive count otherwise
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);
185 # ------------------------------------------------------------------------
188 my ($term, $event, $keysym, $octets) = @_;
190 foreach my $key (keys %key2mod) {
191 if ($keysym == $key) {
192 $mod |= $key2mod{$key};
197 # ignore all input when we are active
198 $mark_mode_active && return 1;
204 my ($term, $event, $keysym) = @_;
206 foreach my $key (keys %key2mod) {
207 if ($keysym == $key) {
208 $mod &= ~$key2mod{$key};
213 return () unless ($mark_mode_active);
215 my $ch = chr($keysym);
217 if ($mod & urxvt::ShiftMask && $ch =~ m/[[:alpha:]]/) {
219 $mod &= ~urxvt::ShiftMask;
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);
233 my %url = get_active_url($term);
238 visual_mode_enable ($term, 'v', @cursor);
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);
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);
260 visual_mode_enable ($term, 'v', @cursor);
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;
269 $term->screen_cur (@dest);
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]);
314 } elsif (!$mod && $ch eq 'j') { # down
315 if ($cursor[0] < $term->nrow) {
316 visual_mode_update ($term, \@cursor, [$cursor[0] + 1, $cursor[1]]);
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]]);
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]);
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') {
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/) {
360 next if $dest[1] <= $term->ROW_l($dest[0]);
362 return 1 if $dest[0] >= $term->nrow;
364 $line = $term->ROW_t($dest[0]);
367 visual_mode_update ($term, \@cursor, \@dest);
369 } elsif ($ch eq 'e') {
371 my $line = $term->ROW_t($dest[0]);
373 ++$dest[1] while substr($line, $dest[1], 1) =~ m/\W/;
374 until (substr($line, $dest[1], 1) =~ m/\w/) {
376 next if $dest[1] <= $term->ROW_l($dest[0]);
378 return 1 if $dest[0] >= $term->nrow;
380 $line = $term->ROW_t($dest[0]);
382 ++$dest[1] while substr($line, $dest[1] + 1, 1) =~ m/\w/;
384 visual_mode_update ($term, \@cursor, \@dest);
386 } elsif ($ch eq 'b') {
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/) {
392 next if $dest[1] >= 0;
394 return 1 if $dest[0] <= 0;
395 $dest[1] = $term->ROW_l($dest[0]);
396 $line = $term->ROW_t($dest[0]);
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') {
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/) {
408 next if $dest[1] <= $term->ROW_l($dest[0]);
410 return 1 if $dest[0] >= $term->nrow;
412 $line = $term->ROW_t($dest[0]);
415 visual_mode_update ($term, \@cursor, \@dest);
417 } elsif ($ch eq 'E') {
419 my $line = $term->ROW_t($dest[0]);
421 ++$dest[1] while substr($line, $dest[1], 1) =~ m/\s/;
422 until (substr($line, $dest[1], 1) =~ m/\S/) {
424 next if $dest[1] <= $term->ROW_l($dest[0]);
426 return 1 if $dest[0] >= $term->nrow;
428 $line = $term->ROW_t($dest[0]);
430 ++$dest[1] while substr($line, $dest[1] + 1, 1) =~ m/\S/;
432 visual_mode_update ($term, \@cursor, \@dest);
434 } elsif ($ch eq 'B') {
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/) {
440 next if $dest[1] >= 0;
442 return 1 if $dest[0] <= 0;
443 $dest[1] = $term->ROW_l($dest[0]);
444 $line = $term->ROW_t($dest[0]);
446 --$dest[1] while $dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\S/;
448 visual_mode_update ($term, \@cursor, \@dest);
454 # ------------------------------------------------------------------------
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]};
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;
479 if ($term->{have_Clipboard}) {
480 Clipboard->copy($text);
482 $text =~ s/\(["|><&()]\)/\\$1/;
483 system ("echo -n \"$text\" | xclip -i");
487 # ------------------------------------------------------------------------
489 sub move_url_highlight {
490 my ($term, $dir) = @_;
491 my $max = $#url_db + 1;
493 do_highlight ($term, 0);
495 $url_selected = ($max + $url_selected + $dir) % $max;
497 do_highlight ($term, 1);
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];
513 my $line = $term->line ($row);
519 for @{$rend}[ $h{col_from} .. $h{col_to}];
522 $term->view_start ( $row < 0 ? $row : 0 );
525 $_ &= ~urxvt::RS_RVid
526 for @{$rend}[ $h{col_from} .. $h{col_to}];
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) = @_;
545 $term->scr_xor_span(canonicalise_coordinates(@visual_start, @cursor));
553 sub visual_mode_update {
554 my ($term, $oldref, $newref) = @_;
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));
566 $term->screen_cur (@cursor);
568 $term->view_start (clamp $cursor[0] - $term->nrow + 1,
575 # ------------------------------------------------------------------------
577 sub activate_mark_mode {
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 {
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;
612 sub deactivate_mark_mode {
615 do_highlight ($term, 0);
617 $mark_mode_active = 0;
618 $term->screen_cur (@backup_cursor);
622 $term->view_start ($term->{save_view_start});
626 # vim: set et ts=4 sw=4: