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;
141 # ------------------------------------------------------------------------
143 sub do_scan_for_urls {
148 my $row_start = $term->top_row;
149 my $row_end = $term->nrow;
151 for (my $row=$row_start; $row<=$row_end; $row++) {
153 # fetch the line that has changed
154 my $line = $term->line ($row);
157 # find all urls (if any)
158 while ($text =~ /$url_matcher/g) {
161 my %h = ( 'row' => $row,
163 'col_to' => $+[1] - 1,
169 # 0 for none, positive count otherwise
174 my ($self, $text, $timeout) = @_;
175 $timeout = $msg_timeout unless defined $timeout;
177 ov => $self->overlay (0, -1, length($text)+1, 1, urxvt::OVERLAY_RSTYLE, 1),
180 ->start (urxvt::NOW + $timeout)
185 $self->{msg}{ov}->set(0,0,$text);
188 sub on_user_command {
189 my ($term, $cmd) = @_;
191 if ($cmd eq "mark-and-yank:activate_mark_mode") {
192 activate_mark_mode($term);
194 } elsif ($cmd eq "mark-and-yank:activate_mark_url_mode") {
195 activate_mark_url_mode($term);
198 status_message ($term, "urxvt copy mode started");
203 # ------------------------------------------------------------------------
206 my ($term, $event, $keysym, $octets) = @_;
208 foreach my $key (keys %key2mod) {
209 if ($keysym == $key) {
210 $mod |= $key2mod{$key};
215 # ignore all input when we are active
216 $mark_mode_active && return 1;
222 my ($term, $event, $keysym) = @_;
224 foreach my $key (keys %key2mod) {
225 if ($keysym == $key) {
226 $mod &= ~$key2mod{$key};
231 return () unless ($mark_mode_active);
233 my $ch = chr($keysym);
235 if ($mod & urxvt::ShiftMask && $ch =~ m/[[:alpha:]]/) {
237 $mod &= ~urxvt::ShiftMask;
240 if (!$mod && $keysym == 65307) { # <esc>
241 deactivate_mark_mode ($term);
242 visual_mode_disable ($term, @cursor);
244 } elsif (($mod & urxvt::ControlMask) && $ch eq 'c') {# ^c - abort
245 deactivate_mark_mode ($term);
246 visual_mode_disable ($term, @cursor);
248 } elsif (!$mod && $keysym == 65293) { # <enter>
249 if ($first_mark_set) {
250 do_copy($term, @visual_start, @cursor);
252 deactivate_mark_mode ($term);
253 visual_mode_disable ($term, @cursor);
255 my %url = get_active_url($term);
260 visual_mode_enable ($term, 'v', @cursor);
262 my $urltext = $url{url};
264 $urltext =~ s/\(["|><&()]\)/\\$1/;
265 open_url($term, $urltext);
267 deactivate_mark_mode ($term);
268 visual_mode_disable ($term, @cursor);
272 } elsif (!$mod && $keysym == 32) { # <space>
273 if ($first_mark_set) {
274 do_copy($term, @visual_start, @cursor);
276 deactivate_mark_mode ($term);
277 visual_mode_disable ($term, @cursor);
282 visual_mode_enable ($term, 'v', @cursor);
285 } elsif (!$mod && $ch eq 'o') { # o - go to other end of region
286 if ($first_mark_set) {
287 my @dest = @visual_start;
288 @visual_start = @cursor;
291 $term->screen_cur (@dest);
295 } elsif (($mod & urxvt::ControlMask) && $ch eq 'w') {# w - copy the word under the cursor
296 my ($y1, $x1, $y2, $x2) = (@cursor, @cursor);
298 --$x1 while substr($term->ROW_t($y1), $x1 - 1, 1) =~ m/\w/;
299 ++$x2 while substr($term->ROW_t($y2), $x2 + 1, 1) =~ m/\w/;
301 do_copy($term, $y1, $x1, $y2, $x2);
303 deactivate_mark_mode ($term);
304 visual_mode_disable ($term, @cursor);
306 } elsif ($ch eq 'Y') { # Y - yank from cursor to EOL
307 do_copy($term, @cursor, $cursor[0], $term->ROW_l($cursor[0]) - $cursor[0]);
309 deactivate_mark_mode ($term);
310 visual_mode_disable ($term, @cursor);
312 } elsif (!$mod && $ch eq 'y') { # y - yank selected
313 my %url = get_active_url ($term);
315 do_copy($term, $url{row}, $url{col_from}, $url{row}, $url{col_to}) if %url;
317 deactivate_mark_mode ($term);
318 visual_mode_disable ($term, @cursor);
320 } elsif (($mod & urxvt::ControlMask) && (($ch eq 'n') || ($ch eq 'p'))) {
321 # ^n and ^p to cycle list
322 my $dir = ($ch eq 'n') ? 1 : -1;
323 move_url_highlight ($term, $dir);
324 visual_mode_disable ($term, @cursor);
326 } elsif (($mod & urxvt::ControlMask) && (($ch eq 'f') || ($ch eq 'b'))) {
327 # ^f and ^b to scroll
328 my $ofs = ($ch eq 'f') ? 1 : -1;
329 visual_mode_update ($term, \@cursor, [$cursor[0] + $ofs*($term->nrow - 1), $cursor[1]]);
331 } elsif (!$mod && $ch eq 'h') { # left
332 if ($cursor[1] > 0) {
333 visual_mode_update ($term, \@cursor, [$cursor[0], $cursor[1] - 1]);
336 } elsif (!$mod && $ch eq 'j') { # down
337 if ($cursor[0] < $term->nrow) {
338 visual_mode_update ($term, \@cursor, [$cursor[0] + 1, $cursor[1]]);
341 } elsif (!$mod && $ch eq 'k') { # up
342 if ($cursor[0] > $term->top_row) {
343 visual_mode_update ($term, \@cursor, [$cursor[0] - 1, $cursor[1]]);
346 } elsif (!$mod && $ch eq 'l') { # right
347 if ($cursor[1] < ($term->ncol - 1)) {
348 visual_mode_update ($term, \@cursor, [$cursor[0], $cursor[1] + 1]);
351 } elsif ($ch eq 'H') {
352 visual_mode_update ($term, \@cursor, [0, $cursor[1]]);
354 } elsif ($ch eq 'M') {
355 visual_mode_update ($term, \@cursor, [$term->nrow / 2, $cursor[1]]);
357 } elsif ($ch eq 'L') {
358 visual_mode_update ($term, \@cursor, [$term->nrow, $cursor[1]]);
360 } elsif ($ch eq '^') {
361 $term->ROW_t($cursor[0]) =~ m/^\s*/;
362 visual_mode_update ($term, \@cursor, [$cursor[0], $+[0]]);
364 } elsif ($ch eq '0') {
365 visual_mode_update ($term, \@cursor, [$cursor[0], 0]);
367 } elsif ($ch eq '$') {
368 visual_mode_update ($term, \@cursor, [$cursor[0], $term->ROW_l($cursor[0]) - 1]);
370 } elsif ($ch eq 'g') {
371 visual_mode_update ($term, \@cursor, [$term->top_row, 0]);
373 } elsif ($ch eq 'G') {
374 visual_mode_update ($term, \@cursor, [$term->nrow - 1, $term->ROW_l($term->nrow - 1) - 1]);
376 } elsif ($ch eq 'w') {
378 my $line = $term->ROW_t($dest[0]);
379 ++$dest[1] while substr($line, $dest[1], 1) =~ m/\w/;
380 until (substr($line, $dest[1], 1) =~ m/\w/) {
382 next if $dest[1] <= $term->ROW_l($dest[0]);
384 return 1 if $dest[0] >= $term->nrow;
386 $line = $term->ROW_t($dest[0]);
389 visual_mode_update ($term, \@cursor, \@dest);
391 } elsif ($ch eq 'e') {
393 my $line = $term->ROW_t($dest[0]);
395 ++$dest[1] while substr($line, $dest[1], 1) =~ m/\W/;
396 until (substr($line, $dest[1], 1) =~ m/\w/) {
398 next if $dest[1] <= $term->ROW_l($dest[0]);
400 return 1 if $dest[0] >= $term->nrow;
402 $line = $term->ROW_t($dest[0]);
404 ++$dest[1] while substr($line, $dest[1] + 1, 1) =~ m/\w/;
406 visual_mode_update ($term, \@cursor, \@dest);
408 } elsif ($ch eq 'b') {
410 my $line = $term->ROW_t($dest[0]);
411 # unless at the beginning of a word, jump to the beginning
412 --$dest[1] while ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\w/);
413 if ($dest[1] == $cursor[1]) { # at beginning of a word
414 # skip non-word characters
415 until ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\w/) {
417 next if $dest[1] > 0;
419 return 1 if $dest[0] < 0;
420 $dest[1] = $term->ROW_l($dest[0]);
421 $line = $term->ROW_t($dest[0]);
423 --$dest[1] while $dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\w/;
426 visual_mode_update ($term, \@cursor, \@dest);
428 } elsif ($ch eq 'W') {
430 my $line = $term->ROW_t($dest[0]);
431 ++$dest[1] while substr($line, $dest[1], 1) =~ m/\S/;
432 until (substr($line, $dest[1], 1) =~ m/\S/) {
434 next if $dest[1] <= $term->ROW_l($dest[0]);
436 return 1 if $dest[0] >= $term->nrow;
438 $line = $term->ROW_t($dest[0]);
441 visual_mode_update ($term, \@cursor, \@dest);
443 } elsif ($ch eq 'E') {
445 my $line = $term->ROW_t($dest[0]);
447 ++$dest[1] while substr($line, $dest[1], 1) =~ m/\s/;
448 until (substr($line, $dest[1], 1) =~ m/\S/) {
450 next if $dest[1] <= $term->ROW_l($dest[0]);
452 return 1 if $dest[0] >= $term->nrow;
454 $line = $term->ROW_t($dest[0]);
456 ++$dest[1] while substr($line, $dest[1] + 1, 1) =~ m/\S/;
458 visual_mode_update ($term, \@cursor, \@dest);
460 } elsif ($ch eq 'B') {
462 my $line = $term->ROW_t($dest[0]);
463 --$dest[1] while ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\S/);
464 until ($dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\S/) {
466 next if $dest[1] > 0;
468 return 1 if $dest[0] < 0;
469 $dest[1] = $term->ROW_l($dest[0]);
470 $line = $term->ROW_t($dest[0]);
472 --$dest[1] while $dest[1] >= 1 && substr($line, $dest[1] - 1, 1) =~ m/\S/;
474 visual_mode_update ($term, \@cursor, \@dest);
480 # ------------------------------------------------------------------------
484 my $max = $#url_db + 1;
486 return if $url_selected < 0 || $url_selected >= $max;
487 return if not defined $url_db[$url_selected];
489 return %{$url_db[$url_selected]};
495 my ($y1, $x1, $y2, $x2) = canonicalise_coordinates(@_);
497 $term->selection_beg($y1, $x1);
498 $term->selection_end($y2, 1 + $x2);
499 $term->selection_make(urxvt::CurrentTime);
501 my $text = $term->selection();
503 if ($term->{have_Clipboard}) {
504 Clipboard->copy($text);
506 my $pid = open(XCLIP, "|xclip -i");
511 status_message ($term, "copied ".length($text)." characters into clipboard");
514 # ------------------------------------------------------------------------
516 sub move_url_highlight {
517 my ($term, $dir) = @_;
518 my $max = $#url_db + 1;
520 do_highlight ($term, 0);
522 $url_selected = ($max + $url_selected + $dir) % $max;
524 do_highlight ($term, 1);
530 my ($term, $enable) = @_;
531 my $max = $#url_db + 1;
533 return if $url_selected < 0 || $url_selected >= $max;
534 return if not defined $url_db[$url_selected];
536 my $o = $url_db[$url_selected];
540 my $line = $term->line ($row);
546 for @{$rend}[ $h{col_from} .. $h{col_to}];
549 $term->view_start ( $row < 0 ? $row : 0 );
552 $_ &= ~urxvt::RS_RVid
553 for @{$rend}[ $h{col_from} .. $h{col_to}];
559 # ------------------------------------------------------------------------
561 sub visual_mode_enable {
562 my ($term, $mode, @cur) = @_;
564 $visual_mode = $mode;
565 @visual_start = @cur;
568 sub visual_mode_disable {
569 my ($term, @cursor) = @_;
572 $term->scr_xor_span(canonicalise_coordinates(@visual_start, @cursor));
580 sub visual_mode_update {
581 my ($term, $oldref, $newref) = @_;
587 $cursor[0] = clamp $term->top_row, $cursor[0], $term->nrow - 1;
589 if ($visual_mode eq 'v') {
590 $term->scr_xor_span(canonicalise_coordinates(@old, @new));
593 $term->screen_cur (@cursor);
595 $term->view_start (clamp $cursor[0] - $term->nrow + 1,
602 # ------------------------------------------------------------------------
604 sub activate_mark_mode {
607 print "title: " . ($term->resource("title")) . "\n";
608 print "name: " . ($term->resource("name")) . "\n";
609 print "term_name: " . ($term->resource("term_name")) . "\n";
610 print "color: " . ($term->resource("color")) . "\n";
612 my ($row, $col) = $term->screen_cur;
613 print "cursor: $row / $col \n";
615 $mark_mode_active = 1;
616 @backup_cursor = @cursor = $term->screen_cur;
619 sub activate_mark_url_mode {
622 if ($mark_mode_active) {
624 move_url_highlight ($term, -1);
626 } elsif ( do_scan_for_urls ($term) ) {
628 $term->{save_view_start} = $term->view_start;
630 move_url_highlight ($term, 0);
632 if ($url_selected > -1) {
633 $mark_mode_active = 1;
634 @backup_cursor = @cursor = $term->screen_cur;
639 sub deactivate_mark_mode {
642 do_highlight ($term, 0);
644 $mark_mode_active = 0;
645 $term->screen_cur (@backup_cursor);
649 $term->view_start ($term->{save_view_start});
653 # vim: set et ts=4 sw=4: