diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/tmux-nicklist-portable.pl | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/scripts/tmux-nicklist-portable.pl b/scripts/tmux-nicklist-portable.pl new file mode 100644 index 0000000..e0c6920 --- /dev/null +++ b/scripts/tmux-nicklist-portable.pl @@ -0,0 +1,432 @@ +# 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.8'; +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 + 1; + 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; + +} |