Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dequis committed Feb 19, 2014
0 parents commit b1e5479
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2014 dequis.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
117 changes: 117 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# tmux-url-select

`tmux-url-select` is a perl script that integrates tightly with tmux to capture
the current pane buffer, switch to a window with highlighted links, and let you
select the link you want to open/yank.

It's like the urxvtperls [url-select][1] script or [urlview][2], except that
it's inspired by the interface of the former and the capture-pane method usually
used with the latter.

Features!

* Tightly integrated to tmux to prevent flickering when switching to url
selection
* No dependencies (Not even ncurses, so no portability either!)
* Uses colors! Configurable colors!
* Configurable external commands too!
* vi-like keybindings (actually just `j` and `k`)

[1]: https://github.com/muennich/urxvt-perls/blob/master/url-select
[2]: http://packages.qa.debian.org/u/urlview.html

## Is it any good?

[Yes][3]

[3]: https://news.ycombinator.com/item?id=3067434

## Requirements

Depends on `perl`, `tmux` and `stty`.

Optional and configurable: `xdg-open` (can be any url opener or browser) and
`xclip` (for yank)

## Installation

Place it somewhere in your path and `chmod +x` it.

## Configuration

There's a bunch of constants near the top of the file, you can modify them to
your liking.

use constant COMMAND => 'xdg-open %s';
use constant YANK_COMMAND => 'echo %s | xclip -i';

use constant SHOW_STATUS_BAR => 1;
use constant VERBOSE_MESSAGES => 0;
use constant TMUX_WINDOW_TITLE => 'Select URL';

use constant PROMPT_COLOR => "\033[42;30m";
use constant ACTIVE_LINK_HIGHLIGHT => "\033[44;4m";
use constant NORMAL_LINK_HIGHLIGHT => "\033[94;1;4m";

Probably should add some explanations. Maybe. For now just go ahead and
experiment with stuff.

## Usage

Add this to your `.tmux.conf`:

bind some-key-here run tmux-url-select

Personally I use "z" which is an unused keybinding that is really close to my
tmux prefix key (`` ` ``)

bind z run tmux-url-select

Once you're inside tmux-url-select, keybindings:

* `j`: down
* `k`: up
* `0`-`9`: select link by number
* `y`: yank (copy to clipboard)
* Enter: open link
* `q`: quit

You can't use arrow keys because those are more complex than a single ascii
character.

## Known issues

If a line with a link has a background color, it will get reset after the link.
I have no idea how to do this without writing a parser of ansi escape codes or
using ncurses properly, so I'm leaving it unfixed as a reminder that as humans
we're all flawed in different ways.

Might flicker when selecting links because the whole screen is redrawn. It can't
be helped. Works fine for me most of the time.

Already workarounded, but: Some url openers don't like the fact that we kill the
terminal right after running the process. `gvfs-open` in particular opens my
browser asynchronously and doesn't wait for it, so there's a race condition in
which the terminal often get closed before the browser gets to do anything.
Workarounds for this:

* `sleep 1` after running the command. The laziest and most generic workaround,
currently applied in the code because it doesn't cause any other issues.
* Prefix your url open command with `setsid`. This makes the child process a
process group leader, so it won't care about getting the terminal closed.
* Prefix with `nohup`. Does something similar but not quite the same as
`setsid`, and didn't work for me, but worth a try!
* Wrap the command with `$(...)`. Weirdest one, I found this trick accidentally
last year and [had to ask stack overflow][4] about it. It makes bash wait for
the whole process tree to finish executing, apparently.
* Prefix with `strace -f -o /dev/null`. Another weird one! This one also seems
to force waiting for all child processes to finish, by attaching to them with
ptrace. It's overkill and that makes it fun.

[4]: http://stackoverflow.com/questions/16874043/bash-command-substitution-forcing-process-to-foreground

## FAQ

Q: Why perl? It's dead and it sucks, cool kids use node.js nowadays.

A: It's fun. Fun things are fun.
196 changes: 196 additions & 0 deletions tmux-url-select.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#!/usr/bin/env perl
#
# tmux-url-select
# mit licensed
#

use strict;
use warnings;

### config

use constant COMMAND => 'xdg-open %s';
use constant YANK_COMMAND => 'echo %s | xclip -i';

use constant SHOW_STATUS_BAR => 1;
use constant VERBOSE_MESSAGES => 0;
use constant TMUX_WINDOW_TITLE => 'Select URL';

use constant PROMPT_COLOR => "\033[42;30m";
use constant ACTIVE_LINK_HIGHLIGHT => "\033[44;4m";
use constant NORMAL_LINK_HIGHLIGHT => "\033[94;1;4m";

# other options:
# - blue background, underlined: \033[44;4m
# - 256 color term light blue: \033[38;5;39m
# - bold bright blue: \033[94;1;4m
# - bright blue background: \033[104;4m
# - just underlined: \033[4m

# regex stolen from urxvtperls url-select.pl
my $url_pattern = qr{(
(?:https?://|ftp://|news://|git://|mailto:|file://|www\.)
[\w\-\@;\/?:&=%\$_.+!*\x27(),~#]+[\w\-\@;\/?&=%\$_+!*\x27()~]
)}x;

### config end

my $raw_buffer;
my $buffer;
my $buffer_first_newline_position;
my @matches;
my $match_count;
my $selection = 0;

# terminal helper functions

sub clear {
print "\033[H\033[2J";
}

sub display_status_bar {
my $is_first_line = shift;
my $position = $is_first_line ? "2;2" : "1;2";
print sprintf("\033[%sH%s URL select: (%s/%s) [j/k/y/q/enter] \033[0m", $position, PROMPT_COLOR, $selection+1, $match_count);
}

sub display_highlighted_buffer {
my $i = 0;
my $is_first_line = 0;
my $cb = sub {
if ($i++ == $selection) {
$is_first_line = 1 if ($+[0] < $buffer_first_newline_position);
return ACTIVE_LINK_HIGHLIGHT."$1\033[0m";
return;
}
return NORMAL_LINK_HIGHLIGHT."$1\033[0m" if NORMAL_LINK_HIGHLIGHT;
return $1;
};
print $buffer =~ s/($url_pattern)/&$cb()/ger;
return $is_first_line;
}

sub display_stuff {
clear();
my $is_first_line = display_highlighted_buffer();
display_status_bar($is_first_line) if SHOW_STATUS_BAR;
}

# tmux command helpers

sub tmux_display_message {
system 'tmux', 'display-message', shift;
}

sub tmux_switch_to_last {
system 'tmux', 'last-window';
}

sub tmux_select_my_window {
system "tmux", "select-window", "-t", TMUX_WINDOW_TITLE;
}

sub tmux_capture_pane {
system "tmux", "capture-pane", "-eJ";
}

sub tmux_get_buffer {
return `tmux show-buffer`;
}

sub tmux_open_inner_window {
system "tmux", "new-window", "-dn", TMUX_WINDOW_TITLE, "$0 inner";
}

# other shell helpers

sub enable_canonical_mode {
# "canonical mode" to read char by char, thanks roger.
system "stty", "-icanon";
}

sub single_quote_escape {
return "'".(shift =~ s/\'/%27/gr)."'";
}

# actions

sub fix_url {
my $url = shift;
# some silly url openers think ^www. urls are files
return "http://".$url if $url =~ /^www\./;
return $url;
}

sub launch_url {
my $url = fix_url(shift);
tmux_switch_to_last();
system sprintf(COMMAND, single_quote_escape($url));
tmux_display_message("Launched ". $url) if VERBOSE_MESSAGES;

# shitty workaround for race conditions
# gvfs-open doesn't like when we kill the terminal
# and i'm not going to setsid it
sleep 1;
}

sub yank_url {
my $url = fix_url(shift);
tmux_switch_to_last();
system sprintf(YANK_COMMAND, single_quote_escape($url));
tmux_display_message("Yanked ". $url) if VERBOSE_MESSAGES;
}

# main functions

sub main_inner {
$raw_buffer = tmux_get_buffer();
$buffer = $raw_buffer =~ s/\n$//r;
$buffer_first_newline_position = index($raw_buffer, "\n");

@matches = ($buffer =~ /$url_pattern/g);
$match_count = @matches;
exit 1 if !$match_count;

$selection = $#matches;

display_stuff();

enable_canonical_mode();

# switch to the tmux-url-select window now to avoid 'flickering'
tmux_select_my_window();

# main loop
while(defined($_ = getc)) {
$selection++ if /j/;
$selection-- if /k/;
$selection = ($_-1) if /[0-9]/;
$selection %= $match_count;
yank_url($matches[$selection]) if /y/;
return launch_url($matches[$selection]) if /\n/;
return if /q/;
display_stuff();
}
}

sub main {
tmux_capture_pane();

@matches = tmux_get_buffer() =~ /$url_pattern/g;
$match_count = @matches;

if (!$match_count) {
tmux_display_message("No URLs");
exit 0;
}

# open window here, backgrounded
tmux_open_inner_window();
}

if (!@ARGV) {
main();
} elsif ($ARGV[0] eq "inner") {
main_inner();
}

0 comments on commit b1e5479

Please sign in to comment.