319 lines
7.4 KiB
Perl
319 lines
7.4 KiB
Perl
#! perl -w
|
|
# Author: Bert Muennich
|
|
# Website: http://www.github.com/muennich/urxvt-perls
|
|
# Version: 1.0
|
|
# 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/^/$/H/M/L: more vi-like cursor movement keys
|
|
# Ctrl-f/b: move cursor down/up one screen
|
|
# Ctrl-d/u: move cursor down/up half a screen
|
|
# v/V/Ctrl-v: toggle normal/linewise/blockwise selection
|
|
# y,Return: copy selected text to primary buffer and quit selection mode
|
|
# Escape: cancel 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);
|
|
}
|
|
|
|
status_area($self);
|
|
$self->want_refresh();
|
|
}
|
|
|
|
()
|
|
}
|
|
|
|
|
|
sub activate {
|
|
my ($self) = @_;
|
|
|
|
$self->{active} = 1;
|
|
|
|
$self->{select} = "";
|
|
$self->{overlay_len} = 0;
|
|
|
|
($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,
|
|
);
|
|
|
|
status_area($self);
|
|
|
|
()
|
|
}
|
|
|
|
|
|
sub deactivate {
|
|
my ($self) = @_;
|
|
|
|
$self->screen_cur($self->{oldcr}, $self->{oldcc});
|
|
|
|
$self->selection_beg(1, 0);
|
|
$self->selection_end(1, 0);
|
|
|
|
delete $self->{overlay} if $self->{overlay};
|
|
|
|
$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 status_area {
|
|
my ($self) = @_;
|
|
|
|
my $row = $self->{cr} < 0 ?
|
|
$self->{cr} - $self->top_row : abs($self->top_row) + $self->{cr};
|
|
my $text = sprintf("%d,%d ", $row + 1, $self->{cc} + 1);
|
|
|
|
if ($self->{select}) {
|
|
my ($br, $bc, $er, $ec) = calc_span($self);
|
|
if ($self->{select} eq 'b') {
|
|
$text .= sprintf("%dx%d ", $er - $br + 1, $ec - $bc);
|
|
} else {
|
|
if ($br == $er) {
|
|
$text .= sprintf("%d ", $ec - $bc);
|
|
} else {
|
|
$text .= sprintf("%d ", $er - $br + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($self->top_row == 0) {
|
|
$text .= "All";
|
|
} elsif ($self->view_start() == $self->top_row) {
|
|
$text .= "Top";
|
|
} elsif ($self->view_start() == 0) {
|
|
$text .= "Bot";
|
|
} else {
|
|
$text .= sprintf("%2d%",
|
|
($self->top_row - $self->view_start) * 100 / $self->top_row);
|
|
}
|
|
|
|
my $text_len = length($text);
|
|
|
|
if ($self->{overlay_len} != $text_len) {
|
|
delete $self->{overlay} if $self->{overlay};
|
|
$self->{overlay} = $self->overlay(-1, -1, $text_len, 1,
|
|
urxvt::OVERLAY_RSTYLE, 0);
|
|
$self->{overlay_len} = $text_len;
|
|
}
|
|
|
|
$self->{overlay}->set(0, 0, $self->special_encode($text));
|
|
$self->{overlay}->show();
|
|
|
|
()
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
status_area($self);
|
|
$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);
|
|
}
|