#! perl -w # Author: Bert Muennich # Website: http://www.github.com/muennich/urxvt-perls # License: GPLv2 # Use keyboard shortcuts to select and copy text. # Usage: put the following lines in your .Xdefaults: # URxvt.perl-ext-common: ...,keyboard-select # URxvt.keysym.M-Escape: perl:keyboard-select:activate # Use Meta-Escape to activate selection mode, then use the following keys: # h/j/k/l: move cursor left/down/up/right (also with arrow keys) # g/G/0/^/$: vi-like cursor movement keys # H/M/L: move cursor to first/center/last line of visible area # Ctrl-f/b: move cursor one screen down/up # Ctrl-d/u: move cursor half a screen down/up # v/V/Ctrl-v: toggle normal/linewise/blockwise selection # y,Return: copy selected text to primary buffer and quit selection mode # Escape: cancel whole keyboard selection mode use strict; sub on_user_command { my ($self, $cmd) = @_; if ($cmd eq 'keyboard-select:activate') { if (not $self->{active}) { activate($self); } } () } sub key_press { my ($self, $event, $keysym) = @_; my $char = chr($keysym); if ($keysym == 0xff1b) { # escape deactivate($self); } elsif ($char eq 'y' || $keysym == 0xff0d) { if ($self->{select}) { my ($br, $bc, $er, $ec) = calc_span($self); $self->selection_beg($br, $bc); $self->selection_end($er, $ec); $self->selection_make($event->{time}, $self->{select} eq 'b'); deactivate($self); } } elsif ($char eq 'V') { toggle_select($self, 'l'); } elsif ($char eq 'v') { if ($event->{state} & urxvt::ControlMask) { toggle_select($self, 'b'); } else { toggle_select($self, 'n'); } } elsif ($char eq 'k' || $keysym == 0xff52) { move_cursor($self, 'k'); } elsif ($char eq 'j' || $keysym == 0xff54) { move_cursor($self, 'j'); } elsif ($char eq 'h' || $keysym == 0xff51) { move_cursor($self, 'h'); } elsif ($char eq 'l' || $keysym == 0xff53) { move_cursor($self, 'l'); } elsif ('fbdu' =~ m/\Q$char\E/ && $event->{state} & urxvt::ControlMask) { move_cursor($self, $char); } elsif ('gG0^$HML' =~ m/\Q$char\E/) { move_cursor($self, $char); } return 1; } sub tt_write { return 1; } sub refresh { my ($self) = @_; if ($self->{select}) { my ($br, $bc, $er, $ec) = calc_span($self); if ($self->{select} eq 'b') { $self->scr_xor_rect($br, $bc, $er, $ec, urxvt::RS_RVid); } else { $self->scr_xor_span($br, $bc, $er, $ec, urxvt::RS_RVid); } if ($self->{select} ne 'l') { # make the cursor visible again $self->scr_xor_span($self->{cr}, $self->{cc}, $self->{cr}, $self->{cc} + 1, urxvt::RS_RVid); } } () } sub move_cursor { my ($self, $key) = @_; if ($self->{active}) { if ($key eq 'k' && $self->{cr} > $self->top_row) { --$self->{cr}; } elsif ($key eq 'j' && $self->{cr} < $self->nrow - 1) { ++$self->{cr}; } elsif ($key eq 'h' && $self->{cc} > 0) { --$self->{cc}; } elsif ($key eq 'l' && $self->{cc} < $self->ncol - 1) { ++$self->{cc}; } elsif ($key eq 'f' || $key eq 'd') { $self->{cr} += $key eq 'd' ? ($self->nrow - 1) / 2 : $self->nrow - 1; $self->{cr} = $self->nrow - 1 if $self->{cr} > $self->nrow; } elsif ($key eq 'b' || $key eq 'u') { $self->{cr} -= $key eq 'u' ? ($self->nrow - 1) / 2 : $self->nrow - 1; $self->{cr} = $self->top_row if $self->{cr} < $self->top_row; } elsif ($key eq 'g') { ($self->{cr}, $self->{cc}) = ($self->top_row, 0); } elsif ($key eq 'G') { ($self->{cr}, $self->{cc}) = ($self->nrow - 1, 0); } elsif ($key eq '0') { $self->{cc} = 0; } elsif ($key eq '^') { my $ltxt = $self->special_decode($self->line($self->{cr})->t); while ($ltxt =~ s/^( *)\t/$1 . " " x (8 - length($1) % 8)/e) {} $self->{cc} = $ltxt =~ m/^ +/ ? $+[0] : 0; } elsif ($key eq '$') { my $ltxt = $self->line($self->{cr})->t; $self->{cc} = ($ltxt =~ m/$/ ? $-[0] : $self->ncol) - 1; } elsif ($key eq 'H') { $self->{cr} = $self->view_start(); } elsif ($key eq 'M') { $self->{cr} = $self->view_start() + $self->nrow / 2; } elsif ($key eq 'L') { $self->{cr} = $self->view_start() + $self->nrow - 1; } $self->screen_cur($self->{cr}, $self->{cc}); # scroll the current cursor position into visible area if ($self->{cr} < $self->view_start()) { $self->view_start($self->{cr}); } elsif ($self->{cr} >= $self->view_start() + $self->nrow) { $self->view_start($self->{cr} - $self->nrow + 1); } $self->want_refresh(); } () } sub activate { my ($self) = @_; $self->{active} = 1; $self->{select} = ""; ($self->{cr}, $self->{cc}) = $self->screen_cur(); $self->{oldcr} = $self->{cr}; $self->{oldcc} = $self->{cc}; $self->selection_beg(1, 0); $self->selection_end(1, 0); $self->{view_start} = $self->view_start(); $self->{pty_ev_events} = $self->pty_ev_events(urxvt::EV_NONE); $self->enable( key_press => \&key_press, refresh_begin => \&refresh, refresh_end => \&refresh, tt_write => \&tt_write, ); () } sub deactivate { my ($self) = @_; $self->screen_cur($self->{oldcr}, $self->{oldcc}); $self->selection_beg(1, 0); $self->selection_end(1, 0); $self->disable("key_press", "refresh_begin", "refresh_end", "tt_write"); $self->view_start($self->{view_start}); $self->pty_ev_events($self->{pty_ev_events}); $self->want_refresh(); $self->{active} = 0; () } sub toggle_select { my ($self, $mode) = @_; if ($self->{active}) { if ($self->{select} eq $mode) { $self->{select} = ''; } else { if (not $self->{select}) { $self->{ar} = $self->{cr}; $self->{ac} = $self->{cc}; } $self->{select} = $mode; } $self->want_refresh(); } () } sub calc_span { my ($self) = @_; my ($br, $bc, $er, $ec); if ($self->{cr} < $self->{ar}) { $br = $self->{cr}; $bc = $self->{cc}; $er = $self->{ar}; $ec = $self->{ac}; } elsif ($self->{cr} > $self->{ar}) { $br = $self->{ar}; $bc = $self->{ac}; $er = $self->{cr}; $ec = $self->{cc}; } else { $br = $self->{cr}; $bc = $self->{cc} < $self->{ac} ? $self->{cc} : $self->{ac}; $er = $self->{cr}; $ec = $self->{cc} > $self->{ac} ? $self->{cc} : $self->{ac}; } if ($self->{select} eq 'l') { $bc = 0; $ec = $self->ncol; } else { if ($self->{select} eq 'b') { ($br, $er) = ($er, $br) if $br > $er; ($bc, $ec) = ($ec, $bc) if $bc > $ec; } ++$ec; } return ($br, $bc, $er, $ec); }