Permalink
Switch branches/tags
Nothing to show
Find file Copy path
217f7e7 May 25, 2018
2 contributors

Users who have contributed to this file

@gerardbm @dequis
433 lines (406 sloc) 13.4 KB
# based on the nicklist.pl script
################################################################################
# tmux_nicklist.pl
# This script integrates tmux and irssi to display a list of nicks in a
# vertical right pane with 20% width. Right now theres no configuration
# or setup, simply initialize the script with irssi and by default you
# will get the nicklist for every channel(customize by altering
# the regex in /set nicklist_channel_re)
#
# /set nicklist_channel_re <regex>
# * only show on channels matching this regular expression
#
# /set nicklist_max_users <num>
# * only show when the channel has so many users or less (0 = always)
#
# /set nicklist_smallest_main <num>
# * only show when main window is larger than this (0 = always)
#
# /set nicklist_pane_width <num>
# * width of the nicklist pane
#
# /set nicklist_color <ON|OFF>
# * colourise the nicks in the nicklist (required nickcolor script
# with get_nick_color2 and debug_ansicolour functions)
#
# /set nicklist_gone_sort <ON|OFF>
# * sort away people below
#
# It supports mouse scrolling and the following keys:
# k/up arrow: up one line
# j/down arrow: down one line
# u/pageup: up 50% lines
# d/pagedown: down 50% lines
# gg: go to top
# G: go to bottom
#
# For better integration, unrecognized sequences will be sent to irssi and
# its pane will be focused.
#
# to toggle the nicklist if it is in the way you can make a key binding:
# /bind meta-Z /script exec Irssi::Script::tmux_nicklist_portable::toggle_nicklist
################################################################################
use strict;
use warnings;
use IO::Handle;
use IO::Select;
use POSIX;
use File::Temp qw/ :mktemp /;
use File::Basename;
our $VERSION = '0.1.7';
our %IRSSI = (
authors => 'Thiago de Arruda',
contact => 'tpadilha84@gmail.com',
name => 'tmux-nicklist',
description => 'displays a list of nicks in a separate tmux pane',
license => 'WTFPL',
);
# "other" prefixes by danielg4 <daniel@gimpelevich.san-francisco.ca.us>
# added 'd' and 'u' navigation as in vim, by @gerardbm (github)
{ package Irssi::Nick }
if ($#ARGV == -1) {
require Irssi;
my $enabled = 0;
my $nicklist_toggle = 1;
my $script_path = __FILE__;
my $tmpdir;
my $fifo_path;
my $fifo;
my $just_launched;
my $resize_timer;
sub enable_nicklist {
return if ($enabled);
$tmpdir = mkdtemp Irssi::get_irssi_dir()."/nicklist-XXXXXXXX";
$fifo_path = "$tmpdir/fifo";
POSIX::mkfifo($fifo_path, 0600) or die "can't mkfifo $fifo_path: $!";
my $cmd = "perl $script_path $fifo_path $ENV{TMUX_PANE}";
my $width = Irssi::settings_get_int('nicklist_pane_width');
system('tmux', 'split-window', '-dh', '-l', $width, '-t', $ENV{TMUX_PANE}, $cmd);
open_fifo();
Irssi::timeout_remove($just_launched) if defined $just_launched;
$just_launched = Irssi::timeout_add_once(300, sub { $just_launched = undef; }, '');
}
sub open_fifo {
# The next system call will block until the other pane has opened the pipe
# for reading, so synchronization is not an issue here.
open $fifo, ">", $fifo_path or do {
if ($! == 4) {
Irssi::timeout_add_once(300, \&open_fifo, '');
$enabled = -1 unless $enabled;
return;
}
die "can't open $fifo_path: $!";
};
$fifo->autoflush(1);
if ($enabled < -1) {
$enabled = 1;
disable_nicklist();
} elsif ($enabled == -1) {
$enabled = 1;
reset_nicklist("enabled");
} else {
$enabled = 1;
}
}
sub disable_nicklist {
return unless ($enabled);
if ($enabled > 0) {
print $fifo "EXIT\n";
close $fifo;
$fifo = undef;
unlink $fifo_path;
rmdir $tmpdir;
}
$enabled--;
}
sub reset_nicklist {
my $event = shift;
my $active = Irssi::active_win();
my $channel = $active->{active};
return disable_nicklist unless $channel && ref $channel;
if ($event =~ /^nick/) {
# check if that nick event is for the current channel/nicklist
my ($event_channel) = @_;
return unless $channel->{_irssi} == $event_channel->{_irssi};
}
my ($colourer, $ansifier);
if (Irssi::settings_get_bool('nicklist_color')) {
for my $script (sort map { my $z = $_; $z =~ s/::$//; $z } grep { /^nickcolor|nm/ } keys %Irssi::Script::) {
if ($colourer = "Irssi::Script::$script"->can('get_nick_color2')) {
$ansifier = "Irssi::Script::$script"->can('debug_ansicolour');
last;
}
}
}
my $channel_pattern = Irssi::settings_get_str('nicklist_channel_re');
{ local $@;
$channel_pattern = eval { qr/$channel_pattern/ };
$channel_pattern = qr/(?!)/ if $@;
}
my $smallest_main = Irssi::settings_get_int('nicklist_smallest_main');
if (!$nicklist_toggle
|| !$channel || !ref($channel)
|| !$channel->isa('Irssi::Channel')
|| !$channel->{'names_got'}
|| $channel->{'name'} !~ $channel_pattern
|| ($smallest_main && $channel->window->{width} < $smallest_main)) {
disable_nicklist;
} else {
my %colour;
my @nicks = $channel->nicks();
my $max_nicks = Irssi::settings_get_int('nicklist_max_users');
if ($max_nicks && @nicks > $max_nicks) {
disable_nicklist;
} else {
enable_nicklist;
return unless $enabled > 0;
foreach my $nick (sort { $a->{_irssi} <=> $b->{_irssi} } @nicks) {
$colour{$nick->{nick}} = ($ansifier && $colourer) ? $ansifier->($colourer->($active->{active}{server}{tag}, $channel->{name}, $nick->{nick}, 0)) : '';
}
print($fifo "BEGIN\n");
my $gone_sort = Irssi::settings_get_bool('nicklist_gone_sort');
my $prefer_real;
if (exists $Irssi::Script::{'realnames::'}) {
my $code = "Irssi::Script::realnames"->can('use_realnames');
$prefer_real = $code && $code->($channel);
}
my $_real = sub {
my $nick = shift;
$prefer_real && length $nick->{'realname'} ? $nick->{'realname'} : $nick->{'nick'}
};
foreach my $nick (sort {($a->{'op'}?'1':$a->{'halfop'}?'2':$a->{'voice'}?'3':$a->{'other'}>32?'0':'4').($gone_sort?($a->{'gone'}?1:0):'').lc($_real->($a))
cmp ($b->{'op'}?'1':$b->{'halfop'}?'2':$b->{'voice'}?'3':$b->{'other'}>32?'0':'4').($gone_sort?($b->{'gone'}?1:0):'').lc($_real->($b))} @nicks) {
my $colour = $colour{$nick->{nick}} || "\e[39m";
$colour = "\e[37m" if $nick->{'gone'};
print($fifo "NICK");
if ($nick->{'op'}) {
print($fifo "\e[32m\@$colour".$_real->($nick)."\e[39m");
} elsif ($nick->{'halfop'}) {
print($fifo "\e[34m%$colour".$_real->($nick)."\e[39m");
} elsif ($nick->{'voice'}) {
print($fifo "\e[33m+$colour".$_real->($nick)."\e[39m");
} elsif ($nick->{'other'}>32) {
print($fifo "\e[31m".(chr $nick->{'other'})."$colour".$_real->($nick)."\e[39m");
} else {
print($fifo " $colour".$_real->($nick)."\e[39m");
}
print($fifo "\n");
}
print($fifo "END\n");
}
}
}
sub toggle_nicklist {
if ($enabled) {
$nicklist_toggle = undef
} else {
$nicklist_toggle = 1;
}
reset_nicklist("toggle");
}
sub switch_channel {
print $fifo "SWITCH_CHANNEL\n" if $fifo;
&reset_nicklist;
}
sub resized_timed {
Irssi::timeout_remove($resize_timer) if defined $resize_timer;
return if defined $just_launched;
$resize_timer = Irssi::timeout_add_once(1100, \&resized, '');
#resized();
}
sub resized {
$resize_timer = undef;
return if defined $just_launched;
return unless $enabled >= 0;
disable_nicklist;
Irssi::timeout_add_once(200, sub{reset_nicklist("terminal resized")}, '');
}
sub UNLOAD {
disable_nicklist;
}
Irssi::settings_add_str('tmux_nicklist', 'nicklist_channel_re', '.*');
Irssi::settings_add_int('tmux_nicklist', 'nicklist_max_users', 0);
Irssi::settings_add_int('tmux_nicklist', 'nicklist_smallest_main', 0);
Irssi::settings_add_int('tmux_nicklist', 'nicklist_pane_width', 13);
Irssi::settings_add_bool('tmux_nicklist', 'nicklist_color', 1);
Irssi::settings_add_bool('tmux_nicklist', 'nicklist_gone_sort', 0);
Irssi::signal_add_last('window item changed', sub{switch_channel("window item changed",@_)});
Irssi::signal_add_last('window changed', sub{switch_channel("window changed",@_)});
Irssi::signal_add_last('channel joined', sub{switch_channel("channel joined",@_)});
Irssi::signal_add('nicklist new', sub{reset_nicklist("nicklist new",@_)});
Irssi::signal_add('nicklist remove', sub{reset_nicklist("nicklist remove",@_)});
Irssi::signal_add('nicklist changed', sub{reset_nicklist("nicklist changed",@_)});
Irssi::signal_add_first('nick mode changed', sub{reset_nicklist("nick mode changed",@_)});
Irssi::signal_add('gui exit', \&disable_nicklist);
Irssi::signal_add_last('terminal resized', \&resized_timed);
} else {
my $fifo_path = $ARGV[0];
my $irssi_pane = $ARGV[1];
# array to store the current channel nicknames
my @nicknames = ();
# helper functions for manipulating the terminal
# escape sequences taken from
# http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
sub enable_mouse { print "\e[?1000h"; }
# recognized sequences
my $MOUSE_SCROLL_DOWN="\e[Ma";
my $MOUSE_SCROLL_UP="\e[M`";
my $ARROW_DOWN="\e[B";
my $ARROW_UP="\e[A";
my $DOWN="j";
my $UP="k";
my $PAGE_DOWN="\e[6~";
my $PAGE_UP="\e[5~";
my $PAGE_DOWN_D="d";
my $PAGE_UP_U="u";
my $GO_TOP="gg";
my $GO_BOTTOM="G";
my $current_line = 0;
my $sequence = '';
my ($rows, $cols);
sub term_size {
split ' ', `stty size`;
}
sub redraw {
my $last_nick_idx = @nicknames;
my $last_idx = $current_line + $rows;
# normalize last visible index
if ($last_idx > ($last_nick_idx)) {
$last_idx = $last_nick_idx;
}
# redraw visible nicks
for my $i (reverse 1..$rows) {
print "\e[$i;1H\e[K";
my $idx = $current_line + $i - 1;
if ($idx < $last_idx) {
my $z = 0; my $col = $cols;
for (split /(\e\[(?:\d|;|:|\?|\s)*.)/, $nicknames[$idx]) {
if ($z ^= 1) {
print +(substr $_, 0, $col) if $col > 0;
$col -= length;
} else {
print
}
}
}
}
}
sub move_down {
$sequence = '';
my $count = int $_[0];
my $nickcount = $#nicknames;
return if ($nickcount <= $rows);
if ($count == -1) {
$current_line = $nickcount - $rows - 1;
redraw;
return;
}
my $visible = $nickcount - $current_line - $count;
if ($visible > $rows) {
$current_line += $count;
redraw;
} elsif (($visible + $count) > $rows) {
# scroll the maximum we can
$current_line = $nickcount - $rows - 1;
redraw;
}
}
sub move_up {
$sequence = '';
my $count = int $_[0];
if ($count == -1) {
$current_line = 0;
redraw;
return;
}
return if ($current_line == 0);
$count = 1 if $count == 0;
$current_line -= $count;
$current_line = 0 if $current_line < 0;
redraw;
}
$SIG{INT} = 'IGNORE';
STDOUT->autoflush(1);
# setup terminal so we can listen for individual key presses without echo
`stty -icanon -echo`;
# open named pipe and setup the 'select' wrapper object for listening on both
# fds(fifo and sdtin)
open my $fifo, "<", $fifo_path or die "can't open $fifo_path: $!";
my $select = IO::Select->new();
my @ready;
$select->add($fifo);
$select->add(\*STDIN);
enable_mouse;
system('tput', 'smcup');
print "\e[?7l"; #system('tput', 'rmam');
system('tput', 'civis');
MAIN: {
while (@ready = $select->can_read) {
foreach my $fd (@ready) {
($rows, $cols) = term_size;
if ($fd == $fifo) {
while (<$fifo>) {
my $line = $_;
if ($line =~ /^BEGIN/) {
@nicknames = ();
} elsif ($line =~ /^SWITCH_CHANNEL/) {
$current_line = 0;
} elsif ($line =~ /^NICK(.+)$/) {
push @nicknames, $1;
} elsif ($line =~ /^END$/) {
redraw;
last;
} elsif ($line =~ /^EXIT$/) {
last MAIN;
}
}
} else {
my $key = '';
sysread(STDIN, $key, 1);
$sequence .= $key;
if ($MOUSE_SCROLL_DOWN =~ /^\Q$sequence\E/) {
if ($MOUSE_SCROLL_DOWN eq $sequence) {
move_down 3;
# mouse scroll has two more bytes that I dont use here
# so consume them now to avoid sending unwanted bytes to
# irssi
sysread(STDIN, $key, 2);
}
} elsif ($MOUSE_SCROLL_UP =~ /^\Q$sequence\E/) {
if ($MOUSE_SCROLL_UP eq $sequence) {
move_up 3;
sysread(STDIN, $key, 2);
}
} elsif ($ARROW_DOWN =~ /^\Q$sequence\E/) {
move_down 1 if ($ARROW_DOWN eq $sequence);
} elsif ($ARROW_UP =~ /^\Q$sequence\E/) {
move_up 1 if ($ARROW_UP eq $sequence);
} elsif ($DOWN =~ /^\Q$sequence\E/) {
move_down 1 if ($DOWN eq $sequence);
} elsif ($UP =~ /^\Q$sequence\E/) {
move_up 1 if ($UP eq $sequence);
} elsif ($PAGE_DOWN =~ /^\Q$sequence\E/) {
move_down $rows/2 if ($PAGE_DOWN eq $sequence);
} elsif ($PAGE_UP =~ /^\Q$sequence\E/) {
move_up $rows/2 if ($PAGE_UP eq $sequence);
} elsif ($PAGE_DOWN_D =~ /^\Q$sequence\E/) {
move_down $rows/2 if ($PAGE_DOWN_D eq $sequence);
} elsif ($PAGE_UP_U =~ /^\Q$sequence\E/) {
move_up $rows/2 if ($PAGE_UP_U eq $sequence);
} elsif ($GO_BOTTOM =~ /^\Q$sequence\E/) {
move_down -1 if ($GO_BOTTOM eq $sequence);
} elsif ($GO_TOP =~ /^\Q$sequence\E/) {
move_up -1 if ($GO_TOP eq $sequence);
} else {
# Unrecognized sequences will be send to irssi and its pane
# will be focused
system('tmux', 'send-keys', '-t', $irssi_pane, $sequence);
system('tmux', 'select-pane', '-t', $irssi_pane);
$sequence = '';
}
}
}
}
}
close $fifo;
}