aboutsummaryrefslogtreecommitdiff
path: root/reselect
diff options
context:
space:
mode:
Diffstat (limited to 'reselect')
-rw-r--r--reselect234
1 files changed, 234 insertions, 0 deletions
diff --git a/reselect b/reselect
new file mode 100644
index 0000000..ba14191
--- /dev/null
+++ b/reselect
@@ -0,0 +1,234 @@
+#! perl
+
+# Author: Chip Camden <sterling@camdensoftware.com>
+
+my $re_url =
+ qr{
+ (?:https?://|ftp://|news://|mailto:|file://|\bwww\.)
+ [\w\-\@;\/?:&=%\$.+!*\x27,~#]*
+ (
+ \([\w\-\@;\/?:&=%\$.+!*\x27,~#]*\)| # Allow a pair of matched parentheses
+ [\w\-\@;\/?:&=%\$+*~] # exclude some trailing characters (heuristic)
+ )+
+ }x;
+
+sub find_matches {
+ my ($text, $rowmap) = @_;
+
+ my @overlays;
+ while ($text =~ /$re_url/g) {
+ my $ndx = $-[0];
+ my $href = $&;
+ my $col = 0;
+ my $row = 0;
+
+ for my $key (keys %$rowmap) {
+ my $value = $rowmap->{$key};
+ my ($start, $end) = @$value;
+ if (($start <= $ndx) && ($end >= $ndx)) {
+ $row = $key;
+ $col = $ndx - $start;
+ last;
+ }
+ }
+ my @ov = ($col, $row, $href);
+ push(@overlays, \@ov) if ($row >= 0);
+ }
+
+ return @overlays;
+}
+
+sub build_overlays {
+ my ($self, $text, $rowmap) = @_;
+
+ my $label_rend = $self->get_rend("label", urxvt::OVERLAY_RSTYLE);
+ my $num = 0;
+ my $labels = {};
+ my $hrefs = {};
+
+ my @overlays = find_matches($text, $rowmap);
+ @overlays = reverse @overlays if ($self->{descending});
+
+ for my $ov (@overlays) {
+ my ($col, $row, $href) = @$ov;
+ $num++;
+ my $overlay = $self->overlay($col, $row, $self->strwidth($num),
+ 1, $label_rend, 0);
+ $overlay->set(0, 0, $num);
+
+ $labels->{$num} = $overlay;
+ $hrefs->{$num} = $href;
+ }
+
+ return ($num, $labels, $hrefs)
+};
+
+sub on_user_command {
+ my ($self, $cmd) = @_;
+ if ($cmd =~ s/^reselect\b//) {
+ my $rowmap = {};
+ my $row = 0;
+ my $base_col = 0;
+ my $text = '';
+
+ my ($brow, $bcol) = $self->selection_beg();
+ my ($erow, $ecol) = $self->selection_end();
+ my $issel = ($ecol > $bcol) || ($erow > $brow);
+ if ($issel) { # restrict to selection if one exists
+ ($row, $base_col) = ($brow - $self->view_start, $bcol);
+ for (split(/\n/, $self->selection())) {
+ my $start = length($text) - $base_col;
+ $text .= $_;
+ $rowmap->{$row} = [$start, (length($text)-1)];
+ $base_col = 0;
+ $row++;
+ }
+ } else { # no selection, use visible terminal
+ for (0..($self->nrow - 1)) {
+ $row = $_;
+ my $start = length($text);
+ $text .= $self->ROW_t($row + $self->view_start);
+ $rowmap->{$row} = [$start, (length($text)-1)];
+ }
+ }
+
+ my ($num, $labels, $hrefs) = $self->build_overlays($text, $rowmap);
+
+ if ($num < 1) {
+ my $desc = $issel ? "in visible selected text" : "on visible screen";
+ $self->status_msg("url-picker: no URLs found $desc");
+ } else {
+ my $url_picker = {};
+ $url_picker->{prompt} = $self->overlay(
+ 0, -1, 8, 1, $self->get_rend("prompt", urxvt::OVERLAY_RSTYLE), 0
+ );
+ $url_picker->{prompt}->set(0,0,"Follow:");
+ $url_picker->{labels} = $labels;
+ $url_picker->{hrefs} = $hrefs;
+ $url_picker->{num} = $num;
+ $url_picker->{buffer} = '';
+ my ($crow,$ccol) = $self->screen_cur;
+ $url_picker->{crow} = $crow;
+ $url_picker->{ccol} = $ccol;
+ $self->{url_picker} = $url_picker;
+ $self->update($url_picker);
+ }
+ }
+ ()
+}
+
+sub on_key_press {
+ my ($self, $event, $keysym) = @_;
+ if ($self->{url_picker_msg}) {
+ $self->{url_picker_msg} = ();
+ }
+ my $p = $self->{url_picker};
+ if ($p) {
+ if ($keysym == 0xff1b || $keysym == 113) { # escape or q
+ $self->screen_cur($p->{crow},$p->{ccol});
+ $self->{url_picker} = ();
+ } elsif ($keysym == 0xff08) { # backspace
+ if (length($p->{buffer}) > 0) {
+ $p->{buffer} = substr($p->{buffer},0,-1);
+ $self->update($p);
+ }
+ } elsif (($keysym >= 48) && ($keysym <= 57)) {
+ $p->{buffer} = $p->{buffer} . ($keysym - 48);
+ $self->update($p);
+ } elsif ($keysym == 0xff0d) { # CR
+ my $num = $p->{buffer};
+ my $hrefs = $p->{hrefs};
+ if (($num > 0) && ($num <= $p->{num})) {
+ my $href = $hrefs->{$num};
+ $self->launch($href);
+ }
+ }
+ return 1;
+ }
+ ()
+}
+
+sub update {
+ my ($self, $p) = @_;
+ $p->{typing} = $self->overlay(
+ 8, -1, length($p->{buffer}), 1, $self->get_rend("input", urxvt::DEFAULT_RSTYLE), 0
+ );
+ $p->{typing}->set(0,0,$p->{buffer});
+ my $ndx = 0;
+ my $labels = $p->{labels};
+ my $hrefs = $p->{hrefs};
+ my $len = length($p->{buffer});
+ my $size = $p->{num};
+ my @matches;
+ while (++$ndx <= $size) {
+ my $overlay = $labels->{$ndx};
+ if (($len == 0) ||
+ (($len <= length($ndx)) && (substr($ndx,0,$len) eq $p->{buffer}))) {
+ $overlay->show;
+ unshift @matches,$hrefs->{$ndx};
+ } else {
+ $overlay->hide;
+ }
+ }
+ # Auto-launch a single url only if the launchsingle resource is true
+ # (default), or if some digits were input
+ if (scalar(@matches) == 1 && ($self->{launchsingle} || $len > 0)) {
+ $self->launch(@matches[0]);
+ } else {
+ $self->screen_cur($self->nrow,8+$len);
+ }
+}
+
+sub launch {
+ my ($self, $href) = @_;
+ my $p = $self->{url_picker};
+ $self->screen_cur($p->{crow},$p->{ccol});
+ $self->{url_picker} = ();
+ my $launcher = $self->{launcher};
+ $self->status_msg($href);
+ $self->exec_async ($launcher,$href);
+}
+
+sub status_msg {
+ my ($self, $msg) = @_;
+ $self->{url_picker_msg} = $self->overlay(0, -1, length($msg), 1,
+ $self->get_rend("status",urxvt::OVERLAY_RSTYLE), 0);
+ $self->{url_picker_msg}->set(0,0,$msg);
+ $self->{url_picker_timer} = urxvt::timer
+ ->new
+ ->after (5)
+ ->cb (sub {
+ $self->{url_picker_msg} = ();
+ $self->{url_pickertimer} = ();
+ });
+}
+
+sub get_rend {
+ my ($self, $name, $default) = @_;
+ urxvt::SET_COLOR $default,
+ $self->my_resource("$name.foregroundColor") || urxvt::GET_BASEFG $default,
+ $self->my_resource("$name.backgroundColor") || urxvt::GET_BASEBG $default;
+}
+
+sub on_key_release {
+ my ($self, $event, $keysym) = @_;
+ $self->{url_picker};
+}
+
+sub my_resource {
+ my ($self, $name) = @_;
+ $self->x_resource ("$self->{name}.$name");
+}
+
+sub on_start {
+ my ($self) = @_;
+
+ ($self->{name} = __PACKAGE__) =~ s/.*:://;
+ $self->{name} =~ tr/_/-/;
+ $self->{launcher} = $self->my_resource("launcher") ||
+ $self->x_resource("url-launcher") ||
+ "sensible-browser";
+ $self->{launchsingle} = ($self->my_resource("launchsingle") ne "false");
+ $self->{descending} = ($self->my_resource("order") eq "descending");
+ $self->{url_picker} = ();
+}