dotfiles

My dotfiles, nothing really interesting to see ...
git clone https://git.onna.be/dotfiles.git
Log | Files | Refs | README

commit 5d14957877daa3a551b809d99db1ad4f16f228dc
parent bd20d00a0bbb2ad1ab61c3c2809bb546d8882918
Author: Paco Esteban <paco@onna.be>
Date:   Mon, 25 Jun 2018 13:59:09 +0200

urxvt extensions

Diffstat:
urxvt/.urxvt/ext/clipboard | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
urxvt/.urxvt/ext/keyboard-select | 567+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
urxvt/.urxvt/ext/url-select | 375+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1051 insertions(+), 0 deletions(-)

diff --git a/urxvt/.urxvt/ext/clipboard b/urxvt/.urxvt/ext/clipboard @@ -0,0 +1,109 @@ +#! perl -w +# Author: Bert Muennich +# Website: http://www.github.com/muennich/urxvt-perls +# License: GPLv2 + +# Use keyboard shortcuts to copy the selection to the clipboard and to paste +# the clipboard contents (optionally escaping all special characters). +# Requires xsel to be installed! + +# Usage: put the following lines in your .Xdefaults/.Xresources: +# URxvt.perl-ext-common: ...,clipboard +# URxvt.keysym.M-c: perl:clipboard:copy +# URxvt.keysym.M-v: perl:clipboard:paste +# URxvt.keysym.M-C-v: perl:clipboard:paste_escaped + +# Options: +# URxvt.clipboard.autocopy: If true, PRIMARY overwrites clipboard + +# You can also overwrite the system commands to use for copying/pasting. +# The default ones are: +# URxvt.clipboard.copycmd: xsel -ib +# URxvt.clipboard.pastecmd: xsel -ob +# If you prefer xclip, then put these lines in your .Xdefaults/.Xresources: +# URxvt.clipboard.copycmd: xclip -i -selection clipboard +# URxvt.clipboard.pastecmd: xclip -o -selection clipboard +# On Mac OS X, put these lines in your .Xdefaults/.Xresources: +# URxvt.clipboard.copycmd: pbcopy +# URxvt.clipboard.pastecmd: pbpaste + +# The use of the functions should be self-explanatory! + +use strict; + +sub on_start { + my ($self) = @_; + + $self->{copy_cmd} = $self->x_resource('clipboard.copycmd') || 'xsel -ib'; + $self->{paste_cmd} = $self->x_resource('clipboard.pastecmd') || 'xsel -ob'; + + if ($self->x_resource('clipboard.autocopy') eq 'true') { + $self->enable(sel_grab => \&sel_grab); + } + + () +} + +sub copy { + my ($self) = @_; + + if (open(CLIPBOARD, "| $self->{copy_cmd}")) { + my $sel = $self->selection(); + utf8::encode($sel); + print CLIPBOARD $sel; + close(CLIPBOARD); + } else { + print STDERR "error running '$self->{copy_cmd}': $!\n"; + } + + () +} + +sub paste { + my ($self) = @_; + + my $str = `$self->{paste_cmd}`; + if ($? == 0) { + $self->tt_paste($str); + } else { + print STDERR "error running '$self->{paste_cmd}': $!\n"; + } + + () +} + +sub paste_escaped { + my ($self) = @_; + + my $str = `$self->{paste_cmd}`; + if ($? == 0) { + $str =~ s/([!#\$%&\*\(\) ='"\\\|\[\]`~,<>\?])/\\\1/g; + $self->tt_paste($str); + } else { + print STDERR "error running '$self->{paste_cmd}': $!\n"; + } + + () +} + +sub on_user_command { + my ($self, $cmd) = @_; + + if ($cmd eq "clipboard:copy") { + $self->copy; + } elsif ($cmd eq "clipboard:paste") { + $self->paste; + } elsif ($cmd eq "clipboard:paste_escaped") { + $self->paste_escaped; + } + + () +} + +sub sel_grab { + my ($self) = @_; + + $self->copy; + + () +} diff --git a/urxvt/.urxvt/ext/keyboard-select b/urxvt/.urxvt/ext/keyboard-select @@ -0,0 +1,567 @@ +#! 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/.Xresources: +# URxvt.perl-ext-common: ...,keyboard-select +# URxvt.keysym.M-Escape: perl:keyboard-select:activate +# The following line overwrites the default Meta-s binding and allows to +# activate keyboard-select directly in backward search mode: +# URxvt.keysym.M-s: perl:keyboard-select:search + +# 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/f/F/;/,/w/W/b/B/e/E: More vi-like cursor movement keys +# '/'/?: Start forward/backward search +# n/N: Repeat last search, N: in reverse direction +# Ctrl-f/b: Scroll down/up one screen +# Ctrl-d/u: Scroll down/up half a screen +# v/V/Ctrl-v: Toggle normal/linewise/blockwise selection +# y/Return: Copy selection to primary buffer, Return: deactivate afterwards +# q/Escape: Deactivate keyboard selection mode + + +use strict; + +sub on_start{ + my ($self) = @_; + + $self->{patterns}{'w'} = qr/\w[^\w\s]|\W\w|\s\S/; + $self->{patterns}{'W'} = qr/\s\S/; + $self->{patterns}{'b'} = qr/.*(?:\w[^\w\s]|\W\w|\s\S)/; + $self->{patterns}{'B'} = qr/.*\s\S/; + $self->{patterns}{'e'} = qr/[^\w\s](?=\w)|\w(?=\W)|\S(?=\s|$)/; + $self->{patterns}{'E'} = qr/\S(?=\s|$)/; + + () +} + + +sub on_user_command { + my ($self, $cmd) = @_; + + if (not $self->{active}) { + if ($cmd eq 'keyboard-select:activate') { + activate($self); + } elsif ($cmd eq 'keyboard-select:search') { + activate($self, 1); + } + } + + () +} + + +sub key_press { + my ($self, $event, $keysym, $char) = @_; + my $key = chr($keysym); + + if (lc($key) eq 'c' && $event->{state} & urxvt::ControlMask) { + deactivate($self); + } elsif ($self->{search}) { + if ($keysym == 0xff1b) { + if ($self->{search_mode}) { + deactivate($self); + } else { + $self->{search} = ''; + status_area($self); + } + } elsif ($keysym == 0xff08) { + $self->{search} = substr($self->{search}, 0, -1); + if (not $self->{search} and $self->{search_mode}) { + deactivate($self); + } else { + status_area($self); + } + } elsif ($keysym == 0xff0d) { + my $txt = substr($self->{search}, 1); + if ($txt) { + $self->{pattern} = ($txt =~ m/[[:upper:]]/) ? qr/\Q$txt\E/ : + qr/\Q$txt\E/i; + } elsif ($self->{pattern}) { + delete $self->{pattern}; + } + $self->{search} = ''; + if (not find_next($self)) { + if ($self->{search_mode}) { + deactivate($self); + } else { + status_area($self); + } + } + } elsif (length($char) > 0) { + $self->{search} .= $self->locale_decode($char); + status_area($self); + } + } elsif ($self->{move_to}) { + if ($keysym == 0xff1b) { + $self->{move_to} = 0; + status_area($self); + } elsif (length($char) > 0) { + $self->{move_to} = 0; + $self->{patterns}{'f-1'} = qr/^.*\Q$key\E/; + $self->{patterns}{'f+1'} = qr/^.+?\Q$key\E/; + move_to($self, ';'); + status_area($self); + } + } elsif ($keysym == 0xff1b || lc($key) eq 'q') { + deactivate($self); + } elsif ($key eq 'y' || $keysym == 0xff0d) { + if ($self->{select}) { + if ($self->{select} eq 'b') { + $self->selection($self->{selection}); + $self->selection_grab($event->{time}); + } else { + my ($br, $bc, $er, $ec) = calc_span($self); + $ec = $self->line($er)->l if $self->{select} eq 'l'; + $self->selection_beg($br, $bc); + $self->selection_end($er, $ec); + $self->selection_make($event->{time}); + } + if ($key eq 'y') { + if ($self->{select} ne 'b') { + $self->selection_beg(1, 0); + $self->selection_end(1, 0); + } + $self->{select} = ''; + status_area($self); + $self->want_refresh(); + } else { + deactivate($self); + } + } + } elsif ($key eq 'V') { + toggle_select($self, 'l'); + } elsif ($key eq 'v') { + if ($event->{state} & urxvt::ControlMask) { + toggle_select($self, 'b'); + } else { + toggle_select($self, 'n'); + } + } elsif ($key eq 'k' || $keysym == 0xff52) { + move_cursor($self, 'k'); + } elsif ($key eq 'j' || $keysym == 0xff54) { + move_cursor($self, 'j'); + } elsif ($key eq 'h' || $keysym == 0xff51) { + move_cursor($self, 'h'); + } elsif ($key eq 'l' || $keysym == 0xff53) { + move_cursor($self, 'l'); + } elsif ('gG0^$HML' =~ m/\Q$key\E/ || + ('fbdu' =~ m/\Q$key\E/ && $event->{state} & urxvt::ControlMask)) { + move_cursor($self, $key); + } elsif (lc($key) eq 'f') { + $self->{move_to} = 1; + $self->{move_dir} = $key eq 'F' ? -1 : 1; + status_area($self, $key); + } elsif (';,wWbBeE' =~ m/\Q$key\E/) { + move_to($self, $key); + } elsif ($key eq '/' || $key eq '?') { + $self->{search} = $key; + $self->{search_dir} = $key eq '?' ? -1 : 1; + status_area($self); + } elsif (lc($key) eq 'n') { + find_next($self, $self->{search_dir} * ($key eq 'N' ? -1 : 1)); + } + + return 1; +} + + +sub move_cursor { + my ($self, $key) = @_; + my ($cr, $cc) = $self->screen_cur(); + my $line = $self->line($cr); + + if ($key eq 'k' && $line->beg > $self->top_row) { + $cr = $line->beg - 1; + } elsif ($key eq 'j' && $line->end < $self->nrow - 1) { + $cr = $line->end + 1; + } elsif ($key eq 'h' && $self->{offset} > 0) { + $self->{offset} = $line->offset_of($cr, $cc) - 1; + $self->{dollar} = 0; + } elsif ($key eq 'l' && $self->{offset} < $line->l - 1) { + ++$self->{offset}; + } elsif ($key eq 'f' || $key eq 'd') { + my $vs = $self->view_start() + + ($key eq 'd' ? $self->nrow / 2 : $self->nrow - 1); + $vs = 0 if $vs > 0; + $cr += $vs - $self->view_start($vs); + } elsif ($key eq 'b' || $key eq 'u') { + my $vs = $self->view_start() - + ($key eq 'u' ? $self->nrow / 2 : $self->nrow - 1); + $vs = $self->top_row if $vs < $self->top_row; + $cr += $vs - $self->view_start($vs); + } elsif ($key eq 'g') { + ($cr, $self->{offset}) = ($self->top_row, 0); + $self->{dollar} = 0; + } elsif ($key eq 'G') { + ($cr, $self->{offset}) = ($self->nrow - 1, 0); + $self->{dollar} = 0; + } elsif ($key eq '0') { + $self->{offset} = 0; + $self->{dollar} = 0; + } elsif ($key eq '^') { + my $ltxt = $self->special_decode($line->t); + while ($ltxt =~ s/^( *)\t/$1 . " " x (8 - length($1) % 8)/e) {} + $self->{offset} = $ltxt =~ m/^ +/ ? $+[0] : 0; + $self->{dollar} = 0; + } elsif ($key eq '$') { + my $co = $line->offset_of($cr, $cc); + $self->{dollar} = $co + 1; + $self->{offset} = $line->l - 1; + } elsif ($key eq 'H') { + $cr = $self->view_start(); + } elsif ($key eq 'M') { + $cr = $self->view_start() + $self->nrow / 2; + } elsif ($key eq 'L') { + $cr = $self->view_start() + $self->nrow - 1; + } + + $line = $self->line($cr); + $cc = $self->{dollar} || $self->{offset} >= $line->l ? $line->l - 1 : + $self->{offset}; + $self->screen_cur($line->coord_of($cc)); + + status_area($self); + $self->want_refresh(); + + () +} + + +sub move_to { + my ($self, $key) = @_; + my ($cr, $cc) = $self->screen_cur(); + my $line = $self->line($cr); + my $offset = $self->{offset}; + my ($dir, $pattern); + my ($wrap, $found) = (0, 0); + + if ($key eq ';' || $key eq ',') { + $dir = $self->{move_dir} * ($key eq ',' ? -1 : 1); + $pattern = $self->{patterns}{sprintf('f%+d', $dir)}; + return if not $pattern; + } else { + if (lc($key) eq 'b') { + $dir = -1; + } else { + $dir = 1; + ++$offset if lc($key) eq 'e'; + } + $pattern = $self->{patterns}{$key}; + $wrap = 1; + } + + if ($dir > 0) { + NEXTDOWN: my $text = substr($line->t, $offset); + if ($text =~ m/$pattern/) { + $offset += $+[0] - 1; + $found = 1; + } elsif ($wrap && $line->end + 1 < $self->nrow) { + $cr = $line->end + 1; + $line = $self->line($cr); + $offset = 0; + if (lc($key) eq 'e') { + goto NEXTDOWN; + } else { + $found = 1; + } + } + } elsif ($dir < 0) { + NEXTUP: my $text = substr($line->t, 0, $offset); + if ($text =~ m/$pattern/) { + $offset += $+[0] - length($text) - 1; + $found = 1; + } elsif ($wrap) { + if ($offset > 0) { + $offset = 0; + $found = 1; + } elsif ($line->beg > $self->top_row) { + $cr = $line->beg - 1; + $line = $self->line($cr); + $offset = $line->l; + goto NEXTUP; + } + } + } + + if ($found) { + $self->{dollar} = 0; + $self->{offset} = $offset; + $self->screen_cur($line->coord_of($offset)); + $self->want_refresh(); + } + + () +} + + +sub find_next { + my ($self, $dir) = @_; + + return if not $self->{pattern}; + $dir = $self->{search_dir} if not $dir; + + my ($cr, $cc) = $self->screen_cur(); + my $line = $self->line($cr); + my $offset = $line->offset_of($cr, $cc); + my $text; + my $found = 0; + + ++$offset if $dir > 0; + + while (not $found) { + if ($dir > 0) { + $text = substr($line->t, $offset); + if ($text =~ m/$self->{pattern}/) { + $found = 1; + $offset += $-[0]; + } else { + last if $line->end >= $self->nrow; + $line = $self->line($line->end + 1); + $offset = 0; + } + } else { + $text = substr($line->t, 0, $offset); + if ($text =~ m/$self->{pattern}/) { + $found = 1; + $offset = $-[0] while $text =~ m/$self->{pattern}/g; + } else { + last if $line->beg <= $self->top_row; + $line = $self->line($line->beg - 1); + $offset = $line->l; + } + } + } + + if ($found) { + $self->{dollar} = 0; + $self->{offset} = $offset; + $self->screen_cur($line->coord_of($offset)); + status_area($self); + $self->want_refresh(); + } + + return $found; +} + + +sub tt_write { + return 1; +} + + +sub refresh { + my ($self) = @_; + my $reverse_cursor = $self->{select} ne 'l'; + my ($cr, $cc) = $self->screen_cur(); + + if ($self->{select}) { + my ($br, $bc, $er, $ec) = calc_span($self); + + if ($self->{select} eq 'b') { + delete $self->{selection} if $self->{selection}; + my $co = $self->line($cr)->offset_of($cr, $cc); + my $dollar = $self->{dollar} && $co >= $self->{dollar} - 1; + + my $r = $br; + while ($r <= $er) { + my $line = $self->line($r); + if ($bc < $line->l) { + $ec = $line->l if $dollar; + $self->{selection} .= substr($line->t, $bc, $ec - $bc); + my ($br, $bc) = $line->coord_of($bc); + my ($er, $ec) = $line->coord_of($ec <= $line->l ? $ec : $line->l); + $self->scr_xor_span($br, $bc, $er, $ec, urxvt::RS_RVid); + } elsif ($r == $cr) { + $reverse_cursor = 0; + } + $self->{selection} .= "\n" if $line->end < $er; + $r = $line->end + 1; + } + } else { + $self->scr_xor_span($br, $bc, $er, $ec, urxvt::RS_RVid); + } + + if ($reverse_cursor) { + # make the cursor visible again + $self->scr_xor_span($cr, $cc, $cr, $cc + 1, urxvt::RS_RVid); + } + } + + # scroll the current cursor position into visible area + if ($cr < $self->view_start()) { + $self->view_start($cr); + } elsif ($cr >= $self->view_start() + $self->nrow) { + $self->view_start($cr - $self->nrow + 1); + } + + () +} + + +sub activate { + my ($self, $search) = @_; + + $self->{active} = 1; + + $self->{select} = ''; + $self->{dollar} = 0; + $self->{move_to} = 0; + + if ($search) { + $self->{search} = '?'; + $self->{search_dir} = -1; + $self->{search_mode} = 1; + } else { + $self->{search} = ''; + $self->{search_mode} = 0; + } + + ($self->{oldcr}, $self->{oldcc}) = $self->screen_cur(); + $self->{old_view_start} = $self->view_start(); + $self->{old_pty_ev_events} = $self->pty_ev_events(urxvt::EV_NONE); + + my $line = $self->line($self->{oldcr}); + $self->{offset} = $line->offset_of($self->{oldcr}, $self->{oldcc}); + + $self->selection_beg(1, 0); + $self->selection_end(1, 0); + + $self->enable( + key_press => \&key_press, + refresh_begin => \&refresh, + refresh_end => \&refresh, + tt_write => \&tt_write, + ); + + if ($self->{offset} >= $line->l) { + $self->{offset} = $line->l > 0 ? $line->l - 1 : 0; + $self->screen_cur($line->coord_of($self->{offset})); + $self->want_refresh(); + } + + $self->{overlay_len} = 0; + status_area($self); + + () +} + + +sub deactivate { + my ($self) = @_; + + $self->selection_beg(1, 0); + $self->selection_end(1, 0); + + delete $self->{overlay} if $self->{overlay}; + delete $self->{selection} if $self->{selection}; + + $self->disable("key_press", "refresh_begin", "refresh_end", "tt_write"); + $self->screen_cur($self->{oldcr}, $self->{oldcc}); + $self->view_start($self->{old_view_start}); + $self->pty_ev_events($self->{old_pty_ev_events}); + + $self->want_refresh(); + + $self->{active} = 0; + + () +} + + +sub status_area { + my ($self, $extra) = @_; + my ($stat, $stat_len); + + if ($self->{search}) { + $stat_len = $self->ncol; + $stat = $self->{search} . ' ' x ($stat_len - length($self->{search})); + } else { + if ($self->{select}) { + $stat = "-V" . ($self->{select} ne 'n' ? uc($self->{select}) : "") . "- "; + } + + if ($self->top_row == 0) { + $stat .= "All"; + } elsif ($self->view_start() == $self->top_row) { + $stat .= "Top"; + } elsif ($self->view_start() == 0) { + $stat .= "Bot"; + } else { + $stat .= sprintf("%2d%%", + ($self->top_row - $self->view_start) * 100 / $self->top_row); + } + + $stat = "$extra $stat" if $extra; + $stat_len = length($stat); + } + + if (!$self->{overlay} || $self->{overlay_len} != $stat_len) { + delete $self->{overlay} if $self->{overlay}; + $self->{overlay} = $self->overlay(-1, -1, $stat_len, 1, + urxvt::OVERLAY_RSTYLE, 0); + $self->{overlay_len} = $stat_len; + } + + $self->{overlay}->set(0, 0, $self->special_encode($stat)); + $self->{overlay}->show(); + + () +} + + +sub toggle_select { + my ($self, $mode) = @_; + + if ($self->{select} eq $mode) { + $self->{select} = ''; + } else { + if (not $self->{select}) { + ($self->{ar}, $self->{ac}) = $self->screen_cur(); + } + $self->{select} = $mode; + } + + status_area($self); + $self->want_refresh(); + + () +} + + +sub calc_span { + my ($self) = @_; + my ($cr, $cc) = $self->screen_cur(); + my ($br, $bc, $er, $ec); + + if ($self->{select} eq 'b') { + $br = $self->line($cr)->beg; + $bc = $self->line($cr)->offset_of($cr, $cc); + $er = $self->line($self->{ar})->beg; + $ec = $self->line($self->{ar})->offset_of($self->{ar}, $self->{ac}); + ($br, $er) = ($er, $br) if $br > $er; + ($bc, $ec) = ($ec, $bc) if $bc > $ec; + } else { + if ($cr < $self->{ar}) { + ($br, $bc, $er, $ec) = ($cr, $cc, $self->{ar}, $self->{ac}); + } elsif ($cr > $self->{ar}) { + ($br, $bc, $er, $ec) = ($self->{ar}, $self->{ac}, $cr, $cc); + } else { + ($br, $er) = ($cr, $cr); + ($bc, $ec) = $cc < $self->{ac} ? ($cc, $self->{ac}) : ($self->{ac}, $cc); + } + } + + if ($self->{select} eq 'l') { + ($br, $er) = ($self->line($br)->beg, $self->line($er)->end); + ($bc, $ec) = (0, $self->ncol); + } else { + ++$ec; + } + + return ($br, $bc, $er, $ec); +} diff --git a/urxvt/.urxvt/ext/url-select b/urxvt/.urxvt/ext/url-select @@ -0,0 +1,375 @@ +#! perl -w +# Author: Bert Muennich +# Website: http://www.github.com/muennich/urxvt-perls +# Based on: http://www.jukie.net/~bart/blog/urxvt-url-yank +# License: GPLv2 + +# Use keyboard shortcuts to select URLs. +# This should be used as a replacement for the default matcher extension, +# it also makes URLs clickable with the middle mouse button. + +# Usage: put the following lines in your .Xdefaults/.Xresources: +# URxvt.perl-ext-common: ...,url-select +# URxvt.keysym.M-u: perl:url-select:select_next + +# Use Meta-u to activate URL selection mode, then use the following keys: +# j/k: Select next downward/upward URL (also with arrow keys) +# g/G: Select first/last URL (also with home/end key) +# o/Return: Open selected URL in browser, Return: deactivate afterwards +# y: Copy (yank) selected URL and deactivate selection mode +# q/Escape: Deactivate URL selection mode + +# Options: +# URxvt.url-select.autocopy: If true, selected URLs are copied to PRIMARY +# URvxt.url-select.button: Mouse button to click-open URLs (default: 2) +# URxvt.url-select.launcher: Browser/command to open selected URL with +# URxvt.url-select.underline: If set to true, all URLs get underlined + +use strict; + +sub on_start { + my ($self) = @_; + + # read resource settings + if ($self->x_resource('url-select.launcher')) { + @{$self->{browser}} = split /\s+/, $self->x_resource('url-select.launcher'); + } else { + @{$self->{browser}} = ('x-www-browser'); + } + if ($self->x_resource('url-select.underline') eq 'true') { + $self->enable(line_update => \&line_update); + } + if ($self->x_resource('url-select.autocopy') eq 'true') { + $self->{autocopy} = 1; + } + + $self->{state} = 0; + + for my $mod (split '', $self->x_resource("url-select.button") || + $self->x_resource("matcher.button") || 2) { + if ($mod =~ /^\d+$/) { + $self->{button} = $mod; + } elsif ($mod eq "C") { + $self->{state} |= urxvt::ControlMask; + } elsif ($mod eq "S") { + $self->{state} |= urxvt::ShiftMask; + } elsif ($mod eq "M") { + $self->{state} |= $self->ModMetaMask; + } elsif ($mod ne "-" && $mod ne " ") { + warn("invalid button/modifier in $self->{_name}<$self->{argv}[0]>: $mod\n"); + } + } + + if ($self->x_resource('matcher.pattern')) { + @{$self->{pattern}} = ($self->x_resource('matcher.pattern')); + } elsif ($self->x_resource('matcher.pattern.1')) { + my $current = 1; + + while ($self->x_resource("matcher.pattern.$current")) { + push @{$self->{pattern}}, $self->x_resource("matcher.pattern.$current"); + $current++; + } + } else { + @{$self->{pattern}} = 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 line_update { + my ($self, $row) = @_; + + my $line = $self->line($row); + my $text = $line->t; + my $rend = $line->r; + + for my $pattern (@{$self->{pattern}}) { + while ($text =~ /$pattern/g) { + my $url = $&; + my ($beg, $end) = ($-[0], $+[0] - 1); + + for (@{$rend}[$beg .. $end]) { + $_ |= urxvt::RS_Uline; + } + $line->r($rend); + } + } + + () +} + + +sub on_user_command { + my ($self, $cmd) = @_; + + if ($cmd eq 'url-select:select_next') { + if (not $self->{active}) { + activate($self); + } + select_next($self, -1); + } + + () +} + + +sub key_press { + my ($self, $event, $keysym) = @_; + my $char = chr($keysym); + + if ($keysym == 0xff1b || lc($char) eq 'q' || + (lc($char) eq 'c' && $event->{state} & urxvt::ControlMask)) { + deactivate($self); + } elsif ($keysym == 0xff0d || $char eq 'o') { + $self->exec_async(@{$self->{browser}}, ${$self->{found}[$self->{n}]}[4]); + deactivate($self) unless $char eq 'o'; + } elsif ($char eq 'y') { + my $found = $self->{found}[$self->{n}]; + $self->selection_beg(${$found}[0], ${$found}[1]); + $self->selection_end(${$found}[2], ${$found}[3]); + $self->selection_make($event->{time}); + $self->selection_beg(1, 0); + $self->selection_end(1, 0); + deactivate($self); + } elsif ($char eq 'k' || $keysym == 0xff52 || $keysym == 0xff51) { + select_next($self, -1, $event); + } elsif ($char eq 'j' || $keysym == 0xff54 || $keysym == 0xff53) { + select_next($self, 1, $event); + } elsif ($char eq 'g' || $keysym == 0xff50) { + $self->{row} = $self->top_row - 1; + delete $self->{found}; + select_next($self, 1, $event); + } elsif ($char eq 'G' || $keysym == 0xff57) { + $self->{row} = $self->nrow; + delete $self->{found}; + select_next($self, -1, $event); + } + + return 1; +} + + +sub on_button_press { + my ($self, $event) = @_; + + my $mask = $self->ModLevel3Mask | $self->ModMetaMask | + urxvt::ShiftMask | urxvt::ControlMask; + + if ($event->{button} == $self->{button} && ($event->{state} & $mask) == $self->{state}) { + $self->{button_pressed} = 1; + $self->{button_col} = $event->{col}; + $self->{button_row} = $event->{row}; + } + + () +} + +sub on_button_release { + my ($self, $event) = @_; + + if ($self->{button_pressed} && $event->{button} == $self->{button}) { + my $col = $event->{col}; + my $row = $event->{row}; + + $self->{button_pressed} = 0; + + if ($col == $self->{button_col} && $row == $self->{button_row}) { + my $line = $self->line($row); + my $text = $line->t; + + for my $pattern (@{$self->{pattern}}) { + while ($text =~ /$pattern/g) { + my ($url, $beg, $end) = ($&, $-[0], $+[0]); + --$end if $url =~ s/["')]$//; + + if ($col >= $beg && $col <= $end) { + $self->exec_async(@{$self->{browser}}, $url); + return 1; + } + } + } + } + } + + () +} + + +sub select_next { + # $dir < 0: up, > 0: down + my ($self, $dir, $event) = @_; + my $row = $self->{row}; + + if (($dir < 0 && $self->{n} > 0) || + ($dir > 0 && $self->{n} < $#{ $self->{found} })) { + # another url on current line + $self->{n} += $dir; + hilight($self); + if ($self->{autocopy}) { + my $found = $self->{found}[$self->{n}]; + $self->selection_beg(${$found}[0], ${$found}[1]); + $self->selection_end(${$found}[2], ${$found}[3]); + $self->selection_make($event->{time}); + $self->selection_beg(1, 0); + $self->selection_end(1, 0); + } + return; + } + + while (($dir < 0 && $row > $self->top_row) || + ($dir > 0 && $row < $self->nrow - 1)) { + my $line = $self->line($row); + $row = ($dir < 0 ? $line->beg : $line->end) + $dir; + $line = $self->line($row); + my $text = $line->t; + + for my $pattern (@{$self->{pattern}}) { + if ($text =~ /$pattern/g) { + delete $self->{found}; + + do { + my ($beg, $end) = ($-[0], $+[0]); + push @{$self->{found}}, [$line->coord_of($beg), + $line->coord_of($end), substr($text, $beg, $end - $beg)]; + } while ($text =~ /$pattern/g); + + $self->{row} = $row; + $self->{n} = $dir < 0 ? $#{$self->{found}} : 0; + hilight($self); + if ($self->{autocopy}) { + my $found = $self->{found}[$self->{n}]; + $self->selection_beg(${$found}[0], ${$found}[1]); + $self->selection_end(${$found}[2], ${$found}[3]); + $self->selection_make($event->{time}); + $self->selection_beg(1, 0); + $self->selection_end(1, 0); + } + return; + } + } + } + + deactivate($self) unless $self->{found}; + + () +} + + +sub hilight { + my ($self) = @_; + + if ($self->{found}) { + if ($self->{row} < $self->view_start() || + $self->{row} >= $self->view_start() + $self->nrow) { + # scroll selected url into visible area + my $top = $self->{row} - ($self->nrow >> 1); + $self->view_start($top < 0 ? $top : 0); + } + + status_area($self); + $self->want_refresh(); + } + + () +} + + +sub refresh { + my ($self) = @_; + + if ($self->{found}) { + $self->scr_xor_span(@{$self->{found}[$self->{n}]}[0 .. 3], urxvt::RS_RVid); + } + + () +} + + +sub status_area { + my ($self) = @_; + + my $row = $self->{row} < 0 ? + $self->{row} - $self->top_row : abs($self->top_row) + $self->{row}; + my $text = sprintf("%d,%d ", $row + 1, $self->{n} + 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 tt_write { + return 1; +} + + +sub activate { + my ($self) = @_; + + $self->{active} = 1; + + $self->{row} = $self->view_start() + $self->nrow; + $self->{n} = 0; + $self->{overlay_len} = 0; + $self->{button_pressed} = 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->disable("key_press", "refresh_begin", "refresh_end", "tt_write"); + $self->view_start($self->{view_start}); + $self->pty_ev_events($self->{pty_ev_events}); + + delete $self->{overlay} if $self->{overlay}; + delete $self->{found} if $self->{found}; + + $self->want_refresh(); + + $self->{active} = 0; + + () +}