#!/usr/bin/env perl
#
# tmux-url-select
# mit licensed
#
# https://github.com/dequis/tmux-url-select

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 TMUX_WINDOW_ID => 9999;
use constant HIDE_WINDOW => 1;

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(),~#\x1b\[\]]+[\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/o/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_ID;
}

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", "", "-t", TMUX_WINDOW_ID, "$0 inner";
    system "tmux", "setw", "-qt", TMUX_WINDOW_ID, "window-status-format", "";
    system "tmux", "setw", "-qt", TMUX_WINDOW_ID, "window-status-current-format", "";
}

# other shell helpers

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

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
    $url = "http://".$url if $url =~ /^www\./;
    # clear out color codes
    $url =~ s/\x1b\[[0-9;]*m//g;
    return $url;
}

sub safe_exec {
    my ($command, $message) = @_;
    $SIG{CHLD} = 'IGNORE';
    $SIG{HUP} = 'IGNORE';

    unless (fork) {
        tmux_display_message($message) if VERBOSE_MESSAGES;
        exec $command;
    }
}

sub launch_url {
    my $url = fix_url(shift);
    tmux_switch_to_last() if shift;

    my $command = sprintf(COMMAND, single_quote_escape($url));
    safe_exec($command, "Launched ". $url);
}

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

# main functions

sub main_inner {
    $raw_buffer = tmux_get_buffer();
    system "tmux delete-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 /[jB]/;
        $selection-- if /[kA]/;
        $selection = ($_-1) if /[0-9]/;
        $selection %= $match_count;
        my $do_return = /[qyo\n]/;
        yank_url($matches[$selection], $do_return) if /[yY]/;
        launch_url($matches[$selection], $do_return) if /[\noO]/;
        return if $do_return;
        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");
        system "tmux delete-buffer";
        exit 0;
    }

    # open window here, backgrounded
    tmux_open_inner_window();
}

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