aboutsummaryrefslogtreecommitdiff
path: root/weechat/perl
diff options
context:
space:
mode:
Diffstat (limited to 'weechat/perl')
l---------weechat/perl/autoload/awaylog.pl1
l---------weechat/perl/autoload/beep.pl1
l---------weechat/perl/autoload/buffers.pl1
l---------weechat/perl/autoload/coords.pl1
-rw-r--r--weechat/perl/awaylog.pl100
-rwxr-xr-xweechat/perl/beep.pl257
-rwxr-xr-xweechat/perl/buffers.pl1816
-rw-r--r--weechat/perl/coords.pl3223
8 files changed, 5400 insertions, 0 deletions
diff --git a/weechat/perl/autoload/awaylog.pl b/weechat/perl/autoload/awaylog.pl
new file mode 120000
index 0000000..200ecd5
--- /dev/null
+++ b/weechat/perl/autoload/awaylog.pl
@@ -0,0 +1 @@
+../awaylog.pl \ No newline at end of file
diff --git a/weechat/perl/autoload/beep.pl b/weechat/perl/autoload/beep.pl
new file mode 120000
index 0000000..0cc452e
--- /dev/null
+++ b/weechat/perl/autoload/beep.pl
@@ -0,0 +1 @@
+../beep.pl \ No newline at end of file
diff --git a/weechat/perl/autoload/buffers.pl b/weechat/perl/autoload/buffers.pl
new file mode 120000
index 0000000..445dc3c
--- /dev/null
+++ b/weechat/perl/autoload/buffers.pl
@@ -0,0 +1 @@
+../buffers.pl \ No newline at end of file
diff --git a/weechat/perl/autoload/coords.pl b/weechat/perl/autoload/coords.pl
new file mode 120000
index 0000000..201de83
--- /dev/null
+++ b/weechat/perl/autoload/coords.pl
@@ -0,0 +1 @@
+../coords.pl \ No newline at end of file
diff --git a/weechat/perl/awaylog.pl b/weechat/perl/awaylog.pl
new file mode 100644
index 0000000..91e9b1b
--- /dev/null
+++ b/weechat/perl/awaylog.pl
@@ -0,0 +1,100 @@
+###############################################################################
+#
+# Copyright (c) 2008 by GolemJ <golemj@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+###############################################################################
+#
+# Log highlights msg to core buffer
+# You need to set "notify" to "yes" and "command" to proper command to run
+# external command. You also need "shell" script to run external command.
+#
+# History:
+# 2010-06-20, GolemJ <golemj@gmail.com>
+# version 0.8, add posibility to execute command for notification
+# 2010-02-14, Emmanuel Bouthenot <kolter@openics.org>
+# version 0.7, add colors and notifications support
+# 2009-05-02, FlashCode <flashcode@flashtux.org>:
+# version 0.6, sync with last API changes
+# 2008-11-30, GolemJ <golemj@gmail.com>:
+# version 0.5, conversion to WeeChat 0.3.0+
+#
+###############################################################################
+
+use strict;
+
+weechat::register( "awaylog", "Jiri Golembiovsky", "0.8", "GPL", "Prints highlights to core buffer", "", "" );
+weechat::hook_print( "", "", "", 1, "highlight_cb", "" );
+
+if( weechat::config_get_plugin( "on_away_only" ) eq "" ) {
+ weechat::config_set_plugin( "on_away_only", "off" );
+}
+
+if( weechat::config_get_plugin( "plugin_color" ) eq "" ) {
+ weechat::config_set_plugin( "plugin_color", "default" );
+}
+
+if( weechat::config_get_plugin( "name_color" ) eq "" ) {
+ weechat::config_set_plugin( "name_color", "default" );
+}
+
+if( weechat::config_get_plugin( "notify" ) eq "" ) {
+ weechat::config_set_plugin( "notify", "off" );
+}
+
+if( weechat::config_get_plugin( "command" ) eq "") {
+ weechat::config_set_plugin( "command", "" );
+}
+
+sub highlight_cb {
+ if( $_[5] == 1 ) {
+ my $away = weechat::buffer_get_string($_[1], "localvar_away");
+ if (($away ne "") || (weechat::config_get_plugin( "on_away_only" ) ne "on"))
+ {
+ my $buffer_color = weechat::color(weechat::config_get_plugin( "plugin_color"))
+ . weechat::buffer_get_string($_[1], "plugin")
+ . "."
+ . weechat::buffer_get_string($_[1], "name")
+ . weechat::color("default");
+ my $buffer = weechat::buffer_get_string($_[1], "plugin")
+ . "."
+ . weechat::buffer_get_string($_[1], "name");
+ my $name_color = weechat::color(weechat::config_get_plugin( "name_color"))
+ . $_[6]
+ . weechat::color("default");
+ my $name = $_[6];
+ my $message_color = "${buffer_color} -- ${name_color} :: $_[7]";
+ my $message = "${buffer} -- ${name} :: $_[7]";
+ if( weechat::config_get_plugin( "notify" ) ne "on" ) {
+ my $command = weechat::config_get_plugin( "command" );
+ if( $command ne "" ) {
+ if( $command =~ /\$msg/ ) {
+ $command =~ s/\$msg/\'$message\'/;
+ } else {
+ $command = "$command '$message'";
+ }
+ weechat::command( "", "/shell $command" );
+ } else {
+ weechat::print("", $message_color);
+ }
+ } else {
+ weechat::print_date_tags("", 0, "notify_highlight", $message_color);
+ }
+ }
+ }
+
+ return weechat::WEECHAT_RC_OK;
+}
diff --git a/weechat/perl/beep.pl b/weechat/perl/beep.pl
new file mode 100755
index 0000000..69096c2
--- /dev/null
+++ b/weechat/perl/beep.pl
@@ -0,0 +1,257 @@
+#
+# Copyright (C) 2006-2012 Sebastien Helleu <flashcode@flashtux.org>
+# Copyright (C) 2011 Nils Görs <weechatter@arcor.de>
+# Copyright (C) 2011 ArZa <arza@arza.us>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# Beep (terminal bell) and/or run command on highlight/private message or new DCC.
+#
+# History:
+# 2012-06-05, ldvx<ldvx@freenode>:
+# version 1.1: Added wildcard support for whitelist_nicks.
+# 2012-05-09, ldvx <ldvx@freenode>:
+# version 1.0: Added beep_pv_blacklist, beep_highlight_blacklist, blacklist_nicks,
+# and wildcard support for blacklist_nicks.
+# 2012-05-02, ldvx <ldvx@freenode>:
+# version 0.9: fix regex for nick in tags, add options "whitelist_channels"
+# and "bell_always"
+# 2012-04-19, ldvx <ldvx@freenode>:
+# version 0.8: add whitelist, trigger, use hook_process for commands,
+# rename option "beep_command" to "beep_command_pv", add help for options
+# 2011-04-16, ArZa <arza@arza.us>:
+# version 0.7: fix default beep command
+# 2011-03-11, nils_2 <weechatter@arcor.de>:
+# version 0.6: add additional command options for dcc and highlight
+# 2011-03-09, nils_2 <weechatter@arcor.de>:
+# version 0.5: add option for beep command and dcc
+# 2009-05-02, Sebastien Helleu <flashcode@flashtux.org>:
+# version 0.4: sync with last API changes
+# 2008-11-05, Sebastien Helleu <flashcode@flashtux.org>:
+# version 0.3: conversion to WeeChat 0.3.0+
+# 2007-08-10, Sebastien Helleu <flashcode@flashtux.org>:
+# version 0.2: upgraded licence to GPL 3
+# 2006-09-02, Sebastien Helleu <flashcode@flashtux.org>:
+# version 0.1: initial release
+
+use strict;
+my $SCRIPT_NAME = "beep";
+my $VERSION = "1.1";
+
+# default values in setup file (~/.weechat/plugins.conf)
+my %options_default = ('beep_pv' => ['on', 'beep on private message'],
+ 'beep_pv_whitelist' => ['off', 'turn whitelist for private messages on or off'],
+ 'beep_pv_blacklist' => ['off', 'turn blacklist for private messages on or off'],
+ 'beep_highlight' => ['on', 'beep on highlight'],
+ 'beep_highlight_whitelist' => ['off', 'turn whitelist for highlights on or off'],
+ 'beep_highlight_blacklist' => ['off', 'turn blacklist for highlights on or off'],
+ 'beep_dcc' => ['on', 'beep on dcc'],
+ 'beep_trigger_pv' => ['', 'word that will trigger execution of beep_command_pv (it empty, anything will trigger)'],
+ 'beep_trigger_highlight' => ['', 'word that will trigger execution of beep_command_highlight (if empty, anything will trigger)'],
+ 'beep_command_pv' => ['$bell', 'command for beep on private message, special value "$bell" is allowed, as well as "$bell;command"'],
+ 'beep_command_highlight' => ['$bell', 'command for beep on highlight, special value "$bell" is allowed, as well as "$bell;command"'],
+ 'beep_command_dcc' => ['$bell', 'command for beep on dcc, special value "$bell" is allowed, as well as "$bell;command"'],
+ 'beep_command_timeout' => ['30000', 'timeout for command run (in milliseconds, 0 = never kill (not recommended))'],
+ 'whitelist_nicks' => ['', 'comma-separated list of "server.nick": if not empty, only these nicks will trigger execution of commands (example: "freenode.nick1,freenode.nick2")'],
+ 'blacklist_nicks' => ['', 'comma-separated list of "server.nick": if not empty, these nicks will not be able to trigger execution of commands. Cannot be used in conjuction with whitelist (example: "freenode.nick1,freenode.nick2")'],
+ 'whitelist_channels' => ['', 'comma-separated list of "server.#channel": if not empty, only these channels will trigger execution of commands (example: "freenode.#weechat,freenode.#channel2")'],
+ 'bell_always' => ['', 'use $bell on private messages and/or highlights regardless of trigger and whitelist settings (example: "pv,highlight")'],
+);
+my %options = ();
+
+weechat::register($SCRIPT_NAME, "FlashCode <flashcode\@flashtux.org>", $VERSION,
+ "GPL3", "Beep (terminal bell) and/or run command on highlight/private message or new DCC", "", "");
+init_config();
+
+weechat::hook_config("plugins.var.perl.$SCRIPT_NAME.*", "toggle_config_by_set", "");
+weechat::hook_print("", "", "", 1, "pv_and_highlight", "");
+weechat::hook_signal("irc_dcc", "dcc", "");
+
+sub pv_and_highlight
+{
+ my ($data, $buffer, $date, $tags, $displayed, $highlight, $prefix, $message) = @_;
+
+ # return if message is filtered
+ return weechat::WEECHAT_RC_OK if ($displayed != 1);
+
+ # return if nick in message is own nick
+ my $nick = "";
+ $nick = $2 if ($tags =~ m/(^|,)nick_([^,]*)(,|$)/);
+ return weechat::WEECHAT_RC_OK if (weechat::buffer_get_string($buffer, "localvar_nick") eq $nick);
+
+ # highlight
+ if ($highlight)
+ {
+ # Always print visual bel, regardless of whitelist and trigger settings
+ # beep_command_highlight does not need to contain $bell
+ if ($options{bell_always} =~ m/(^|,)highlight(,|$)/)
+ {
+ print STDERR "\a";
+ }
+ # Channels whitelist for highlights
+ if ($options{beep_highlight} eq "on")
+ {
+ if ($options{whitelist_channels} ne "")
+ {
+ my $serverandchannel = weechat::buffer_get_string($buffer, "localvar_server"). "." .
+ weechat::buffer_get_string($buffer, "localvar_channel");
+ if ($options{beep_trigger_highlight} eq "" or $message =~ m/\b$options{beep_trigger_highlight}\b/)
+ {
+ if ($options{whitelist_channels} =~ /(^|,)$serverandchannel(,|$)/)
+ {
+ beep_exec_command($options{beep_command_highlight});
+ }
+ # What if we are highlighted and we're in a PM? For now, do nothing.
+ }
+ }
+ else
+ {
+ # Execute $bell and/or command with trigger and whitelist/blacklist settings
+ beep_trigger_whitelist_blacklist($buffer, $message, $nick, $options{beep_trigger_highlight},
+ $options{beep_highlight_whitelist}, $options{beep_highlight_blacklist},
+ $options{beep_command_highlight});
+ }
+ }
+ }
+ # private message
+ elsif (weechat::buffer_get_string($buffer, "localvar_type") eq "private" and $tags =~ m/(^|,)notify_private(,|$)/)
+ {
+ # Always print visual bel, regardless of whitelist and trigger settings
+ # beep_command_pv does not need to contain $bell
+ if ($options{bell_always} =~ m/(^|,)pv(,|$)/)
+ {
+ print STDERR "\a";
+ }
+ # Execute $bell and/or command with trigger and whitelist/blacklist settings
+ if ($options{beep_pv} eq "on")
+ {
+ beep_trigger_whitelist_blacklist($buffer, $message, $nick, $options{beep_trigger_pv},
+ $options{beep_pv_whitelist}, $options{beep_pv_blacklist},
+ $options{beep_command_pv});
+ }
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub dcc
+{
+ beep_exec_command($options{beep_command_dcc}) if ($options{beep_dcc} eq "on");
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub beep_trigger_whitelist_blacklist
+{
+ my ($buffer, $message, $nick, $trigger, $whitelist, $blacklist, $command) = @_;
+
+ if ($trigger eq "" or $message =~ m/\b$trigger\b/)
+ {
+ my $serverandnick = weechat::buffer_get_string($buffer, "localvar_server").".".$nick;
+ if ($whitelist eq "on" and $options{whitelist_nicks} ne "")
+ {
+ # What to do if there's a wildcard in the whitelit
+ if ($options{whitelist_nicks} =~ m/\*/ and
+ match_in_wild_card($serverandnick, $options{whitelist_nicks}))
+ {
+ beep_exec_command($command);
+ }
+ # What to do if there's no wildcard in the whitelist
+ elsif ($options{whitelist_nicks} =~ /(^|,)$serverandnick(,|$)/)
+ {
+ beep_exec_command($command);
+ }
+ }
+ elsif ($blacklist eq "on" and $options{blacklist_nicks} ne "")
+ {
+ # What to do if there's a wildcard in the blacklist
+ if ($options{blacklist_nicks} =~ m/\*/ and
+ !match_in_wild_card($serverandnick, $options{blacklist_nicks}))
+ {
+ beep_exec_command($command);
+ }
+ # What to do if there's no wildcard in the blacklist
+ elsif ($options{blacklist_nicks} !~ /(^|,)$serverandnick(,|$)/)
+ {
+ beep_exec_command($command);
+ }
+ }
+ # What to do if we are not using whitelist of blacklist feature
+ elsif ($whitelist eq "off" and $blacklist eq "off")
+ {
+ beep_exec_command($command);
+ }
+ }
+}
+
+sub beep_exec_command
+{
+ my $command = $_[0];
+ if ($command =~ /^\$bell/)
+ {
+ print STDERR "\a";
+ ($command) = $command =~ /^\$bell;(.+)$/;
+ }
+ weechat::hook_process($command, $options{beep_command_timeout}, "my_process", "") if ($command);
+}
+
+sub match_in_wild_card
+{
+ my ($serverandnick, $white_or_black) = @_;
+ my $nick_iter;
+ my @array_of_nicks = split(",", $white_or_black);
+
+ foreach $nick_iter (@array_of_nicks)
+ {
+ $nick_iter =~ s/\*/[^,]*/g;
+ if ($serverandnick =~ /$nick_iter/)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub my_process
+{
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub toggle_config_by_set
+{
+ my ($pointer, $name, $value) = @_;
+ $name = substr($name, length("plugins.var.perl.".$SCRIPT_NAME."."), length($name));
+ $options{$name} = $value;
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub init_config
+{
+ my $version = weechat::info_get("version_number", "") || 0;
+ foreach my $option (keys %options_default)
+ {
+ if (!weechat::config_is_set_plugin($option))
+ {
+ weechat::config_set_plugin($option, $options_default{$option}[0]);
+ $options{$option} = $options_default{$option}[0];
+ }
+ else
+ {
+ $options{$option} = weechat::config_get_plugin($option);
+ }
+ if ($version >= 0x00030500)
+ {
+ weechat::config_set_desc_plugin($option, $options_default{$option}[1]." (default: \"".$options_default{$option}[0]."\")");
+ }
+ }
+}
diff --git a/weechat/perl/buffers.pl b/weechat/perl/buffers.pl
new file mode 100755
index 0000000..e63c3c8
--- /dev/null
+++ b/weechat/perl/buffers.pl
@@ -0,0 +1,1816 @@
+#
+# Copyright (C) 2008-2014 Sebastien Helleu <flashcode@flashtux.org>
+# Copyright (C) 2011-2013 Nils G <weechatter@arcor.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# Display sidebar with list of buffers.
+#
+# History:
+#
+# 2015-05-02, arza <arza@arza.us>:
+# v5.2: truncate long names (name_size_max) more when mark_inactive adds parenthesis
+# 2015-03-29, Ed Santiago <ed@edsantiago.com>:
+# v5.1: merged buffers: always indent, except when filling is horizontal
+# 2014-12-12
+# v5.0: fix cropping non-latin buffer names
+# 2014-08-29, Patrick Steinhardt <ps@pks.im>:
+# v4.9: add support for specifying custom buffer names
+# 2014-07-19, Sebastien Helleu <flashcode@flashtux.org>:
+# v4.8: add support of ctrl + mouse wheel to jump to previous/next buffer,
+# new option "mouse_wheel"
+# 2014-06-22, Sebastien Helleu <flashcode@flashtux.org>:
+# v4.7: fix typos in options
+# 2014-04-05, Sebastien Helleu <flashcode@flashtux.org>:
+# v4.6: add support of hidden buffers (WeeChat >= 0.4.4)
+# 2014-01-01, Sebastien Helleu <flashcode@flashtux.org>:
+# v4.5: add option "mouse_move_buffer"
+# 2013-12-11, Sebastien Helleu <flashcode@flashtux.org>:
+# v4.4: fix buffer number on drag to the end of list when option
+# weechat.look.buffer_auto_renumber is off
+# 2013-12-10, nils_2@freenode.#weechat:
+# v4.3: add options "prefix_bufname" and "suffix_bufname (idea by silverd)
+# : fix hook_timer() for show_lag wasn't disabled
+# : improved signal handling (less updating of buffers list)
+# 2013-11-07, Sebastien Helleu <flashcode@flashtux.org>:
+# v4.2: use default filling "columns_vertical" when bar position is top/bottom
+# 2013-10-31, nils_2@freenode.#weechat:
+# v4.1: add option "detach_buffer_immediately" (idea by farn)
+# 2013-10-20, nils_2@freenode.#weechat:
+# v4.0: add options "detach_displayed_buffers", "detach_display_window_number"
+# 2013-09-27, nils_2@freenode.#weechat:
+# v3.9: add option "toggle_bar" and option "show_prefix_query" (idea by IvarB)
+# : fix problem with linefeed at end of list of buffers (reported by grawity)
+# 2012-10-18, nils_2@freenode.#weechat:
+# v3.8: add option "mark_inactive", to mark buffers you are not in (idea by xrdodrx)
+# : add wildcard "*" for immune_detach_buffers (idea by StarWeaver)
+# : add new options "detach_query" and "detach_free_content" (idea by StarWeaver)
+# 2012-10-06, Nei <anti.teamidiot.de>:
+# v3.7: call menu on right mouse if menu script is loaded.
+# 2012-10-06, nils_2 <weechatter@arcor.de>:
+# v3.6: add new option "hotlist_counter" (idea by torque).
+# 2012-06-02, nils_2 <weechatter@arcor.de>:
+# v3.5: add values "server|channel|private|all|keepserver|none" to option "hide_merged_buffers" (suggested by dominikh).
+# 2012-05-25, nils_2 <weechatter@arcor.de>:
+# v3.4: add new option "show_lag".
+# 2012-04-07, Sebastien Helleu <flashcode@flashtux.org>:
+# v3.3: fix truncation of wide chars in buffer name (option name_size_max) (bug #36034)
+# 2012-03-15, nils_2 <weechatter@arcor.de>:
+# v3.2: add new option "detach"(weechat >= 0.3.8)
+# add new option "immune_detach_buffers" (requested by Mkaysi)
+# add new function buffers_whitelist add|del|reset (suggested by FiXato)
+# add new function buffers_detach add|del|reset
+# 2012-03-09, Sebastien Helleu <flashcode@flashtux.org>:
+# v3.1: fix reload of config file
+# 2012-01-29, nils_2 <weechatter@arcor.de>:
+# v3.0: fix: buffers did not update directly during window_switch (reported by FiXato)
+# 2012-01-29, nils_2 <weechatter@arcor.de>:
+# v2.9: add options "name_size_max" and "name_crop_suffix"
+# 2012-01-08, nils_2 <weechatter@arcor.de>:
+# v2.8: fix indenting for option "show_number off"
+# fix unset of buffer activity in hotlist when buffer was moved with mouse
+# add buffer with free content and core buffer sorted first (suggested by nyuszika7h)
+# add options queries_default_fg/bg and queries_message_fg/bg (suggested by FiXato)
+# add clicking with left button on current buffer will do a jump_previously_visited_buffer (suggested by FiXato)
+# add clicking with right button on current buffer will do a jump_next_visited_buffer
+# add additional informations in help texts
+# add default_fg and default_bg for whitelist channels
+# internal changes (script is now 3Kb smaller)
+# 2012-01-04, Sebastien Helleu <flashcode@flashtux.org>:
+# v2.7: fix regex lookup in whitelist buffers list
+# 2011-12-04, nils_2 <weechatter@arcor.de>:
+# v2.6: add own config file (buffers.conf)
+# add new behavior for indenting (under_name)
+# add new option to set different color for server buffers and buffers with free content
+# 2011-10-30, nils_2 <weechatter@arcor.de>:
+# v2.5: add new options "show_number_char" and "color_number_char",
+# add help-description for options
+# 2011-08-24, Sebastien Helleu <flashcode@flashtux.org>:
+# v2.4: add mouse support
+# 2011-06-06, nils_2 <weechatter@arcor.de>:
+# v2.3: added: missed option "color_whitelist_default"
+# 2011-03-23, Sebastien Helleu <flashcode@flashtux.org>:
+# v2.2: fix color of nick prefix with WeeChat >= 0.3.5
+# 2011-02-13, nils_2 <weechatter@arcor.de>:
+# v2.1: add options "color_whitelist_*"
+# 2010-10-05, Sebastien Helleu <flashcode@flashtux.org>:
+# v2.0: add options "sort" and "show_number"
+# 2010-04-12, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.9: replace call to log() by length() to align buffer numbers
+# 2010-04-02, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.8: fix bug with background color and option indenting_number
+# 2010-04-02, Helios <helios@efemes.de>:
+# v1.7: add indenting_number option
+# 2010-02-25, m4v <lambdae2@gmail.com>:
+# v1.6: add option to hide empty prefixes
+# 2010-02-12, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.5: add optional nick prefix for buffers like IRC channels
+# 2009-09-30, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.4: remove spaces for indenting when bar position is top/bottom
+# 2009-06-14, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.3: add option "hide_merged_buffers"
+# 2009-06-14, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.2: improve display with merged buffers
+# 2009-05-02, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.1: sync with last API changes
+# 2009-02-21, Sebastien Helleu <flashcode@flashtux.org>:
+# v1.0: remove timer used to update bar item first time (not needed any more)
+# 2009-02-17, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.9: fix bug with indenting of private buffers
+# 2009-01-04, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.8: update syntax for command /set (comments)
+# 2008-10-20, Jiri Golembiovsky <golemj@gmail.com>:
+# v0.7: add indenting option
+# 2008-10-01, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.6: add default color for buffers, and color for current active buffer
+# 2008-09-18, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.5: fix color for "low" level entry in hotlist
+# 2008-09-18, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.4: rename option "show_category" to "short_names",
+# remove option "color_slash"
+# 2008-09-15, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.3: fix bug with priority in hotlist (var not defined)
+# 2008-09-02, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.2: add color for buffers with activity and config options for
+# colors, add config option to display/hide categories
+# 2008-03-15, Sebastien Helleu <flashcode@flashtux.org>:
+# v0.1: script creation
+#
+# Help about settings:
+# display all settings for script (or use iset.pl script to change settings):
+# /set buffers*
+# show help text for option buffers.look.whitelist_buffers:
+# /help buffers.look.whitelist_buffers
+#
+# Mouse-support (standard key bindings):
+# left mouse-button:
+# - click on a buffer to switch to selected buffer
+# - click on current buffer will do action jump_previously_visited_buffer
+# - drag a buffer and drop it on another position will move the buffer to position
+# right mouse-button:
+# - click on current buffer will do action jump_next_visited_buffer
+# - moving buffer to the left/right will close buffer.
+#
+
+use strict;
+use Encode qw( decode encode );
+# -----------------------------[ internal ]-------------------------------------
+my $SCRIPT_NAME = "buffers";
+my $SCRIPT_VERSION = "5.2";
+
+my $BUFFERS_CONFIG_FILE_NAME = "buffers";
+my $buffers_config_file;
+my $cmd_buffers_whitelist= "buffers_whitelist";
+my $cmd_buffers_detach = "buffers_detach";
+
+my $maxlength;
+
+my %mouse_keys = ("\@item(buffers):button1*" => "hsignal:buffers_mouse",
+ "\@item(buffers):button2*" => "hsignal:buffers_mouse",
+ "\@bar(buffers):ctrl-wheelup" => "hsignal:buffers_mouse",
+ "\@bar(buffers):ctrl-wheeldown" => "hsignal:buffers_mouse");
+my %options;
+my %hotlist_level = (0 => "low", 1 => "message", 2 => "private", 3 => "highlight");
+my @whitelist_buffers = ();
+my @immune_detach_buffers= ();
+my @detach_buffer_immediately= ();
+my @buffers_focus = ();
+my %buffers_timer = ();
+my %Hooks = ();
+
+# --------------------------------[ init ]--------------------------------------
+weechat::register($SCRIPT_NAME, "Sebastien Helleu <flashcode\@flashtux.org>",
+ $SCRIPT_VERSION, "GPL3",
+ "Sidebar with list of buffers", "shutdown_cb", "");
+my $weechat_version = weechat::info_get("version_number", "") || 0;
+
+buffers_config_init();
+buffers_config_read();
+
+weechat::bar_item_new($SCRIPT_NAME, "build_buffers", "");
+weechat::bar_new($SCRIPT_NAME, "0", "0", "root", "", "left", "columns_vertical",
+ "vertical", "0", "0", "default", "default", "default", "1",
+ $SCRIPT_NAME);
+
+if ( check_bar_item() == 0 )
+{
+ weechat::command("", "/bar show " . $SCRIPT_NAME) if ( weechat::config_boolean($options{"toggle_bar"}) eq 1 );
+}
+
+weechat::hook_signal("buffer_opened", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_closed", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_merged", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_unmerged", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_moved", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_renamed", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_switch", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_hidden", "buffers_signal_buffer", ""); # WeeChat >= 0.4.4
+weechat::hook_signal("buffer_unhidden", "buffers_signal_buffer", ""); # WeeChat >= 0.4.4
+weechat::hook_signal("buffer_localvar_added", "buffers_signal_buffer", "");
+weechat::hook_signal("buffer_localvar_changed", "buffers_signal_buffer", "");
+
+weechat::hook_signal("window_switch", "buffers_signal_buffer", "");
+weechat::hook_signal("hotlist_changed", "buffers_signal_hotlist", "");
+#weechat::hook_command_run("/input switch_active_*", "buffers_signal_buffer", "");
+weechat::bar_item_update($SCRIPT_NAME);
+
+
+if ($weechat_version >= 0x00030600)
+{
+ weechat::hook_focus($SCRIPT_NAME, "buffers_focus_buffers", "");
+ weechat::hook_hsignal("buffers_mouse", "buffers_hsignal_mouse", "");
+ weechat::key_bind("mouse", \%mouse_keys);
+}
+
+weechat::hook_command($cmd_buffers_whitelist,
+ "add/del current buffer to/from buffers whitelist",
+ "[add] || [del] || [reset]",
+ " add: add current buffer in configuration file\n".
+ " del: delete current buffer from configuration file\n".
+ "reset: reset all buffers from configuration file ".
+ "(no confirmation!)\n\n".
+ "Examples:\n".
+ "/$cmd_buffers_whitelist add\n",
+ "add %-||".
+ "del %-||".
+ "reset %-",
+ "buffers_cmd_whitelist", "");
+weechat::hook_command($cmd_buffers_detach,
+ "add/del current buffer to/from buffers detach",
+ "[add] || [del] || [reset]",
+ " add: add current buffer in configuration file\n".
+ " del: delete current buffer from configuration file\n".
+ "reset: reset all buffers from configuration file ".
+ "(no confirmation!)\n\n".
+ "Examples:\n".
+ "/$cmd_buffers_detach add\n",
+ "add %-||".
+ "del %-||".
+ "reset %-",
+ "buffers_cmd_detach", "");
+
+if ($weechat_version >= 0x00030800)
+{
+ weechat::hook_config("buffers.look.detach", "hook_timer_detach", "");
+ if (weechat::config_integer($options{"detach"}) > 0)
+ {
+ $Hooks{timer_detach} = weechat::hook_timer(weechat::config_integer($options{"detach"}) * 1000,
+ 60, 0, "buffers_signal_hotlist", "");
+ }
+}
+
+weechat::hook_config("buffers.look.show_lag", "hook_timer_lag", "");
+
+if (weechat::config_boolean($options{"show_lag"}))
+{
+ $Hooks{timer_lag} = weechat::hook_timer(
+ weechat::config_integer(weechat::config_get("irc.network.lag_refresh_interval")) * 1000,
+ 0, 0, "buffers_signal_hotlist", "");
+}
+
+# -------------------------------- [ command ] --------------------------------
+sub buffers_cmd_whitelist
+{
+my ( $data, $buffer, $args ) = @_;
+ $args = lc($args);
+ my $buffers_whitelist = weechat::config_string( weechat::config_get("buffers.look.whitelist_buffers") );
+ return weechat::WEECHAT_RC_OK if ( $buffers_whitelist eq "" and $args eq "del" or $buffers_whitelist eq "" and $args eq "reset" );
+ my @buffers_list = split( /,/, $buffers_whitelist );
+ # get buffers name
+ my $infolist = weechat::infolist_get("buffer", weechat::current_buffer(), "");
+ weechat::infolist_next($infolist);
+ my $buffers_name = weechat::infolist_string($infolist, "name");
+ weechat::infolist_free($infolist);
+ return weechat::WEECHAT_RC_OK if ( $buffers_name eq "" ); # should never happen
+
+ if ( $args eq "add" )
+ {
+ return weechat::WEECHAT_RC_OK if ( grep /^$buffers_name$/, @buffers_list ); # check if buffer already in list
+ push @buffers_list, ( $buffers_name );
+ my $buffers_list = &create_whitelist(\@buffers_list);
+ weechat::config_option_set( weechat::config_get("buffers.look.whitelist_buffers"), $buffers_list, 1);
+ weechat::print(weechat::current_buffer(), "buffer \"$buffers_name\" added to buffers whitelist");
+ }
+ elsif ( $args eq "del" )
+ {
+ return weechat::WEECHAT_RC_OK unless ( grep /^$buffers_name$/, @buffers_list ); # check if buffer is in list
+ @buffers_list = grep {$_ ne $buffers_name} @buffers_list; # delete entry
+ my $buffers_list = &create_whitelist(\@buffers_list);
+ weechat::config_option_set( weechat::config_get("buffers.look.whitelist_buffers"), $buffers_list, 1);
+ weechat::print(weechat::current_buffer(), "buffer \"$buffers_name\" deleted from buffers whitelist");
+ }
+ elsif ( $args eq "reset" )
+ {
+ return weechat::WEECHAT_RC_OK if ( $buffers_whitelist eq "" );
+ weechat::config_option_set( weechat::config_get("buffers.look.whitelist_buffers"), "", 1);
+ weechat::print(weechat::current_buffer(), "buffers whitelist is empty, now...");
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+sub buffers_cmd_detach
+{
+ my ( $data, $buffer, $args ) = @_;
+ $args = lc($args);
+ my $immune_detach_buffers = weechat::config_string( weechat::config_get("buffers.look.immune_detach_buffers") );
+ return weechat::WEECHAT_RC_OK if ( $immune_detach_buffers eq "" and $args eq "del" or $immune_detach_buffers eq "" and $args eq "reset" );
+
+ my @buffers_list = split( /,/, $immune_detach_buffers );
+ # get buffers name
+ my $infolist = weechat::infolist_get("buffer", weechat::current_buffer(), "");
+ weechat::infolist_next($infolist);
+ my $buffers_name = weechat::infolist_string($infolist, "name");
+ weechat::infolist_free($infolist);
+ return weechat::WEECHAT_RC_OK if ( $buffers_name eq "" ); # should never happen
+
+ if ( $args eq "add" )
+ {
+ return weechat::WEECHAT_RC_OK if ( grep /^$buffers_name$/, @buffers_list ); # check if buffer already in list
+ push @buffers_list, ( $buffers_name );
+ my $buffers_list = &create_whitelist(\@buffers_list);
+ weechat::config_option_set( weechat::config_get("buffers.look.immune_detach_buffers"), $buffers_list, 1);
+ weechat::print(weechat::current_buffer(), "buffer \"$buffers_name\" added to immune detach buffers");
+ }
+ elsif ( $args eq "del" )
+ {
+ return weechat::WEECHAT_RC_OK unless ( grep /^$buffers_name$/, @buffers_list ); # check if buffer is in list
+ @buffers_list = grep {$_ ne $buffers_name} @buffers_list; # delete entry
+ my $buffers_list = &create_whitelist(\@buffers_list);
+ weechat::config_option_set( weechat::config_get("buffers.look.immune_detach_buffers"), $buffers_list, 1);
+ weechat::print(weechat::current_buffer(), "buffer \"$buffers_name\" deleted from immune detach buffers");
+ }
+ elsif ( $args eq "reset" )
+ {
+ return weechat::WEECHAT_RC_OK if ( $immune_detach_buffers eq "" );
+ weechat::config_option_set( weechat::config_get("buffers.look.immune_detach_buffers"), "", 1);
+ weechat::print(weechat::current_buffer(), "immune detach buffers is empty, now...");
+ }
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub create_whitelist
+{
+ my @buffers_list = @{$_[0]};
+ my $buffers_list = "";
+ foreach (@buffers_list)
+ {
+ $buffers_list .= $_ .",";
+ }
+ # remove last ","
+ chop $buffers_list;
+ return $buffers_list;
+}
+
+# -------------------------------- [ config ] --------------------------------
+sub hook_timer_detach
+{
+ my $detach = $_[2];
+ if ( $detach eq 0 )
+ {
+ weechat::unhook($Hooks{timer_detach}) if $Hooks{timer_detach};
+ $Hooks{timer_detach} = "";
+ }
+ else
+ {
+ weechat::unhook($Hooks{timer_detach}) if $Hooks{timer_detach};
+ $Hooks{timer_detach} = weechat::hook_timer( weechat::config_integer( $options{"detach"}) * 1000, 60, 0, "buffers_signal_hotlist", "");
+ }
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub hook_timer_lag
+{
+ my $lag = $_[2];
+ if ( $lag eq "off" )
+ {
+ weechat::unhook($Hooks{timer_lag}) if $Hooks{timer_lag};
+ $Hooks{timer_lag} = "";
+ }
+ else
+ {
+ weechat::unhook($Hooks{timer_lag}) if $Hooks{timer_lag};
+ $Hooks{timer_lag} = weechat::hook_timer( weechat::config_integer(weechat::config_get("irc.network.lag_refresh_interval")) * 1000, 0, 0, "buffers_signal_hotlist", "");
+ }
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub buffers_config_read
+{
+ return weechat::config_read($buffers_config_file) if ($buffers_config_file ne "");
+}
+sub buffers_config_write
+{
+ return weechat::config_write($buffers_config_file) if ($buffers_config_file ne "");
+}
+sub buffers_config_reload_cb
+{
+ my ($data, $config_file) = ($_[0], $_[1]);
+ return weechat::config_reload($config_file)
+}
+sub buffers_config_init
+{
+ $buffers_config_file = weechat::config_new($BUFFERS_CONFIG_FILE_NAME,
+ "buffers_config_reload_cb", "");
+ return if ($buffers_config_file eq "");
+
+my %default_options_color =
+("color_current_fg" => [
+ "current_fg", "color",
+ "foreground color for current buffer",
+ "", 0, 0, "lightcyan", "lightcyan", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_current_bg" => [
+ "current_bg", "color",
+ "background color for current buffer",
+ "", 0, 0, "red", "red", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_default_fg" => [
+ "default_fg", "color",
+ "default foreground color for buffer name",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_default_bg" => [
+ "default_bg", "color",
+ "default background color for buffer name",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_highlight_fg" => [
+ "hotlist_highlight_fg", "color",
+ "change foreground color of buffer name if a highlight messaged received",
+ "", 0, 0, "magenta", "magenta", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_highlight_bg" => [
+ "hotlist_highlight_bg", "color",
+ "change background color of buffer name if a highlight messaged received",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_low_fg" => [
+ "hotlist_low_fg", "color",
+ "change foreground color of buffer name if a low message received",
+ "", 0, 0, "white", "white", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_low_bg" => [
+ "hotlist_low_bg", "color",
+ "change background color of buffer name if a low message received",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_message_fg" => [
+ "hotlist_message_fg", "color",
+ "change foreground color of buffer name if a normal message received",
+ "", 0, 0, "yellow", "yellow", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_message_bg" => [
+ "hotlist_message_bg", "color",
+ "change background color of buffer name if a normal message received",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_private_fg" => [
+ "hotlist_private_fg", "color",
+ "change foreground color of buffer name if a private message received",
+ "", 0, 0, "lightgreen", "lightgreen", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_hotlist_private_bg" => [
+ "hotlist_private_bg", "color",
+ "change background color of buffer name if a private message received",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_number" => [
+ "number", "color",
+ "color for buffer number",
+ "", 0, 0, "lightgreen", "lightgreen", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_number_char" => [
+ "number_char", "color",
+ "color for buffer number char",
+ "", 0, 0, "lightgreen", "lightgreen", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_default_fg" => [
+ "whitelist_default_fg", "color",
+ "default foreground color for whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_default_bg" => [
+ "whitelist_default_bg", "color",
+ "default background color for whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_low_fg" => [
+ "whitelist_low_fg", "color",
+ "low color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_low_bg" => [
+ "whitelist_low_bg", "color",
+ "low color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_message_fg" => [
+ "whitelist_message_fg", "color",
+ "message color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_message_bg" => [
+ "whitelist_message_bg", "color",
+ "message color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_private_fg" => [
+ "whitelist_private_fg", "color",
+ "private color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_private_bg" => [
+ "whitelist_private_bg", "color",
+ "private color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_highlight_fg" => [
+ "whitelist_highlight_fg", "color",
+ "highlight color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_whitelist_highlight_bg" => [
+ "whitelist_highlight_bg", "color",
+ "highlight color of whitelist buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_none_channel_fg" => [
+ "none_channel_fg", "color",
+ "foreground color for none channel buffer (e.g.: core/server/plugin ".
+ "buffer)",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_none_channel_bg" => [
+ "none_channel_bg", "color",
+ "background color for none channel buffer (e.g.: core/server/plugin ".
+ "buffer)",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "queries_default_fg" => [
+ "queries_default_fg", "color",
+ "foreground color for query buffer without message",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "queries_default_bg" => [
+ "queries_default_bg", "color",
+ "background color for query buffer without message",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "queries_message_fg" => [
+ "queries_message_fg", "color",
+ "foreground color for query buffer with unread message",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "queries_message_bg" => [
+ "queries_message_bg", "color",
+ "background color for query buffer with unread message",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "queries_highlight_fg" => [
+ "queries_highlight_fg", "color",
+ "foreground color for query buffer with unread highlight",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "queries_highlight_bg" => [
+ "queries_highlight_bg", "color",
+ "background color for query buffer with unread highlight",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_prefix_bufname" => [
+ "prefix_bufname", "color",
+ "color for prefix of buffer name",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "color_suffix_bufname" => [
+ "suffix_bufname", "color",
+ "color for suffix of buffer name",
+ "", 0, 0, "default", "default", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+);
+
+my %default_options_look =
+(
+ "hotlist_counter" => [
+ "hotlist_counter", "boolean",
+ "show number of message for the buffer (this option needs WeeChat >= ".
+ "0.3.5). The relevant option for notification is \"weechat.look.".
+ "buffer_notify_default\"",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_lag" => [
+ "show_lag", "boolean",
+ "show lag behind server name. This option is using \"irc.color.".
+ "item_lag_finished\", ".
+ "\"irc.network.lag_min_show\" and \"irc.network.lag_refresh_interval\"",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "look_whitelist_buffers" => [
+ "whitelist_buffers", "string",
+ "comma separated list of buffers for using a different color scheme ".
+ "(for example: freenode.#weechat,freenode.#weechat-fr)",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config_whitelist", "", "", ""
+ ],
+ "hide_merged_buffers" => [
+ "hide_merged_buffers", "integer",
+ "hide merged buffers. The value determines which merged buffers should ".
+ "be hidden, keepserver meaning 'all except server buffers'. Other values ".
+ "correspondent to the buffer type.",
+ "server|channel|private|keepserver|all|none", 0, 0, "none", "none", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "indenting" => [
+ "indenting", "integer", "use indenting for channel and query buffers. ".
+ "This option only takes effect if bar is left/right positioned",
+ "off|on|under_name", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "indenting_number" => [
+ "indenting_number", "boolean",
+ "use indenting for numbers. This option only takes effect if bar is ".
+ "left/right positioned",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "short_names" => [
+ "short_names", "boolean",
+ "display short names (remove text before first \".\" in buffer name)",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_number" => [
+ "show_number", "boolean",
+ "display buffer number in front of buffer name",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_number_char" => [
+ "number_char", "string",
+ "display a char behind buffer number",
+ "", 0, 0, ".", ".", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_prefix_bufname" => [
+ "prefix_bufname", "string",
+ "prefix displayed in front of buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_suffix_bufname" => [
+ "suffix_bufname", "string",
+ "suffix displayed at end of buffer name",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_prefix" => [
+ "prefix", "boolean",
+ "displays your prefix for channel in front of buffer name",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_prefix_empty" => [
+ "prefix_empty", "boolean",
+ "use a placeholder for channels without prefix",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "show_prefix_query" => [
+ "prefix_for_query", "string",
+ "prefix displayed in front of query buffer",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "sort" => [
+ "sort", "integer",
+ "sort buffer-list by \"number\" or \"name\"",
+ "number|name", 0, 0, "number", "number", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "core_to_front" => [
+ "core_to_front", "boolean",
+ "core buffer and buffers with free content will be listed first. ".
+ "Take only effect if buffer sort is by name",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "jump_prev_next_visited_buffer" => [
+ "jump_prev_next_visited_buffer", "boolean",
+ "jump to previously or next visited buffer if you click with ".
+ "left/right mouse button on currently visiting buffer",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "name_size_max" => [
+ "name_size_max", "integer",
+ "maximum size of buffer name. 0 means no limitation",
+ "", 0, 256, 0, 0, 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "name_crop_suffix" => [
+ "name_crop_suffix", "string",
+ "contains an optional char(s) that is appended when buffer name is ".
+ "shortened",
+ "", 0, 0, "+", "+", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "detach" => [
+ "detach", "integer",
+ "detach buffer from buffers list after a specific period of time ".
+ "(in seconds) without action (weechat ≥ 0.3.8 required) (0 means \"off\")",
+ "", 0, 31536000, 0, "number", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "immune_detach_buffers" => [
+ "immune_detach_buffers", "string",
+ "comma separated list of buffers to NOT automatically detach. ".
+ "Allows \"*\" wildcard. Ex: \"BitlBee,freenode.*\"",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config_immune_detach_buffers", "", "", ""
+ ],
+ "detach_query" => [
+ "detach_query", "boolean",
+ "query buffer will be detached",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "detach_buffer_immediately" => [
+ "detach_buffer_immediately", "string",
+ "comma separated list of buffers to detach immediately. A query and ".
+ "highlight message will attach buffer again. Allows \"*\" wildcard. ".
+ "Ex: \"BitlBee,freenode.*\"",
+ "", 0, 0, "", "", 0,
+ "", "", "buffers_signal_config_detach_buffer_immediately", "", "", ""
+ ],
+ "detach_free_content" => [
+ "detach_free_content", "boolean",
+ "buffers with free content will be detached (Ex: iset, chanmon)",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "detach_displayed_buffers" => [
+ "detach_displayed_buffers", "boolean",
+ "buffers displayed in a (split) window will be detached",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "detach_display_window_number" => [
+ "detach_display_window_number", "boolean",
+ "window number will be add, behind buffer name (this option takes only ".
+ "effect with \"detach_displayed_buffers\" option)",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "mark_inactive" => [
+ "mark_inactive", "boolean",
+ "if option is \"on\", inactive buffers (those you are not in) will have ".
+ "parentheses around them. An inactive buffer will not be detached.",
+ "", 0, 0, "off", "off", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "toggle_bar" => [
+ "toggle_bar", "boolean",
+ "if option is \"on\", buffers bar will hide/show when script is ".
+ "(un)loaded.",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "mouse_move_buffer" => [
+ "mouse_move_buffer", "boolean",
+ "if option is \"on\", mouse gestures (drag & drop) can move buffers in list.",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+ "mouse_wheel" => [
+ "mouse_wheel", "boolean",
+ "if option is \"on\", mouse wheel jumps to previous/next buffer in list.",
+ "", 0, 0, "on", "on", 0,
+ "", "", "buffers_signal_config", "", "", ""
+ ],
+);
+ # section "color"
+ my $section_color = weechat::config_new_section(
+ $buffers_config_file,
+ "color", 0, 0, "", "", "", "", "", "", "", "", "", "");
+ if ($section_color eq "")
+ {
+ weechat::config_free($buffers_config_file);
+ return;
+ }
+ foreach my $option (keys %default_options_color)
+ {
+ $options{$option} = weechat::config_new_option(
+ $buffers_config_file,
+ $section_color,
+ $default_options_color{$option}[0],
+ $default_options_color{$option}[1],
+ $default_options_color{$option}[2],
+ $default_options_color{$option}[3],
+ $default_options_color{$option}[4],
+ $default_options_color{$option}[5],
+ $default_options_color{$option}[6],
+ $default_options_color{$option}[7],
+ $default_options_color{$option}[8],
+ $default_options_color{$option}[9],
+ $default_options_color{$option}[10],
+ $default_options_color{$option}[11],
+ $default_options_color{$option}[12],
+ $default_options_color{$option}[13],
+ $default_options_color{$option}[14]);
+ }
+
+ # section "look"
+ my $section_look = weechat::config_new_section(
+ $buffers_config_file,
+ "look", 0, 0, "", "", "", "", "", "", "", "", "", "");
+ if ($section_look eq "")
+ {
+ weechat::config_free($buffers_config_file);
+ return;
+ }
+ foreach my $option (keys %default_options_look)
+ {
+ $options{$option} = weechat::config_new_option(
+ $buffers_config_file,
+ $section_look,
+ $default_options_look{$option}[0],
+ $default_options_look{$option}[1],
+ $default_options_look{$option}[2],
+ $default_options_look{$option}[3],
+ $default_options_look{$option}[4],
+ $default_options_look{$option}[5],
+ $default_options_look{$option}[6],
+ $default_options_look{$option}[7],
+ $default_options_look{$option}[8],
+ $default_options_look{$option}[9],
+ $default_options_look{$option}[10],
+ $default_options_look{$option}[11],
+ $default_options_look{$option}[12],
+ $default_options_look{$option}[13],
+ $default_options_look{$option}[14],
+ $default_options_look{$option}[15]);
+ }
+}
+
+sub build_buffers
+{
+ my $str = "";
+
+ # get bar position (left/right/top/bottom)
+ my $position = "left";
+ my $option_position = weechat::config_get("weechat.bar.buffers.position");
+ if ($option_position ne "")
+ {
+ $position = weechat::config_string($option_position);
+ }
+
+ # read hotlist
+ my %hotlist;
+ my $infolist = weechat::infolist_get("hotlist", "", "");
+ while (weechat::infolist_next($infolist))
+ {
+ $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")} =
+ weechat::infolist_integer($infolist, "priority");
+ if ( weechat::config_boolean( $options{"hotlist_counter"} ) eq 1 and $weechat_version >= 0x00030500)
+ {
+ $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_00"} =
+ weechat::infolist_integer($infolist, "count_00"); # low message
+ $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_01"} =
+ weechat::infolist_integer($infolist, "count_01"); # channel message
+ $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_02"} =
+ weechat::infolist_integer($infolist, "count_02"); # private message
+ $hotlist{weechat::infolist_pointer($infolist, "buffer_pointer")."_count_03"} =
+ weechat::infolist_integer($infolist, "count_03"); # highlight message
+ }
+ }
+ weechat::infolist_free($infolist);
+
+ # read buffers list
+ @buffers_focus = ();
+ my @buffers;
+ my @current1 = ();
+ my @current2 = ();
+ my $old_number = -1;
+ my $max_number = 0;
+ my $max_number_digits = 0;
+ my $active_seen = 0;
+ $infolist = weechat::infolist_get("buffer", "", "");
+ while (weechat::infolist_next($infolist))
+ {
+ # ignore hidden buffers (WeeChat >= 0.4.4)
+ if ($weechat_version >= 0x00040400)
+ {
+ next if (weechat::infolist_integer($infolist, "hidden"));
+ }
+ my $buffer;
+ my $number = weechat::infolist_integer($infolist, "number");
+ if ($number ne $old_number)
+ {
+ @buffers = (@buffers, @current2, @current1);
+ @current1 = ();
+ @current2 = ();
+ $active_seen = 0;
+ }
+ if ($number > $max_number)
+ {
+ $max_number = $number;
+ }
+ $old_number = $number;
+ my $active = weechat::infolist_integer($infolist, "active");
+ if ($active)
+ {
+ $active_seen = 1;
+ }
+ $buffer->{"pointer"} = weechat::infolist_pointer($infolist, "pointer");
+ $buffer->{"number"} = $number;
+ $buffer->{"active"} = $active;
+ $buffer->{"current_buffer"} = weechat::infolist_integer($infolist, "current_buffer");
+ $buffer->{"num_displayed"} = weechat::infolist_integer($infolist, "num_displayed");
+ $buffer->{"plugin_name"} = weechat::infolist_string($infolist, "plugin_name");
+ $buffer->{"name"} = weechat::infolist_string($infolist, "name");
+ $buffer->{"short_name"} = weechat::infolist_string($infolist, "short_name");
+ $buffer->{"full_name"} = $buffer->{"plugin_name"}.".".$buffer->{"name"};
+ $buffer->{"type"} = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type");
+ #weechat::print("", $buffer->{"type"});
+
+ # check if buffer is active (or maybe a /part, /kick channel)
+ if ($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1)
+ {
+ my $server = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_server");
+ my $channel = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_channel");
+ my $infolist_channel = weechat::infolist_get("irc_channel", "", $server.",".$channel);
+ if ($infolist_channel)
+ {
+ weechat::infolist_next($infolist_channel);
+ $buffer->{"nicks_count"} = weechat::infolist_integer($infolist_channel, "nicks_count");
+ }else
+ {
+ $buffer->{"nicks_count"} = 0;
+ }
+ weechat::infolist_free($infolist_channel);
+ }
+
+ my $result = check_immune_detached_buffers($buffer->{"name"}); # checking for wildcard
+
+ next if ( check_detach_buffer_immediately($buffer->{"name"}) eq 1
+ and $buffer->{"current_buffer"} eq 0
+ and ( not exists $hotlist{$buffer->{"pointer"}} or $hotlist{$buffer->{"pointer"}} < 2) ); # checking for buffer to immediately detach
+
+ unless ($result)
+ {
+ my $detach_time = weechat::config_integer( $options{"detach"});
+ my $current_time = time();
+ # set timer for buffers with no hotlist action
+ $buffers_timer{$buffer->{"pointer"}} = $current_time
+ if ( not exists $hotlist{$buffer->{"pointer"}}
+ and $buffer->{"type"} eq "channel"
+ and not exists $buffers_timer{$buffer->{"pointer"}}
+ and $detach_time > 0);
+
+ $buffers_timer{$buffer->{"pointer"}} = $current_time
+ if (weechat::config_boolean($options{"detach_query"}) eq 1
+ and not exists $hotlist{$buffer->{"pointer"}}
+ and $buffer->{"type"} eq "private"
+ and not exists $buffers_timer{$buffer->{"pointer"}}
+ and $detach_time > 0);
+
+ $detach_time = 0
+ if (weechat::config_boolean($options{"detach_query"}) eq 0
+ and $buffer->{"type"} eq "private");
+
+ # free content buffer
+ $buffers_timer{$buffer->{"pointer"}} = $current_time
+ if (weechat::config_boolean($options{"detach_free_content"}) eq 1
+ and not exists $hotlist{$buffer->{"pointer"}}
+ and $buffer->{"type"} eq ""
+ and not exists $buffers_timer{$buffer->{"pointer"}}
+ and $detach_time > 0);
+ $detach_time = 0
+ if (weechat::config_boolean($options{"detach_free_content"}) eq 0
+ and $buffer->{"type"} eq "");
+
+ $detach_time = 0 if (weechat::config_boolean($options{"mark_inactive"}) eq 1 and defined $buffer->{"nicks_count"} and $buffer->{"nicks_count"} == 0);
+
+ # check for detach
+ unless ( $buffer->{"current_buffer"} eq 0
+ and not exists $hotlist{$buffer->{"pointer"}}
+# and $buffer->{"type"} eq "channel"
+ and exists $buffers_timer{$buffer->{"pointer"}}
+ and $detach_time > 0
+ and $weechat_version >= 0x00030800
+ and $current_time - $buffers_timer{$buffer->{"pointer"}} >= $detach_time)
+ {
+ if ($active_seen)
+ {
+ push(@current2, $buffer);
+ }
+ else
+ {
+ push(@current1, $buffer);
+ }
+ }
+ elsif ( $buffer->{"current_buffer"} eq 0
+ and not exists $hotlist{$buffer->{"pointer"}}
+# and $buffer->{"type"} eq "channel"
+ and exists $buffers_timer{$buffer->{"pointer"}}
+ and $detach_time > 0
+ and $weechat_version >= 0x00030800
+ and $current_time - $buffers_timer{$buffer->{"pointer"}} >= $detach_time)
+ { # check for option detach_displayed_buffers and if buffer is displayed in a split window
+ if ( $buffer->{"num_displayed"} eq 1
+ and weechat::config_boolean($options{"detach_displayed_buffers"}) eq 0 )
+ {
+ my $infolist_window = weechat::infolist_get("window", "", "");
+ while (weechat::infolist_next($infolist_window))
+ {
+ my $buffer_ptr = weechat::infolist_pointer($infolist_window, "buffer");
+ if ($buffer_ptr eq $buffer->{"pointer"})
+ {
+ $buffer->{"window"} = weechat::infolist_integer($infolist_window, "number");
+ }
+ }
+ weechat::infolist_free($infolist_window);
+
+ push(@current2, $buffer);
+ }
+ }
+ }
+ else # buffer in "immune_detach_buffers"
+ {
+ if ($active_seen)
+ {
+ push(@current2, $buffer);
+ }
+ else
+ {
+ push(@current1, $buffer);
+ }
+ }
+ } # while end
+
+
+ if ($max_number >= 1)
+ {
+ $max_number_digits = length(int($max_number));
+ }
+ @buffers = (@buffers, @current2, @current1);
+ weechat::infolist_free($infolist);
+
+ # sort buffers by number, name or shortname
+ my %sorted_buffers;
+ if (1)
+ {
+ my $number = 0;
+ for my $buffer (@buffers)
+ {
+ my $key;
+ if (weechat::config_integer( $options{"sort"} ) eq 1) # number = 0; name = 1
+ {
+ my $name = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_custom_name");
+ if (not defined $name or $name eq "") {
+ if (weechat::config_boolean( $options{"short_names"} ) eq 1) {
+ $name = $buffer->{"short_name"};
+ } else {
+ $name = $buffer->{"name"};
+ }
+ }
+ if (weechat::config_integer($options{"name_size_max"}) >= 1)
+ {
+ $maxlength = weechat::config_integer($options{"name_size_max"});
+ if($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buffer->{"nicks_count"} == 0)
+ {
+ $maxlength -= 2;
+ }
+ $name = encode("UTF-8", substr(decode("UTF-8", $name), 0, $maxlength));
+ }
+ if ( weechat::config_boolean($options{"core_to_front"}) eq 1)
+ {
+ if ( (weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "channel" ) and ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "private") )
+ {
+ my $type = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type");
+ if ( $type eq "" and $name ne "weechat")
+ {
+ $name = " " . $name
+ }else
+ {
+ $name = " " . $name;
+ }
+ }
+ }
+ $key = sprintf("%s%08d", lc($name), $buffer->{"number"});
+ }
+ else
+ {
+ $key = sprintf("%08d", $number);
+ }
+ $sorted_buffers{$key} = $buffer;
+ $number++;
+ }
+ }
+
+ # build string with buffers
+ $old_number = -1;
+ foreach my $key (sort keys %sorted_buffers)
+ {
+ my $buffer = $sorted_buffers{$key};
+
+ if ( weechat::config_string($options{"hide_merged_buffers"}) eq "server" )
+ {
+ # buffer type "server" or merged with core?
+ if ( ($buffer->{"type"} eq "server" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) )
+ {
+ next;
+ }
+ }
+ if ( weechat::config_string($options{"hide_merged_buffers"}) eq "channel" )
+ {
+ # buffer type "channel" or merged with core?
+ if ( ($buffer->{"type"} eq "channel" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) )
+ {
+ next;
+ }
+ }
+ if ( weechat::config_string($options{"hide_merged_buffers"}) eq "private" )
+ {
+ # buffer type "private" or merged with core?
+ if ( ($buffer->{"type"} eq "private" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) )
+ {
+ next;
+ }
+ }
+ if ( weechat::config_string($options{"hide_merged_buffers"}) eq "keepserver" )
+ {
+ if ( ($buffer->{"type"} ne "server" or $buffer->{"plugin_name"} eq "core") && (! $buffer->{"active"}) )
+ {
+ next;
+ }
+ }
+ if ( weechat::config_string($options{"hide_merged_buffers"}) eq "all" )
+ {
+ if ( ! $buffer->{"active"} )
+ {
+ next;
+ }
+ }
+
+ push(@buffers_focus, $buffer); # buffer > buffers_focus, for mouse support
+ my $color = "";
+ my $bg = "";
+
+ $color = weechat::config_color( $options{"color_default_fg"} );
+ $bg = weechat::config_color( $options{"color_default_bg"} );
+
+ if ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "private" )
+ {
+ if ( (weechat::config_color($options{"queries_default_bg"})) ne "default" || (weechat::config_color($options{"queries_default_fg"})) ne "default" )
+ {
+ $bg = weechat::config_color( $options{"queries_default_bg"} );
+ $color = weechat::config_color( $options{"queries_default_fg"} );
+ }
+ }
+ # check for core and buffer with free content
+ if ( (weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "channel" ) and ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") ne "private") )
+ {
+ $color = weechat::config_color( $options{"color_none_channel_fg"} );
+ $bg = weechat::config_color( $options{"color_none_channel_bg"} );
+ }
+ # default whitelist buffer?
+ if (grep {$_ eq $buffer->{"name"}} @whitelist_buffers)
+ {
+ $color = weechat::config_color( $options{"color_whitelist_default_fg"} );
+ $bg = weechat::config_color( $options{"color_whitelist_default_bg"} );
+ }
+
+ $color = "default" if ($color eq "");
+
+ # color for channel and query buffer
+ if (exists $hotlist{$buffer->{"pointer"}})
+ {
+ delete $buffers_timer{$buffer->{"pointer"}};
+ # check if buffer is in whitelist buffer
+ if (grep {$_ eq $buffer->{"name"}} @whitelist_buffers)
+ {
+ $bg = weechat::config_color( $options{"color_whitelist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} );
+ $color = weechat::config_color( $options{"color_whitelist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} );
+ }
+ elsif ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "private" )
+ {
+ # queries_default_fg/bg and buffers.color.queries_message_fg/bg
+ if ( (weechat::config_color($options{"queries_highlight_fg"})) ne "default" ||
+ (weechat::config_color($options{"queries_highlight_bg"})) ne "default" ||
+ (weechat::config_color($options{"queries_message_fg"})) ne "default" ||
+ (weechat::config_color($options{"queries_message_bg"})) ne "default" )
+ {
+ if ( ($hotlist{$buffer->{"pointer"}}) == 2 )
+ {
+ $bg = weechat::config_color( $options{"queries_message_bg"} );
+ $color = weechat::config_color( $options{"queries_message_fg"} );
+ }
+
+ elsif ( ($hotlist{$buffer->{"pointer"}}) == 3 )
+ {
+ $bg = weechat::config_color( $options{"queries_highlight_bg"} );
+ $color = weechat::config_color( $options{"queries_highlight_fg"} );
+ }
+ }else
+ {
+ $bg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} );
+ $color = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} );
+ }
+ }else
+ {
+ $bg = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_bg"} );
+ $color = weechat::config_color( $options{"color_hotlist_".$hotlist_level{$hotlist{$buffer->{"pointer"}}}."_fg"} );
+ }
+ }
+
+ if ($buffer->{"current_buffer"})
+ {
+ $color = weechat::config_color( $options{"color_current_fg"} );
+ $bg = weechat::config_color( $options{"color_current_bg"} );
+ }
+ my $color_bg = "";
+ $color_bg = weechat::color(",".$bg) if ($bg ne "");
+
+ # create channel number for output
+ if ( weechat::config_string( $options{"show_prefix_bufname"} ) ne "" )
+ {
+ $str .= $color_bg .
+ weechat::color( weechat::config_color( $options{"color_prefix_bufname"} ) ).
+ weechat::config_string( $options{"show_prefix_bufname"} ).
+ weechat::color("default");
+ }
+
+ if ( weechat::config_boolean( $options{"show_number"} ) eq 1 ) # on
+ {
+ if (( weechat::config_boolean( $options{"indenting_number"} ) eq 1)
+ && (($position eq "left") || ($position eq "right")))
+ {
+ $str .= weechat::color("default").$color_bg
+ .(" " x ($max_number_digits - length(int($buffer->{"number"}))));
+ }
+ if ($old_number ne $buffer->{"number"})
+ {
+ $str .= weechat::color( weechat::config_color( $options{"color_number"} ) )
+ .$color_bg
+ .$buffer->{"number"}
+ .weechat::color("default")
+ .$color_bg
+ .weechat::color( weechat::config_color( $options{"color_number_char"} ) )
+ .weechat::config_string( $options{"show_number_char"} )
+ .$color_bg;
+ }
+ else
+ {
+ # Indentation aligns channels in a visually appealing way
+ # when viewing list top-to-bottom...
+ my $indent = (" " x length($buffer->{"number"}))." ";
+ # ...except when list is top/bottom and channels left-to-right.
+ my $option_pos = weechat::config_string( weechat::config_get( "weechat.bar.buffers.position" ) );
+ if (($option_pos eq 'top') || ($option_pos eq 'bottom')) {
+ my $option_filling = weechat::config_string( weechat::config_get( "weechat.bar.buffers.filling_top_bottom" ) );
+ if ($option_filling =~ /horizontal/) {
+ $indent = '';
+ }
+ }
+ $str .= weechat::color("default")
+ .$color_bg
+ .$indent;
+ }
+ }
+
+ if (( weechat::config_integer( $options{"indenting"} ) ne 0 ) # indenting NOT off
+ && (($position eq "left") || ($position eq "right")))
+ {
+ my $type = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type");
+ if (($type eq "channel") || ($type eq "private"))
+ {
+ if ( weechat::config_integer( $options{"indenting"} ) eq 1 )
+ {
+ $str .= " ";
+ }
+ elsif ( (weechat::config_integer($options{"indenting"}) eq 2) and (weechat::config_integer($options{"indenting_number"}) eq 0) ) #under_name
+ {
+ if ( weechat::config_boolean( $options{"show_number"} ) eq 0 )
+ {
+ $str .= " ";
+ }else
+ {
+ $str .= ( (" " x ( $max_number_digits - length($buffer->{"number"}) ))." " );
+ }
+ }
+ }
+ }
+
+ $str .= weechat::config_string( $options{"show_prefix_query"}) if (weechat::config_string( $options{"show_prefix_query"} ) ne "" and $buffer->{"type"} eq "private");
+
+ if (weechat::config_boolean( $options{"show_prefix"} ) eq 1)
+ {
+ my $nickname = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_nick");
+ if ($nickname ne "")
+ {
+ # with version >= 0.3.2, this infolist will return only nick
+ # with older versions, whole nicklist is returned for buffer, and this can be very slow
+ my $infolist_nick = weechat::infolist_get("nicklist", $buffer->{"pointer"}, "nick_".$nickname);
+ if ($infolist_nick ne "")
+ {
+ while (weechat::infolist_next($infolist_nick))
+ {
+ if ((weechat::infolist_string($infolist_nick, "type") eq "nick")
+ && (weechat::infolist_string($infolist_nick, "name") eq $nickname))
+ {
+ my $prefix = weechat::infolist_string($infolist_nick, "prefix");
+ if (($prefix ne " ") or (weechat::config_boolean( $options{"show_prefix_empty"} ) eq 1))
+ {
+ # with version >= 0.3.5, it is now a color name (for older versions: option name with color)
+ if (int($weechat_version) >= 0x00030500)
+ {
+ $str .= weechat::color(weechat::infolist_string($infolist_nick, "prefix_color"));
+ }
+ else
+ {
+ $str .= weechat::color(weechat::config_color(
+ weechat::config_get(
+ weechat::infolist_string($infolist_nick, "prefix_color"))));
+ }
+ $str .= $prefix;
+ }
+ last;
+ }
+ }
+ weechat::infolist_free($infolist_nick);
+ }
+ }
+ }
+ if ($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buffer->{"nicks_count"} == 0)
+ {
+ $str .= "(";
+ }
+
+ $str .= weechat::color($color) . weechat::color(",".$bg);
+
+ my $name = weechat::buffer_get_string($buffer->{"pointer"}, "localvar_custom_name");
+ if (not defined $name or $name eq "")
+ {
+ if (weechat::config_boolean( $options{"short_names"} ) eq 1) {
+ $name = $buffer->{"short_name"};
+ } else {
+ $name = $buffer->{"name"};
+ }
+ }
+
+ if (weechat::config_integer($options{"name_size_max"}) >= 1) # check max_size of buffer name
+ {
+ $name = decode("UTF-8", $name);
+
+ $maxlength = weechat::config_integer($options{"name_size_max"});
+ if($buffer->{"type"} eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buffer->{"nicks_count"} == 0)
+ {
+ $maxlength -= 2;
+ }
+
+ $str .= encode("UTF-8", substr($name, 0, $maxlength));
+ $str .= weechat::color(weechat::config_color( $options{"color_number_char"})).weechat::config_string($options{"name_crop_suffix"}) if (length($name) > weechat::config_integer($options{"name_size_max"}));
+ $str .= add_inactive_parentless($buffer->{"type"}, $buffer->{"nicks_count"});
+ $str .= add_hotlist_count($buffer->{"pointer"}, %hotlist);
+ }
+ else
+ {
+ $str .= $name;
+ $str .= add_inactive_parentless($buffer->{"type"}, $buffer->{"nicks_count"});
+ $str .= add_hotlist_count($buffer->{"pointer"}, %hotlist);
+ }
+
+ if ( weechat::buffer_get_string($buffer->{"pointer"}, "localvar_type") eq "server" and weechat::config_boolean($options{"show_lag"}) eq 1)
+ {
+ my $color_lag = weechat::config_color(weechat::config_get("irc.color.item_lag_finished"));
+ my $min_lag = weechat::config_integer(weechat::config_get("irc.network.lag_min_show"));
+ my $infolist_server = weechat::infolist_get("irc_server", "", $buffer->{"short_name"});
+ weechat::infolist_next($infolist_server);
+ my $lag = (weechat::infolist_integer($infolist_server, "lag"));
+ weechat::infolist_free($infolist_server);
+ if ( int($lag) > int($min_lag) )
+ {
+ $lag = $lag / 1000;
+ $str .= weechat::color("default") . " (" . weechat::color($color_lag) . $lag . weechat::color("default") . ")";
+ }
+ }
+ if (weechat::config_boolean($options{"detach_displayed_buffers"}) eq 0
+ and weechat::config_boolean($options{"detach_display_window_number"}) eq 1)
+ {
+ if ($buffer->{"window"})
+ {
+ $str .= weechat::color("default") . " (" . weechat::color(weechat::config_color( $options{"color_number"})) . $buffer->{"window"} . weechat::color("default") . ")";
+ }
+ }
+ $str .= weechat::color("default");
+
+ if ( weechat::config_string( $options{"show_suffix_bufname"} ) ne "" )
+ {
+ $str .= weechat::color( weechat::config_color( $options{"color_suffix_bufname"} ) ).
+ weechat::config_string( $options{"show_suffix_bufname"} ).
+ weechat::color("default");
+ }
+
+ $str .= "\n";
+ $old_number = $buffer->{"number"};
+ }
+
+ # remove spaces and/or linefeed at the end
+ $str =~ s/\s+$//;
+ chomp($str);
+ return $str;
+}
+
+sub add_inactive_parentless
+{
+my ($buf_type, $buf_nicks_count) = @_;
+my $str = "";
+ if ($buf_type eq "channel" and weechat::config_boolean( $options{"mark_inactive"} ) eq 1 and $buf_nicks_count == 0)
+ {
+ $str .= weechat::color(weechat::config_color( $options{"color_number_char"}));
+ $str .= ")";
+ }
+return $str;
+}
+
+sub add_hotlist_count
+{
+my ($bufpointer, %hotlist) = @_;
+
+return "" if ( weechat::config_boolean( $options{"hotlist_counter"} ) eq 0 or ($weechat_version < 0x00030500)); # off
+my $col_number_char = weechat::color(weechat::config_color( $options{"color_number_char"}) );
+my $str = " ".$col_number_char."(";
+
+# 0 = low level
+if (defined $hotlist{$bufpointer."_count_00"})
+{
+ my $bg = weechat::config_color( $options{"color_hotlist_low_bg"} );
+ my $color = weechat::config_color( $options{"color_hotlist_low_fg"} );
+ $str .= weechat::color($bg).
+ weechat::color($color).
+ $hotlist{$bufpointer."_count_00"} if ($hotlist{$bufpointer."_count_00"} ne "0");
+}
+
+# 1 = message
+if (defined $hotlist{$bufpointer."_count_01"})
+{
+ my $bg = weechat::config_color( $options{"color_hotlist_message_bg"} );
+ my $color = weechat::config_color( $options{"color_hotlist_message_fg"} );
+ if ($str =~ /[0-9]$/)
+ {
+ $str .= ",".
+ weechat::color($bg).
+ weechat::color($color).
+ $hotlist{$bufpointer."_count_01"} if ($hotlist{$bufpointer."_count_01"} ne "0");
+ }else
+ {
+ $str .= weechat::color($bg).
+ weechat::color($color).
+ $hotlist{$bufpointer."_count_01"} if ($hotlist{$bufpointer."_count_01"} ne "0");
+ }
+}
+# 2 = private
+if (defined $hotlist{$bufpointer."_count_02"})
+{
+ my $bg = weechat::config_color( $options{"color_hotlist_private_bg"} );
+ my $color = weechat::config_color( $options{"color_hotlist_private_fg"} );
+ if ($str =~ /[0-9]$/)
+ {
+ $str .= ",".
+ weechat::color($bg).
+ weechat::color($color).
+ $hotlist{$bufpointer."_count_02"} if ($hotlist{$bufpointer."_count_02"} ne "0");
+ }else
+ {
+ $str .= weechat::color($bg).
+ weechat::color($color).
+ $hotlist{$bufpointer."_count_02"} if ($hotlist{$bufpointer."_count_02"} ne "0");
+ }
+}
+# 3 = highlight
+if (defined $hotlist{$bufpointer."_count_03"})
+{
+ my $bg = weechat::config_color( $options{"color_hotlist_highlight_bg"} );
+ my $color = weechat::config_color( $options{"color_hotlist_highlight_fg"} );
+ if ($str =~ /[0-9]$/)
+ {
+ $str .= ",".
+ weechat::color($bg).
+ weechat::color($color).
+ $hotlist{$bufpointer."_count_03"} if ($hotlist{$bufpointer."_count_03"} ne "0");
+ }else
+ {
+ $str .= weechat::color($bg).
+ weechat::color($color).
+ $hotlist{$bufpointer."_count_03"} if ($hotlist{$bufpointer."_count_03"} ne "0");
+ }
+}
+$str .= $col_number_char. ")";
+
+$str = "" if (weechat::string_remove_color($str, "") eq " ()"); # remove color and check for buffer with no messages
+return $str;
+}
+
+sub buffers_signal_buffer
+{
+ my ($data, $signal, $signal_data) = @_;
+
+ # check for buffer_switch and set or remove detach time
+ if ($weechat_version >= 0x00030800)
+ {
+ if ($signal eq "buffer_switch")
+ {
+ my $pointer = weechat::hdata_get_list (weechat::hdata_get("buffer"), "gui_buffer_last_displayed"); # get switched buffer
+ my $current_time = time();
+ if ( weechat::buffer_get_string($pointer, "localvar_type") eq "channel")
+ {
+ $buffers_timer{$pointer} = $current_time;
+ }
+ else
+ {
+ delete $buffers_timer{$pointer};
+ }
+ }
+ if ($signal eq "buffer_opened")
+ {
+ my $current_time = time();
+ $buffers_timer{$signal_data} = $current_time;
+ }
+ if ($signal eq "buffer_closing")
+ {
+ delete $buffers_timer{$signal_data};
+ }
+ }
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub buffers_signal_hotlist
+{
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+
+sub buffers_signal_config_whitelist
+{
+ @whitelist_buffers = ();
+ @whitelist_buffers = split( /,/, weechat::config_string( $options{"look_whitelist_buffers"} ) );
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub buffers_signal_config_immune_detach_buffers
+{
+ @immune_detach_buffers = ();
+ @immune_detach_buffers = split( /,/, weechat::config_string( $options{"immune_detach_buffers"} ) );
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub buffers_signal_config_detach_buffer_immediately
+{
+ @detach_buffer_immediately = ();
+ @detach_buffer_immediately = split( /,/, weechat::config_string( $options{"detach_buffer_immediately"} ) );
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+sub buffers_signal_config
+{
+ weechat::bar_item_update($SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK;
+}
+
+# called when mouse click occured in buffers item: this callback returns buffer
+# hash according to line of item where click occured
+sub buffers_focus_buffers
+{
+ my %info = %{$_[1]};
+ my $item_line = int($info{"_bar_item_line"});
+ undef my $hash;
+ if (($info{"_bar_item_name"} eq $SCRIPT_NAME) && ($item_line >= 0) && ($item_line <= $#buffers_focus))
+ {
+ $hash = $buffers_focus[$item_line];
+ }
+ else
+ {
+ $hash = {};
+ my $hash_focus = $buffers_focus[0];
+ foreach my $key (keys %$hash_focus)
+ {
+ $hash->{$key} = "?";
+ }
+ }
+ return $hash;
+}
+
+# called when a mouse action is done on buffers item, to execute action
+# possible actions: jump to a buffer or move buffer in list (drag & drop of buffer)
+sub buffers_hsignal_mouse
+{
+ my ($data, $signal, %hash) = ($_[0], $_[1], %{$_[2]});
+ my $current_buffer = weechat::buffer_get_integer(weechat::current_buffer(), "number"); # get current buffer number
+
+ if ( $hash{"_key"} eq "button1" )
+ {
+ # left mouse button
+ if ($hash{"number"} eq $hash{"number2"})
+ {
+ if ( weechat::config_boolean($options{"jump_prev_next_visited_buffer"}) )
+ {
+ if ( $current_buffer eq $hash{"number"} )
+ {
+ weechat::command("", "/input jump_previously_visited_buffer");
+ }
+ else
+ {
+ weechat::command("", "/buffer ".$hash{"full_name"});
+ }
+ }
+ else
+ {
+ weechat::command("", "/buffer ".$hash{"full_name"});
+ }
+ }
+ else
+ {
+ move_buffer(%hash) if (weechat::config_boolean($options{"mouse_move_buffer"}));
+ }
+ }
+ elsif ( ($hash{"_key"} eq "button2") && (weechat::config_boolean($options{"jump_prev_next_visited_buffer"})) )
+ {
+ # right mouse button
+ if ( $current_buffer eq $hash{"number2"} )
+ {
+ weechat::command("", "/input jump_next_visited_buffer");
+ }
+ }
+ elsif ( $hash{"_key"} =~ /wheelup$/ )
+ {
+ # wheel up
+ if (weechat::config_boolean($options{"mouse_wheel"}))
+ {
+ weechat::command("", "/buffer -1");
+ }
+ }
+ elsif ( $hash{"_key"} =~ /wheeldown$/ )
+ {
+ # wheel down
+ if (weechat::config_boolean($options{"mouse_wheel"}))
+ {
+ weechat::command("", "/buffer +1");
+ }
+ }
+ else
+ {
+ my $infolist = weechat::infolist_get("hook", "", "command,menu");
+ my $has_menu_command = weechat::infolist_next($infolist);
+ weechat::infolist_free($infolist);
+
+ if ( $has_menu_command && $hash{"_key"} =~ /button2/ )
+ {
+ if ($hash{"number"} eq $hash{"number2"})
+ {
+ weechat::command($hash{"pointer"}, "/menu buffer1 $hash{short_name} $hash{number}");
+ }
+ else
+ {
+ weechat::command($hash{"pointer"}, "/menu buffer2 $hash{short_name}/$hash{short_name2} $hash{number} $hash{number2}")
+ }
+ }
+ else
+ {
+ move_buffer(%hash) if (weechat::config_boolean($options{"mouse_move_buffer"}));
+ }
+ }
+}
+
+sub move_buffer
+{
+ my %hash = @_;
+ my $number2 = $hash{"number2"};
+ if ($number2 eq "?")
+ {
+ # if number 2 is not known (end of gesture outside buffers list), then set it
+ # according to mouse gesture
+ $number2 = "1";
+ if (($hash{"_key"} =~ /gesture-right/) || ($hash{"_key"} =~ /gesture-down/))
+ {
+ $number2 = "999999";
+ if ($weechat_version >= 0x00030600)
+ {
+ my $hdata_buffer = weechat::hdata_get("buffer");
+ my $last_gui_buffer = weechat::hdata_get_list($hdata_buffer, "last_gui_buffer");
+ if ($last_gui_buffer)
+ {
+ $number2 = weechat::hdata_integer($hdata_buffer, $last_gui_buffer, "number") + 1;
+ }
+ }
+ }
+ }
+ my $ptrbuf = weechat::current_buffer();
+ weechat::command($hash{"pointer"}, "/buffer move ".$number2);
+}
+
+sub check_immune_detached_buffers
+{
+ my ($buffername) = @_;
+ foreach ( @immune_detach_buffers ){
+ my $immune_buffer = weechat::string_mask_to_regex($_);
+ if ($buffername =~ /^$immune_buffer$/i)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub check_detach_buffer_immediately
+{
+ my ($buffername) = @_;
+ foreach ( @detach_buffer_immediately ){
+ my $detach_buffer = weechat::string_mask_to_regex($_);
+ if ($buffername =~ /^$detach_buffer$/i)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub shutdown_cb
+{
+ weechat::command("", "/bar hide " . $SCRIPT_NAME) if ( weechat::config_boolean($options{"toggle_bar"}) eq 1 );
+ return weechat::WEECHAT_RC_OK
+}
+
+sub check_bar_item
+{
+ my $item = 0;
+ my $infolist = weechat::infolist_get("bar", "", "");
+ while (weechat::infolist_next($infolist))
+ {
+ my $bar_items = weechat::infolist_string($infolist, "items");
+ if (index($bar_items, $SCRIPT_NAME) != -1)
+ {
+ my $name = weechat::infolist_string($infolist, "name");
+ if ($name ne $SCRIPT_NAME)
+ {
+ $item = 1;
+ last;
+ }
+ }
+ }
+ weechat::infolist_free($infolist);
+ return $item;
+}
diff --git a/weechat/perl/coords.pl b/weechat/perl/coords.pl
new file mode 100644
index 0000000..43104af
--- /dev/null
+++ b/weechat/perl/coords.pl
@@ -0,0 +1,3223 @@
+use strict; use warnings;
+$INC{'Encode/ConfigLocal.pm'}=1;
+require Encode;
+
+# coords.pl is written by Nei <anti.teamidiot.de>
+# and licensed under the under GNU General Public License v3
+# or any later version
+
+# to read the following docs, you can use "perldoc coords.pl"
+
+=head1 NAME
+
+coords - weechat script to map screen coordinates (weechat edition)
+
+=head1 SYNOPSIS
+
+first, copy the file to your
+F<.weechat/perl> directory. Then you can type
+
+ /script load coords.pl
+
+in weechat to load the script. Use
+
+ /coords
+
+to open the coords screen, or conveniently
+
+ /key bind meta-/ /coords /
+
+to open it with the Alt+/ keybinding.
+
+=head1 DESCRIPTION
+
+coords will hilight links, allow text selection and tries to send the
+selection to xterm
+
+=head1 SETUP
+
+if you would like urls to be copied into the selection clipboard, add
+
+ xterm*disallowedWindowOps:20,21,SetXprop
+
+to your F<.Xresources> file for B<xterm>.
+
+For B<rxvt-unicode>, get the F<osc-xterm-clipboard> script from
+L<http://anti.teamidiot.de/static/nei/*/Code/urxvt/>, install it to
+your F<perl-lib> directory and add it to your F<.Xresources> like such:
+
+ URxvt.perl-ext-common: default,osc-xterm-clipboard
+
+=head1 USAGE
+
+to open the url overlay on a window, type C</coords> or use the
+keybinding you created as explained in the L</SYNOPSIS>.
+
+=head2 Selection Mode
+
+by default, the copy window will be in selection mode. you can move
+the text cursor with the arrow keys and open a selection with the
+Space key. The selection content will be transfered into clipboard.
+
+=head2 URL Mode
+
+to switch between selection mode and URL mode, use the C</> key or
+type the command C</coords /> to directly start in URL mode.
+
+inside the overlay, you can use Arrow-Up and Arrow-Down keys to select
+URLs. This script will try to copy them into your selection clipboard
+(see L</SETUP>) so you should be able to open the selected link by
+clicking the middle mouse button in your browser.
+
+once you click or hit enter, an url open signal will be sent to
+WeeChat. Use an appropriate script such as F<urlopener.pl> from
+L<http://anti.teamidiot.de/static/nei/*/Code/WeeChat/> if you would
+like to associate this with some application (web browser etc.)
+
+to leave the overlay, hit the C<q> key.
+
+for mouse support, this script will listen to mouse input
+signals. Another script is needed to supply these signals, such as
+F<mouse.pl> which can be found in the same place as F<urlopener.pl>
+and this script.
+
+=head1 CAVEATS
+
+=over
+
+=item *
+
+WeeChat does not allow for visual feedback during mouse operation
+unless you patch it and use the F<mouse.pl> or F<mouse_var.pl> script
+instead.
+
+=item *
+
+double-click word select does not work in an unpatched WeeChat for the
+same reason -- unless you set a very high close_on_release timeout (with
+the added inconvenience).
+
+=item *
+
+unfortunately, WeeChat scrolls back a buffer to the end when you
+switch to any other buffer, including the copy window
+overlay. F<coords.pl> will work together with the F<keepscroll.pl>
+script to try and remedy this a little bit, but the perfect scrolling
+position cannot be restored due to internal limitations. (no longer
+the case in recent versions of WeeChat.)
+
+=item *
+
+whether other terminal emulators support selection storage, depends on
+how well they emulate B<xterm>. Set C<xterm_compatible> to your
+I<$TERM> if you think it does.
+
+=item *
+
+B<xterm> will not allow selection storage unless you enable it in the
+Xresources as described in L</SETUP>
+
+=item *
+
+B<rxvt-unicode> will need a script to handle the selection operating
+system control. querying the selection is only supported if the
+selection is rxvt-unicodeE<apos>s (not needed for this script)
+
+=item *
+
+GNU B<screen> will not allow to pass through the operating system
+control needed for selection storage to the underlying term, try
+B<tmux> instead (L<http://tmux.sourceforge.net/>)
+
+=item *
+
+it would be possible to provide a remote clipboard, but that will
+require a clipboard server. Also see B<xsel>, B<xclip>, command mode
+
+=back
+
+=head1 TODO
+
+=over
+
+=item *
+
+set the text input cursor on click in the input bar
+
+=back
+
+=head1 BUGS
+
+=over
+
+=item *
+
+my local B<xterm> exhibits a bug where the selection is only copied if
+you I<click> into the xterm window first. still trying to figure this
+one out...
+
+=item *
+
+missing handling of scroll_beyond_end feature in weechat
+
+=item *
+
+broken handling of Day Changed messages in newer weechat
+
+=item *
+
+possibly more...
+
+=back
+
+=head1 SETTINGS
+
+the settings are usually found in the
+
+ plugins.var.perl.coords
+
+namespace, that is, type
+
+ /set plugins.var.perl.coords.*
+
+to see them and
+
+ /set plugins.var.perl.coords.SETTINGNAME VALUE
+
+to change a setting C<SETTINGNAME> to a new value C<VALUE>. Finally,
+
+ /unset plugins.var.perl.coords.SETTINGNAME
+
+will reset a setting to its default value.
+
+the following settings are available:
+
+=head2 url_regex
+
+a regular expression to identify URLs in the text. See L<perlre> for
+more information about Perl regular expressions.
+
+=head2 url_braces
+
+parenthesis-like characters which nest and should be excluded when
+found around an URL. make sure the variable setting nests properly
+when modifying this.
+
+=head2 url_non_endings
+
+this is matched against the end of a link and removed
+
+=head2 hyper_nicks
+
+make nicks to hyperlinks for menu/pm
+
+=head2 hyper_channels
+
+make channels to hyperlinks for join
+
+=head2 hyper_show
+
+set to types of hyperlinks that are shown by default
+
+=head2 use_nick_menu
+
+use nick menu when opening nick hyperlink (see I<hyper_nicks>,
+requires menu.pl script). otherwise open private message. this setting
+only applies to text mode selection, for mouse see
+I<mouse.nick_2nd_click>
+
+=head2 color.url_highlight
+
+the weechat color and/or attribute to be used for highlighting URLs in
+the copy window. seperate multiple attributes with C<.>
+
+=head2 color.url_highlight_active
+
+the same as I<color.url_highlight> except for the currently (using
+arrow keys) selected link.
+
+=head2 color.selection_cursor
+
+the weechat color and/or attribute to be used for the text cursor.
+
+=head2 color.selection
+
+the color of the currently selected text in selection mode
+
+=head2 copybuf_short_name
+
+short_name to use for coords buffer. it is set to the copy sign by
+default to not disturb buffers bar width, set to the empty string to
+have window position and size shown
+
+=head2 mouse.copy_on_click
+
+set to on if it should be possible to directly click on URLs and
+select text, set to off if mouse should only work in open coords
+buffer
+
+=head2 mouse.close_on_release
+
+set to on or a delay (in ms) to autoclose coords buffer opened by
+I<copy_on_click> on button release, set to off if the coords buffer
+should stay open after click
+
+=head2 mouse.click_select_pane
+
+set to on to use the mouse to select windows
+
+=head2 mouse.click_through_pane
+
+set to on if I<copy_on_click> should work on inactive windows (works
+only if I<click_select_pane> is set too). set to off if window needs
+to be active
+
+=head2 mouse.url_open_2nd_click
+
+if this is set, URLs are only opened when clicked twice (in the same
+incarnation of a coords buffer) instead of on first click. it can be set to
+a delay (in ms) that will be added to the I<close_on_release> delay if
+the script is waiting for a second click on the URL to happen
+
+=head2 mouse.handle_scroll
+
+set to on if coords should handle scrolling inside windows. the script
+will try to guess non-chat areas to be nicklist, top to be title and
+bottom to be status and scroll the respective bars if the cursor is in
+that area. set to off if scrolling should be handled by the default
+F<mouse.pl> script or another mouse scrolling script
+
+=head2 mouse.scroll_inactive_pane
+
+set to on if inactive windows should be scrolled instead of active
+window if the mouse cursor is over it (requires I<handle_scroll> to be
+enabled)
+
+=head2 clipboard_command
+
+if you set this, an external program may be executed to store the
+selection or URL. begin with C<|> to pipe into program or use
+parameters C<%s> for text, C<%q> for quoted text or C<%x> for quoted
+escape sequence.
+
+=head2 copywin_custom_keys
+
+You can define custom key bindings to use inside the copywin here. syntax is:
+command-letter:weechat-keycode. available commands: -+>< (up/down/left/right)
+fbae (forward word/backward word/beginning/end) !@ (open/start selection)
+/UNCunc (toggle highlights/urls/nicks/channels) q (close window)
+
+=head1 FUNCTION DESCRIPTION
+
+for full pod documentation, filter this script with
+
+ perl -pE'
+ (s/^## (.*?) -- (.*)/=head2 $1\n\n$2\n\n=over\n/ and $o=1) or
+ s/^## (.*?) - (.*)/=item I<$1>\n\n$2\n/ or
+ (s/^## (.*)/=back\n\n$1\n\n=cut\n/ and $o=0,1) or
+ ($o and $o=0,1 and s/^sub /=back\n\n=cut\n\nsub /)'
+
+=cut
+
+use MIME::Base64;
+
+use constant SCRIPT_NAME => 'coords';
+weechat::register(SCRIPT_NAME, 'Nei <anti.teamidiot.de>', '0.7.3.1', 'GPL3', 'copy text and urls', 'stop_coords', '') || return;
+sub SCRIPT_FILE() {
+ my $infolistptr = weechat::infolist_get('perl_script', '', SCRIPT_NAME);
+ my $filename = weechat::infolist_string($infolistptr, 'filename') if weechat::infolist_next($infolistptr);
+ weechat::infolist_free($infolistptr);
+ return $filename unless @_;
+}
+
+{
+package Nlib;
+# this is a weechat perl library
+use strict; use warnings; no warnings 'redefine';
+
+## i2h -- copy weechat infolist content into perl hash
+## $infolist - name of the infolist in weechat
+## $ptr - pointer argument (infolist dependend)
+## @args - arguments to the infolist (list dependend)
+## $fields - string of ref type "fields" if only certain keys are needed (optional)
+## returns perl list with perl hashes for each infolist entry
+sub i2h {
+ my %i2htm = (i => 'integer', s => 'string', p => 'pointer', b => 'buffer', t => 'time');
+ local *weechat::infolist_buffer = sub { '(not implemented)' };
+ my ($infolist, $ptr, @args) = @_;
+ $ptr ||= "";
+ my $fields = ref $args[-1] eq 'fields' ? ${ pop @args } : undef;
+ my $infptr = weechat::infolist_get($infolist, $ptr, do { local $" = ','; "@args" });
+ my @infolist;
+ while (weechat::infolist_next($infptr)) {
+ my @fields = map {
+ my ($t, $v) = split ':', $_, 2;
+ bless \$v, $i2htm{$t};
+ }
+ split ',',
+ ($fields || weechat::infolist_fields($infptr));
+ push @infolist, +{ do {
+ my (%list, %local, @local);
+ map {
+ my $fn = 'weechat::infolist_'.ref $_;
+ my $r = do { no strict 'refs'; &$fn($infptr, $$_) };
+ if ($$_ =~ /^localvar_name_(\d+)$/) {
+ $local[$1] = $r;
+ ()
+ }
+ elsif ($$_ =~ /^(localvar)_value_(\d+)$/) {
+ $local{$local[$2]} = $r;
+ $1 => \%local
+ }
+ elsif ($$_ =~ /(.*?)((?:_\d+)+)$/) {
+ my ($key, $idx) = ($1, $2);
+ my @idx = split '_', $idx; shift @idx;
+ my $target = \$list{$key};
+ for my $x (@idx) {
+ my $o = 1;
+ if ($key eq 'key' or $key eq 'key_command') {
+ $o = 0;
+ }
+ if ($x-$o < 0) {
+ local $" = '|';
+ weechat::print('',"list error: $target/$$_/$key/$x/$idx/@idx(@_)");
+ $o = 0;
+ }
+ $target = \$$target->[$x-$o]
+ }
+ $$target = $r;
+
+ my $code = qq{
+ local \$[=1;
+ \$list{"\Q$key\E"}$idx = \$r
+ };
+ $key => $list{$key}
+ }
+ else {
+ $$_ => $r
+ }
+ } @fields
+ } };
+ }
+ weechat::infolist_free($infptr);
+ !wantarray && @infolist ? \@infolist : @infolist
+}
+
+## hdh -- hdata helper
+sub hdh {
+ if (@_ > 1 && $_[0] !~ /^0x/ && $_[0] !~ /^\d+$/) {
+ my $arg = shift;
+ unshift @_, weechat::hdata_get_list(weechat::hdata_get($_[0]), $arg);
+ }
+ while (@_ > 2) {
+ my ($arg, $name, $var) = splice @_, 0, 3;
+ my $hdata = weechat::hdata_get($name);
+
+ $var =~ s/!(.*)/weechat::hdata_get_string($hdata, $1)/e;
+ (my $plain_var = $var) =~ s/^\d+\|//;
+ my $type = weechat::hdata_get_var_type_string($hdata, $plain_var);
+ if ($type eq 'pointer') {
+ my $name = weechat::hdata_get_var_hdata($hdata, $var);
+ unshift @_, $name if $name;
+ }
+ if ($type eq 'shared_string') {
+ $type =~ s/shared_//;
+ }
+
+ my $fn = "weechat::hdata_$type";
+ unshift @_, do { no strict 'refs';
+ &$fn($hdata, $arg, $var) };
+ }
+ wantarray ? @_ : $_[0]
+}
+
+## l2l -- copy weechat list into perl list
+## $ptr - weechat list pointer
+## $clear - if true, clear weechat list
+## returns perl list
+sub l2l {
+ my ($ptr, $clear) = @_;
+ my $itemptr = weechat::list_get($ptr, 0);
+ my @list;
+ while ($itemptr) {
+ push @list, weechat::list_string($itemptr);
+ $itemptr = weechat::list_next($itemptr);
+ }
+ weechat::list_remove_all($ptr) if $clear;
+ @list
+}
+
+## find_bar_window -- find the bar window where the coordinates belong to
+## $row - row
+## $col - column
+## returns bar window infolist and bar infolist in a array ref if found
+sub find_bar_window {
+ my ($row, $col) = @_;
+
+ my $barwinptr;
+ my $bar_info;
+ for (i2h('bar_window')) {
+ return [ $_, $bar_info ] if
+ $row > $_->{'y'} && $row <= $_->{'y'}+$_->{'height'} &&
+ $col > $_->{'x'} && $col <= $_->{'x'}+$_->{'width'} &&
+ (($bar_info)=i2h('bar', $_->{'bar'})) && !$bar_info->{'hidden'};
+ }
+
+}
+
+## in_window -- check if given coordinates are in a window
+## $row - row
+## $col - column
+## $wininfo - infolist of window to check
+## returns true if in window
+sub in_window {
+ my ($row, $col, $wininfo) = @_;
+
+ # in window?
+ $row > $wininfo->{'y'} &&
+ $row <= $wininfo->{'y'}+$wininfo->{'height'} &&
+ $col > $wininfo->{'x'} &&
+ $col <= $wininfo->{'x'}+$wininfo->{'width'}
+}
+
+## in_chat_window -- check if given coordinates are in the chat part of a window
+## $row - row
+## $col - column
+## $wininfo - infolist of window to check
+## returns true if in chat part of window
+sub in_chat_window {
+ my ($row, $col, $wininfo) = @_;
+
+ # in chat window?
+ $row > $wininfo->{'chat_y'} &&
+ $row <= $wininfo->{'chat_y'}+$wininfo->{'chat_height'} &&
+ $col > $wininfo->{'chat_x'} &&
+ $col <= $wininfo->{'chat_x'}+$wininfo->{'chat_width'}
+}
+
+## has_true_value -- some constants for "true"
+## $v - value string
+## returns true if string looks like a true thing
+sub has_true_value {
+ my $v = shift || '';
+ $v =~ /^(?:on|yes|y|true|t|1)$/i
+}
+
+## has_false_value -- some constants for "false"
+## $v - value string
+## returns true if string looks like a B<false> thing
+sub has_false_value {
+ my $v = shift || '';
+ $v =~ /^(?:off|no|n|false|f|0)?$/i
+}
+
+## bar_filling -- get current filling according to position
+## $bar_infos - info about bar (from find_bar_window)
+## returns filling as an integer number
+sub bar_filling {
+ my ($bar_infos) = @_;
+ ($bar_infos->[-1]{'position'} <= 1 ? $bar_infos->[-1]{'filling_top_bottom'}
+ : $bar_infos->[-1]{'filling_left_right'})
+}
+
+sub fu8on(@) {
+ Encode::_utf8_on($_) for @_; wantarray ? @_ : shift
+}
+
+sub screen_length($) {
+ weechat::strlen_screen($_[0])
+}
+
+## bar_column_max_length -- get max item length for column based filling
+## $bar_infos - info about bar (from find_bar_window)
+## returns max item length
+sub bar_column_max_length {
+ my ($bar_infos) = @_;
+ my @items;
+ for (@{ $bar_infos->[0]{'items_content'} }) {
+ push @items, split "\n", join "\n", @$_;
+ }
+ my $max_length = 0;
+ for (@items) {
+ my $item_length = screen_length fu8on weechat::string_remove_color($_, '');
+ $max_length = $item_length if $max_length < $item_length;
+ }
+ $max_length;
+}
+
+## find_bar_item_pos -- get position of an item in a bar structure
+## $bar_infos - instance and general info about bar (from find_bar_window)
+## $search - search pattern for item name
+## returns (outer position, inner position, true if found)
+sub find_bar_item_pos {
+ my ($bar_infos, $search) = @_;
+ my $item_pos_a = 0;
+ my $item_pos_b;
+ for (@{ $bar_infos->[-1]{'items_array'} }) {
+ $item_pos_b = 0;
+ for (@$_) {
+ return ($item_pos_a, $item_pos_b, 1)
+ if $_ =~ $search;
+ ++$item_pos_b;
+ }
+ ++$item_pos_a;
+ }
+ (undef, undef, undef)
+}
+
+## bar_line_wrap_horiz -- apply linebreak for horizontal bar filling
+## $prefix_col_r - reference to column counter
+## $prefix_y_r - reference to row counter
+## $bar_infos - info about bar (from find_bar_window)
+sub bar_line_wrap_horiz {
+ my ($prefix_col_r, $prefix_y_r, $bar_infos) = @_;
+ while ($$prefix_col_r > $bar_infos->[0]{'width'}) {
+ ++$$prefix_y_r;
+ $$prefix_col_r -= $bar_infos->[0]{'width'};
+ }
+}
+
+## bar_lines_column_vert -- count lines in column layout
+## $bar_infos - info about bar (from find_bar_window)
+## returns lines needed for columns_horizontal layout
+sub bar_lines_column_vert {
+ my ($bar_infos) = @_;
+ my @items;
+ for (@{ $bar_infos->[0]{'items_content'} }) {
+ push @items, split "\n", join "\n", @$_;
+ }
+ my $max_length = bar_column_max_length($bar_infos);
+ my $dummy_col = 1;
+ my $lines = 1;
+ for (@items) {
+ if ($dummy_col+$max_length > 1+$bar_infos->[0]{'width'}) {
+ ++$lines;
+ $dummy_col = 1;
+ }
+ $dummy_col += 1+$max_length;
+ }
+ $lines;
+}
+
+## bar_items_skip_to -- skip several bar items on search for subitem position
+## $bar_infos - info about bar (from find_bar_window)
+## $search - patter of item to skip to
+## $col - pointer column
+## $row - pointer row
+sub bar_items_skip_to {
+ my ($bar_infos, $search, $col, $row) = @_;
+ $col += $bar_infos->[0]{'scroll_x'};
+ $row += $bar_infos->[0]{'scroll_y'};
+ my ($item_pos_a, $item_pos_b, $found) =
+ find_bar_item_pos($bar_infos, $search);
+
+ return 'item position not found' unless $found;
+
+ # extract items to skip
+ my $item_join =
+ (bar_filling($bar_infos) <= 1 ? '' : "\n");
+ my @prefix;
+ for (my $i = 0; $i < $item_pos_a; ++$i) {
+ push @prefix, split "\n", join $item_join, @{ $bar_infos->[0]{'items_content'}[$i] };
+ }
+ push @prefix, split "\n", join $item_join, @{ $bar_infos->[0]{'items_content'}[$item_pos_a] }[0..$item_pos_b-1] if $item_pos_b;
+
+ # cursor
+ my $prefix_col = 1;
+ my $prefix_y = 1;
+ my $item_max_length;
+ my $col_vert_lines;
+
+ # forward cursor
+ if (!bar_filling($bar_infos)) {
+ my $prefix = join ' ', @prefix;
+ $prefix_col += screen_length fu8on weechat::string_remove_color($prefix, '');
+ ++$prefix_col if @prefix && !$item_pos_b;
+ bar_line_wrap_horiz(\($prefix_col, $prefix_y), $bar_infos);
+ }
+ elsif (bar_filling($bar_infos) == 1) {
+ $prefix_y += @prefix;
+ if ($item_pos_b) {
+ --$prefix_y;
+ $prefix_col += screen_length fu8on weechat::string_remove_color($prefix[-1], '');
+ }
+ }
+ elsif (bar_filling($bar_infos) == 2) {
+ $item_max_length = bar_column_max_length($bar_infos);
+ for (@prefix) {
+ $prefix_col += 1+$item_max_length;
+ if ($prefix_col+$item_max_length > 1+$bar_infos->[0]{'width'}) {
+ ++$prefix_y;
+ $prefix_col = 1;
+ }
+ }
+ }
+ elsif (bar_filling($bar_infos) == 3) {
+ $item_max_length = bar_column_max_length($bar_infos);
+ $col_vert_lines = $bar_infos->[-1]{'position'} <= 1 ? bar_lines_column_vert($bar_infos) : $bar_infos->[0]{'height'};
+ my $pfx_idx = 0;
+ for (@prefix) {
+ $prefix_y = 1+($pfx_idx % $col_vert_lines);
+ $prefix_col = 1+(1+$item_max_length)*(int($pfx_idx / $col_vert_lines)+1);
+ return 'in prefix'
+ if ($prefix_y == $row && $prefix_col > $col);
+ ++$pfx_idx;
+ }
+ $prefix_y = 1+(@prefix % $col_vert_lines);
+ $prefix_col = 1+(1+$item_max_length)*int(@prefix / $col_vert_lines);
+ }
+
+ (undef,
+ $item_pos_a, $item_pos_b,
+ $prefix_col, $prefix_y,
+ (scalar @prefix),
+ $item_max_length, $col_vert_lines)
+}
+
+## bar_item_get_subitem_at -- extract subitem from a bar item at given coords
+## $bar_infos - info about bar
+## $search - search pattern for item whose subitems to get
+## $col - pointer column
+## $row - pointer row
+## returns error message, subitem index, subitem text
+sub bar_item_get_subitem_at {
+ my ($bar_infos, $search, $col, $row) = @_;
+
+ my ($error,
+ $item_pos_a, $item_pos_b,
+ $prefix_col, $prefix_y,
+ $prefix_cnt,
+ $item_max_length, $col_vert_lines) =
+ bar_items_skip_to($bar_infos, $search, $col, $row);
+
+ $col += $bar_infos->[0]{'scroll_x'};
+ $row += $bar_infos->[0]{'scroll_y'};
+
+ return $error if $error;
+
+ return 'no viable position'
+ unless (($row == $prefix_y && $col >= $prefix_col) || $row > $prefix_y || bar_filling($bar_infos) >= 3);
+
+ my @subitems = split "\n", $bar_infos->[0]{'items_content'}[$item_pos_a][$item_pos_b];
+ my $idx = 0;
+ for (@subitems) {
+ my ($beg_col, $beg_y) = ($prefix_col, $prefix_y);
+ $prefix_col += screen_length fu8on weechat::string_remove_color($_, '');
+ if (!bar_filling($bar_infos)) {
+ bar_line_wrap_horiz(\($prefix_col, $prefix_y), $bar_infos);
+ }
+
+ return (undef, $idx, $_, [$beg_col, $col, $prefix_col, $beg_y, $row, $prefix_y])
+ if (($prefix_col > $col && $row == $prefix_y) || ($row < $prefix_y && bar_filling($bar_infos) < 3));
+
+ ++$idx;
+
+ if (!bar_filling($bar_infos)) {
+ ++$prefix_col;
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $prefix_col > $col);
+ }
+ elsif (bar_filling($bar_infos) == 1) {
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $col >= $prefix_col);
+ ++$prefix_y;
+ $prefix_col = 1;
+ }
+ elsif (bar_filling($bar_infos) == 2) {
+ $prefix_col += 1+$item_max_length-(($prefix_col-1)%($item_max_length+1));
+
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $prefix_col > $col);
+
+ if ($prefix_col+$item_max_length > 1+$bar_infos->[0]{'width'}) {
+ return ('outside item', $idx-1, $_)
+ if ($prefix_y == $row && $col >= $prefix_col);
+
+ ++$prefix_y;
+ $prefix_col = 1;
+ }
+ }
+ elsif (bar_filling($bar_infos) == 3) {
+ $prefix_col += 1+$item_max_length-(($prefix_col-1)%($item_max_length+1));
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $prefix_col > $col);
+ $prefix_y = 1+(($idx+$prefix_cnt) % $col_vert_lines);
+ $prefix_col = 1+(1+$item_max_length)*int(($idx+$prefix_cnt) / $col_vert_lines);
+
+ }
+ }
+ 'not found';
+}
+
+use Pod::Select qw();
+use Pod::Simple::TextContent;
+
+## get_desc_from_pod -- return setting description from pod documentation
+## $file - filename with pod
+## $setting - name of setting
+## returns description as text
+sub get_desc_from_pod {
+ my $file = shift;
+ return unless -s $file;
+ my $setting = shift;
+
+ open my $pod_sel, '>', \my $ss;
+ Pod::Select::podselect({
+ -output => $pod_sel,
+ -sections => ["SETTINGS/$setting"]}, $file);
+
+ my $pt = new Pod::Simple::TextContent;
+ $pt->output_string(\my $ss_f);
+ $pt->parse_string_document($ss);
+
+ my ($res) = $ss_f =~ /^\s*\Q$setting\E\s+(.*)\s*/;
+ $res
+}
+
+## get_settings_from_pod -- retrieve all settings in settings section of pod
+## $file - file with pod
+## returns list of all settings
+sub get_settings_from_pod {
+ my $file = shift;
+ return unless -s $file;
+
+ open my $pod_sel, '>', \my $ss;
+ Pod::Select::podselect({
+ -output => $pod_sel,
+ -sections => ["SETTINGS//!.+"]}, $file);
+
+ $ss =~ /^=head2\s+(.*)\s*$/mg
+}
+
+## mangle_man_for_wee -- turn man output into weechat codes
+sub mangle_man_for_wee {
+ for (@_) {
+ s/_\x08(.)/weechat::color('underline').$1.weechat::color('-underline')/ge;
+ s/(.)\x08\1/weechat::color('bold').$1.weechat::color('-bold')/ge;
+ }
+ wantarray ? @_ : $_[0]
+}
+
+## read_manpage -- read a man page in weechat window
+## $file - file with pod
+## $name - buffer name
+sub read_manpage {
+ my $caller_package = (caller)[0];
+ my $file = shift;
+ my $name = shift;
+
+ if (my $obuf = weechat::buffer_search('perl', "man $name")) {
+ eval qq{
+ package $caller_package;
+ weechat::buffer_close(\$obuf);
+ };
+ }
+
+ my @wee_keys = Nlib::i2h('key');
+ my @keys;
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+ my $buf = weechat::buffer_new("man $name", '', '', '', '');
+ return weechat::WEECHAT_RC_OK unless $buf;
+
+ my $width = $wininfo->{'chat_width'};
+ --$width if $wininfo->{'chat_width'} < $wininfo->{'width'} || ($wininfo->{'width_pct'} < 100 && (grep { $_->{'y'} == $wininfo->{'y'} } Nlib::i2h('window'))[-1]{'x'} > $wininfo->{'x'});
+
+ weechat::buffer_set($buf, 'time_for_each_line', 0);
+ eval qq{
+ package $caller_package;
+ weechat::buffer_set(\$buf, 'display', 'auto');
+ };
+ die $@ if $@;
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input history_previous' ||
+ $_->{'command'} eq '/input history_global_previous' } @wee_keys;
+ @keys = 'meta2-A' unless @keys;
+ weechat::buffer_set($buf, "key_bind_$_", '/window scroll -1') for @keys;
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input history_next' ||
+ $_->{'command'} eq '/input history_global_next' } @wee_keys;
+ @keys = 'meta2-B' unless @keys;
+ weechat::buffer_set($buf, "key_bind_$_", '/window scroll +1') for @keys;
+
+ weechat::buffer_set($buf, 'key_bind_ ', '/window page_down');
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input delete_previous_char' } @wee_keys;
+ @keys = ('ctrl-?', 'ctrl-H') unless @keys;
+ weechat::buffer_set($buf, "key_bind_$_", '/window page_up') for @keys;
+
+ weechat::buffer_set($buf, 'key_bind_g', '/window scroll_top');
+ weechat::buffer_set($buf, 'key_bind_G', '/window scroll_bottom');
+
+ weechat::buffer_set($buf, 'key_bind_q', '/buffer close');
+
+ weechat::print($buf, " \t".mangle_man_for_wee($_))
+ for `pod2man \Q$file\E 2>/dev/null | GROFF_NO_SGR=1 nroff -mandoc -rLL=${width}n -rLT=${width}n -Tutf8 2>/dev/null`;
+ weechat::command($buf, '/window scroll_top');
+
+ unless (hdh($buf, 'buffer', 'lines', 'lines_count') > 0) {
+ weechat::print($buf, weechat::prefix('error').$_)
+ for "Unfortunately, your @{[weechat::color('underline')]}nroff".
+ "@{[weechat::color('-underline')]} command did not produce".
+ " any output.",
+ "Working pod2man and nroff commands are required for the ".
+ "help viewer to work.",
+ "In the meantime, please use the command ", '',
+ "\tperldoc $file", '',
+ "on your shell instead in order to read the manual.",
+ "Thank you and sorry for the inconvenience."
+ }
+}
+
+1
+}
+
+use constant CMD_COPYWIN => SCRIPT_NAME;
+weechat::hook_command(CMD_COPYWIN, 'copy active window for hyperlink operations or free selection of text',
+ '[/] || url nicks channels',
+ 'if any or more arguments are given, go into hyperlink mode and set this filter.'."\n".
+ 'in the title bar of opened copy window, the possible key bindings are displayed.'."\n".
+ 'use '.weechat::color('bold').'/'.CMD_COPYWIN.' help'.weechat::color('-bold').
+ ' to read the manual',
+ '|| / %- || url %- || nicks %- || channels %- || url,nicks %- || url,channels %- '.
+ '|| url,nicks,channels %- || nicks,channels %- || help',
+ 'copywin_cmd', '');
+weechat::hook_signal('buffer_closed', 'garbage_str', '');
+weechat::hook_signal('upgrade', 'close_copywin', '');
+weechat::hook_config('plugins.var.perl.'.SCRIPT_NAME.'.*', 'default_options', '');
+weechat::hook_signal('mouse', 'mouse_evt', '');
+weechat::hook_signal('input_flow_free', 'binding_mouse_fix', '');
+weechat::hook_modifier('input_text_display_with_cursor', 'input_text_hlsel', '');
+# is there builtin mouse support?
+weechat::hook_hsignal(SCRIPT_NAME, 'hsignal_evt', '');
+weechat::key_bind('mouse', +{
+ map { $_ => 'hsignal:'.SCRIPT_NAME }
+ '@chat:button1*', '@chat:button1-event-*', '@chat(perl.[*):button1'
+});
+weechat::command('', '/alias copywin '.CMD_COPYWIN)
+ if 'copywin' ne CMD_COPYWIN && !Nlib::i2h('alias', '', 'copywin') && Nlib::i2h('hook', '', 'command,alias');
+
+# downloaded line fields
+use constant TRACE => -1;
+use constant LINE => 0;
+use constant LAYOUT => 1;
+
+# URLS structure fields
+use constant MSG => 0;
+use constant URL_S => 1;
+use constant URL => 2;
+use constant URL_E => 3;
+use constant URL_LINE => 4;
+use constant URL_INFO => 5;
+
+# captured control codes fields
+use constant RES => 0;
+use constant POS_S => 1;
+use constant POS_E => 2;
+
+# trace weelist container
+our $listptr;
+
+# global storage & fields
+our %STR;
+use constant BUFPTR => 0;
+use constant LINES => 1;
+use constant WINDOWS => 2;
+use constant A_LINK => 3;
+use constant URLS => 4;
+use constant MODE => 5;
+use constant CUR => 6;
+use constant MOUSE_AUTOMODE => 7;
+use constant URL_TYPE_FILTER => 8;
+# currently active storage key
+our $ACT_STR;
+
+our $last_mouse_seq;
+our $script_old_mouse_scroll;
+our $mouse_2nd_click;
+our $autoclose_in_progress;
+our $drag_speed_timer;
+our $delayed_nick_menu_timer;
+
+our $hsignal_mouse_down_sent;
+
+our $input_sel;
+
+our $LAYOUT_OK;
+
+my @SIMPLE_MODE_KEYS = ('/', 'U', 'N', 'C', 'P', 'u', 'n', 'c', 'p');
+
+init_coords();
+
+## hdh_get_lineinfo -- get lineinfo from hdata
+## $_[0] - line pointer
+## $_[1] - 'line' (automatic when using result from Nlib::hdh)
+## returns lineinfo hashref
+sub hdh_get_lineinfo {
+ my @line_data = Nlib::hdh(@_, 'data');
+ +{
+ next_line => scalar Nlib::hdh(@_, 'next_line'),
+ (map { $_ => scalar Nlib::hdh(@line_data, $_) }
+ qw(displayed message prefix str_time buffer date highlight)),
+ tag => [ map { Nlib::hdh(@line_data, "$_|tags_array") }
+ 0 .. Nlib::hdh(@line_data, 'tags_count')-1 ]
+ }
+}
+
+sub screen_length($) {
+ weechat::strlen_screen($_[0])
+}
+
+sub fu8on(@) {
+ Encode::_utf8_on($_) for @_; wantarray ? @_ : shift
+}
+
+## calculate_trace -- create a trace profile witgh word splits
+## $lineinfo - lineinfo with message to break into lines
+## $wininfo - window info hash with chat_width
+## $base_trace - ref to base trace with # fields
+## $prev_lineinfo - lineinfo of previous message for prefix erasure
+## returns (layoutinfo hashref, partial trace arrayref)
+sub calculate_trace {
+ # b : pos // break between words
+ # n : pos // new line
+ # x : pos // cut word
+ # t : x # j // time
+ # u : x # j // buffer name
+ # p : x # j // prefix (nick etc.)
+ # q : x # j // separator
+ # q : x . line
+ my ($lineinfo, $wininfo, $base_trace, $prev_lineinfo) = @_;
+ my $msg = fu8on $lineinfo->{'message'};
+ my $layoutinfo = +{ count => 1 };
+ my @trace;
+ my $show_time = $base_trace->[1];
+ my $no_prefix_align;
+ if ($base_trace->[-1] < 0) {
+ $no_prefix_align = $base_trace->[5];
+ $base_trace->[5] = $base_trace->[-1] = 0;
+ my $prefix = $lineinfo->{'prefix'};
+ if (defined $prefix) {
+
+ my ($nick_tag) = grep { /^nick_/ } @{$lineinfo->{'tag'}||[]};
+ my ($pnick_tag) = grep { /^nick_/ } @{$prev_lineinfo->{'tag'}||[]};
+ if ($nick_tag && $pnick_tag && $nick_tag eq $pnick_tag && !$lineinfo->{'highlight'} &&
+ (grep { /_action$/ && !/^nick_/ } @{$lineinfo->{'tag'}||[]}) == 0 &&
+ $prev_lineinfo && $lineinfo->{'prefix'} eq $prev_lineinfo->{'prefix'}) {
+ my $repl = weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_same_nick'));
+ $prefix = $repl if length $repl;
+ $prefix = '' if $repl eq ' ';
+ }
+ $base_trace->[5] = (sort { $a <=> $b } #($no_prefix_align?$no_prefix_align+1:0),
+ screen_length fu8on weechat::string_remove_color($prefix, ''))[0];
+ }
+ }
+ $base_trace->[1] = $show_time ? screen_length fu8on weechat::string_remove_color($lineinfo->{'str_time'}, '') : 0;
+ for (my ($i, $pos) = (0, 0); $i < @$base_trace; $i+=2) {
+ $pos += $base_trace->[$i+1] + 1 if $base_trace->[$i+1];
+ push @trace, $base_trace->[$i] . ':' . $pos . '#' if $base_trace->[$i+1];
+ }
+ my ($ctrl, $plain_msg) = capture_color_codes($msg);
+ my @words = split /(\s+)/, $plain_msg;
+ my $screen = 0;
+ if ($base_trace->[-1]) {
+ ($screen) = ((trace_cond('q', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ elsif ($base_trace->[5]) {
+ ($screen) = ((trace_cond('p', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ elsif ($base_trace->[3]) {
+ ($screen) = ((trace_cond('u', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ elsif ($base_trace->[1]) {
+ ($screen) = ((trace_cond('t', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ my $new_screen = 0;
+ my $eol_pos = weechat::config_string(weechat::config_get('weechat.look.align_end_of_lines'));
+ unless ($eol_pos eq 'time') {
+ ($new_screen) = ((trace_cond('t', [\@trace], 0))[0] =~ /:(\d+)/)
+ if $base_trace->[1];
+ unless ($eol_pos eq 'buffer') {
+ ($new_screen) = ((trace_cond('u', [\@trace], 0))[0] =~ /:(\d+)/)
+ if $base_trace->[3];
+ unless ($eol_pos eq 'prefix') {
+ ($new_screen) = ((trace_cond('p', [\@trace], 0))[0] =~ /:(\d+)/)
+ if $base_trace->[5];
+ unless ($eol_pos eq 'suffix') {
+ $new_screen = $screen;
+ }
+ }
+ }
+ }
+ unless ($lineinfo->{'date'}) {
+ $screen = $new_screen = 0;
+ @trace = ();
+ if ($base_trace->[3]) {
+ $screen = $base_trace->[3] + 1;
+ push @trace, "u:$screen#";
+ }
+ }
+ $base_trace->[1] = $show_time;
+ if (defined $no_prefix_align) {
+ $base_trace->[5] = $no_prefix_align;
+ $base_trace->[-1] = -1;
+ }
+
+ # XXX missing special case: $wininfo->{'chat_width'} - $screen < 4
+ # `--> ignore all line break rules, just X every screenful
+ my $pos = 0;
+ my $width = $wininfo->{'chat_width'};
+ --$width if $wininfo->{'chat_width'} < $wininfo->{'width'} || ($wininfo->{'width_pct'} < 100 && (grep { $_->{'y'} == $wininfo->{'y'} } Nlib::i2h('window'))[-1]{'x'} > $wininfo->{'x'});
+ for (my $i = 0; $i < @words; $i+=2) {
+ my $len = defined $words[$i] ? screen_length $words[$i] : 0;
+ my $len2 = $i == $#words ? 0 : screen_length $words[$i+1];
+ if ($len <= $width - $screen) {
+ # no action needed
+ $screen += $len + $len2;
+ }
+ elsif ($len > $width - $screen) {
+ if ($len <= $width - $new_screen && $pos) { #cannot break before first word
+ push @trace, 'b:'.$pos;
+ push @trace, 'q:'.$new_screen.'.'.$layoutinfo->{'count'}++;
+ $screen = $new_screen + $len + $len2;
+ }
+ else {
+ my $pump = $width - $screen;
+ if ($pump <= 0) {
+ push @trace, 'b:'.$pos;
+ push @trace, 'q:'.$new_screen.'.'.$layoutinfo->{'count'}++;
+ $pump = $width - $new_screen;
+ }
+ if ($pump > 0) {
+ my $ipos = $pos;
+ while ($pump < $len) {
+ my $i = 0;
+ for (;;) {
+ my $clen = screen_length substr $plain_msg, $ipos, 1;
+ last if $i + $clen > $pump;
+ $i += $clen;
+ ++$ipos;
+ }
+ push @trace, 'x:'.$ipos;
+ push @trace, 'q:'.$new_screen.'.'.$layoutinfo->{'count'}++;
+ $len -= $i;
+ $pump = $width - $new_screen;
+ }
+ }
+ $screen = $new_screen + $len + $len2;
+ }
+ }
+ $pos += ($len ? length $words[$i] : 0) + ($len2 ? length $words[$i+1] : 0);
+ }
+ while (@{$ctrl->[POS_S]}) {
+ my $ctrl_s = shift @{$ctrl->[POS_S]};
+ my $ctrl_e = shift @{$ctrl->[POS_E]};
+ my $ctrl_r = shift @{$ctrl->[RES]};
+ for (@trace) {
+ s{^([bnx]:)(\d+)}{
+ $1 . ( $2 + ($2 > $ctrl_s ? length $ctrl_r : 0) )
+ }ge;
+ }
+ }
+ ($layoutinfo, \@trace)
+}
+
+## download_lines -- load all (partially) visible lines & infos
+## $wininfo - hash ref with windowinfo of window to grab
+## returns reference to all downloaded lines
+sub download_lines {
+ my ($wininfo) = @_;
+
+ my @scroll_area = Nlib::hdh($wininfo->{'pointer'}, 'window', 'scroll');
+ my @lines_in = Nlib::hdh($wininfo->{'buffer'}, 'buffer', 'lines');
+ my $lineinfo = hdh_get_lineinfo(Nlib::hdh(@lines_in, 'last_line'));
+
+ my $show_time = Nlib::hdh($wininfo->{'buffer'}, 'buffer', 'time_for_each_line');
+ my $buffer_len = (sort { $a <=> $b } Nlib::hdh(@lines_in, 'buffer_max_length'), grep { $_ > 0 } weechat::config_integer(weechat::config_get('weechat.look.prefix_buffer_align_max')))[0];
+ my $prefix_len = (sort { $a <=> $b } Nlib::hdh(@lines_in, 'prefix_max_length'), grep { $_ > 0 } weechat::config_integer(weechat::config_get('weechat.look.prefix_align_max')))[0];
+ my $separator_len = weechat::config_string(weechat::config_get('weechat.look.prefix_align')) eq 'none' ? -1 : screen_length fu8on weechat::config_string(weechat::config_get('weechat.look.prefix_suffix'));
+
+ my @base_trace = (t => $show_time, u => $buffer_len, p => $prefix_len, q => $separator_len);
+
+ my @line = Nlib::hdh(@lines_in, 'last_line');
+ my $last_read_line = weechat::config_string(weechat::config_get('weechat.look.read_marker')) eq 'line'
+ ? Nlib::hdh(@lines_in, 'last_read_line')
+ : '';
+ my $read_marker_always = weechat::config_boolean(weechat::config_get('weechat.look.read_marker_always_show'));
+ $last_read_line = '' if $last_read_line eq $line[0]
+ && !$read_marker_always;
+ my $lp;
+ my @scroll_line = Nlib::hdh(@scroll_area, 'start_line');
+ if ($scroll_line[0]) {
+ @line = @scroll_line;
+ $lineinfo = hdh_get_lineinfo(@line);
+ $wininfo->{'start_line_pos'} = Nlib::hdh
+ (@scroll_area, 'start_line_pos');
+ }
+ else {
+ $wininfo->{'start_line_pos'} = 0;
+ my $total = Nlib::hdh(@lines_in, 'lines_count');
+ return [] unless $total;
+ my $not_last_line = 0;
+ for (my ($i, $j) = (0, 0); $j < $wininfo->{'chat_height'} && $i < $total; ++$i) {
+
+ $line[0] = Nlib::hdh(@line, 'prev_line') if $i > 0;
+
+ if ($line[0] eq $last_read_line) {
+ if ($not_last_line || $read_marker_always) {
+ ++$j;
+ }
+ else {
+ $last_read_line = '';
+ }
+ }
+ my @line_data = Nlib::hdh(@line, 'data');
+ if (Nlib::hdh(@line_data, 'displayed')) {
+ my $lineinfo = +{ map { $_ => Nlib::hdh(@line_data, $_) } qw(message str_time date highlight) };
+ my $prev_lineinfo;
+ if ($base_trace[-1] < 0 && $i + 1 < $total) {
+ HAS_PREV: {
+ my @prev_line = @line;
+ my @prev_linedata;
+ do {
+ @prev_line = Nlib::hdh(@prev_line, 'prev_line');
+ last HAS_PREV unless $prev_line[0];
+ @prev_linedata = Nlib::hdh(@prev_line, 'data');
+ } until (Nlib::hdh(@prev_linedata, 'displayed'));
+ $prev_lineinfo = +{
+ (map { $_ => Nlib::hdh(@prev_linedata, $_) } qw(message str_time date highlight prefix)),
+ tag => [
+ map { Nlib::hdh(@prev_linedata, "$_|tags_array") }
+ 0 .. Nlib::hdh(@prev_linedata, 'tags_count')-1 ]
+ };
+ my ($prev_layoutinfo) = calculate_trace($prev_lineinfo,
+ $wininfo, \@base_trace);
+ my ($this_layoutinfo) = calculate_trace($lineinfo,
+ $wininfo, \@base_trace);
+ #$prev_lineinfo = undef if $prev_layoutinfo->{'count'} + $this_layoutinfo->{'count'} + $j > $wininfo->{'chat_height'};
+ }
+ $lineinfo->{'prefix'} = Nlib::hdh(@line_data, 'prefix');
+ $lineinfo->{'tag'} = [
+ map { Nlib::hdh(@line_data, "$_|tags_array") }
+ 0 .. Nlib::hdh(@line_data, 'tags_count')-1 ];
+ }
+ my ($layout_info) = calculate_trace($lineinfo,
+ $wininfo, \@base_trace,
+ $prev_lineinfo);
+ $prev_lineinfo = $lineinfo;
+ $j += $layout_info->{'count'};
+ $not_last_line = 1 if $j;
+ $wininfo->{'start_line_pos'} -= $wininfo->{'chat_height'} - $j
+ if $j > $wininfo->{'chat_height'};
+ }
+ }
+ $lineinfo = hdh_get_lineinfo(@line);
+ }
+ $lp = $line[0];
+
+ my @lines;
+ my $current_line = 0;
+ if ($lineinfo->{'displayed'}) {
+ push @lines, [+{%$lineinfo}, calculate_trace($lineinfo, $wininfo, \@base_trace)];
+ $current_line = $lines[0][LAYOUT]{'count'}-$wininfo->{'start_line_pos'};
+ }
+ my $prev_lineinfo;
+ $prev_lineinfo = $lineinfo unless $wininfo->{'start_line_pos'};
+ # XXX start_line_pos is buggy under yet uncertain multi-line messages
+ #$wininfo->{'start_line_pos'} = 6;
+
+ do {
+ if ($lp eq $last_read_line) {
+ push @lines, [+{message=>'',prefix=>''}, +{count=>1}, []];
+ ++$current_line;
+ }
+
+ $lp = $lineinfo->{'next_line'};
+ $lineinfo = hdh_get_lineinfo($lineinfo->{'next_line'}, 'line');
+
+ if ($lineinfo->{'displayed'}) {
+ push @lines, [+{%$lineinfo}, calculate_trace($lineinfo, $wininfo, \@base_trace, $prev_lineinfo)];
+ $prev_lineinfo = $lineinfo;
+ $current_line += $lines[-1][LAYOUT]{'count'};
+ }
+ }
+ while ($lineinfo->{'next_line'} && $current_line < $wininfo->{'chat_height'});
+
+ \@lines;
+}
+
+## OLD_download_lines -- load all (partially) visible lines & infos
+## $wininfo - hash ref with windowinfo of window to grab
+## returns reference to all downloaded lines
+sub OLD_download_lines {
+ my ($wininfo) = @_;
+
+ my @lines;
+
+ # get first line of buffer
+ return \@lines unless $wininfo->{'start_line'};
+ my ($lineinfo) = Nlib::i2h('buffer_lines', @{$wininfo}{'buffer','start_line'});
+ my ($layoutinfo) = Nlib::i2h('layout', $wininfo->{'pointer'}, $lineinfo->{'line'}, $listptr);
+ my $current_line = $layoutinfo->{'count'}-$wininfo->{'start_line_pos'};
+
+ push @lines, [+{%$lineinfo}, $layoutinfo, [Nlib::l2l($listptr, 1)]];
+
+ while ($lineinfo->{'next_line'} && $current_line < $wininfo->{'chat_height'}) {
+ ($lineinfo) = Nlib::i2h('buffer_lines', @{$lineinfo}{'buffer','next_line'});
+ next unless $lineinfo->{'displayed'};
+ my ($layoutinfo) = Nlib::i2h('layout', $wininfo->{'pointer'}, $lineinfo->{'line'}, $listptr);
+ $current_line += $layoutinfo->{'count'};
+
+ push @lines, [+{%$lineinfo}, $layoutinfo, [Nlib::l2l($listptr, 1)]];
+ }
+
+ \@lines
+}
+
+## message_splits -- extract word splits in the message from trace
+## $line - one line from download_lines
+## returns all split events from trace as list of [position, event]
+sub message_splits {
+ my ($line) = @_;
+ map {
+ my ($l, $d) = /(.):(\d+)/;
+ #($l =~ /[bn]/ ? 1 : 0) +
+ [ $d, $l ]
+ }
+ grep { /^[bnx]:/ } # b: break between words, n: new line, x: cut word
+ @{ $line->[TRACE] }
+}
+
+## trace_cond -- filter trace for certain condition
+## $what - event to match
+## $line - downloaded line (with trace section)
+## $lineno - line number for event
+## returns matching trace event
+sub trace_cond {
+ my ($what, $line, $lineno) = @_;
+ my $ext = $lineno ? qr/[.]$lineno$/ : qr/#/;
+ grep { /^$what:/ && /$ext/ } @{ $line->[TRACE] }
+}
+
+## to_pos -- pad string to reach a certain position
+## $tr_info - trace event from trace_cond, contains position
+## $c_ref - string reference where padding is added
+sub to_pos {
+ my ($tr_info, $c_ref) = @_;
+ $tr_info =~ /:(\d+)/;
+ my $pos = $1;
+ my $current_length = screen_length fu8on weechat::string_remove_color($$c_ref, '');
+ if ($pos-$current_length < 0) {
+ chop $$c_ref while length $$c_ref && (screen_length fu8on weechat::string_remove_color($$c_ref, '')) > $pos;
+ }
+ else {
+ $$c_ref .= ' 'x($pos-$current_length);
+ }
+}
+
+## right_align -- right align some text
+## $tr - trace event, contains position for right alignment
+## $text_ref - text reference to right align
+## $c_ref - string reference where padding is added, so that $text_ref would be right aligned
+sub right_align {
+ my ($tr, $text_ref, $c_ref) = @_;
+ $tr =~ /:(\d+)/;
+ my $pos1 = $1 -1;
+ my $text_length = screen_length fu8on weechat::string_remove_color($$text_ref, '');
+ my $current_length = screen_length fu8on weechat::string_remove_color($$c_ref, '');
+ $$c_ref .= ' 'x($pos1-$text_length-$current_length)
+}
+
+## show_time -- output timestamp and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_time {
+ my ($line, $c_ref) = @_;
+ if (my ($tr) = trace_cond('t', $line)) {
+ $$c_ref .= $line->[LINE]{'str_time'};
+ to_pos($tr, $c_ref);
+ }
+}
+
+## show_buffername -- output buffer name and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_buffername {
+ my ($line, $c_ref) = @_;
+ if (my ($tr) = trace_cond('u', $line)) {
+ my $buffer = weechat::color('chat_prefix_buffer').
+ (Nlib::i2h('buffer', $line->[LINE]{'buffer'}))[0]{'short_name'}.
+ weechat::color('reset');
+ if (weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_buffer_align')) =~ /right/) {
+ right_align($tr, \$buffer, $c_ref);
+ }
+ $$c_ref .= $buffer;
+ to_pos($tr, $c_ref);
+ }
+}
+
+## show_prefix -- output prefix and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_prefix {
+ my ($line, $c_ref, $prev_line) = @_;
+ if (my ($tr) = trace_cond('p', $line)) {
+ my $prefix = fu8on $line->[LINE]{'prefix'};
+ my ($nick_tag) = grep { /^nick_/ } @{$line->[LINE]{'tag'}||[]};
+ my ($pnick_tag) = grep { /^nick_/ } @{$prev_line->[LINE]{'tag'}||[]};
+ if ($nick_tag && $pnick_tag && $nick_tag eq $pnick_tag && !$line->[LINE]{'highlight'} &&
+ (grep { /_action$/ && !/^nick_/ } @{$line->[LINE]{'tag'}||[]}) == 0 &&
+ $prev_line && $line->[LINE]{'prefix'} eq $prev_line->[LINE]{'prefix'}) {
+ my $repl = fu8on weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_same_nick'));
+ $prefix = repeat_control_codes($prefix) . $repl if $repl;
+ }
+ if (weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_align')) =~ /right/) {
+ right_align($tr, \$prefix, $c_ref);
+ }
+ $$c_ref .= $prefix;
+ to_pos($tr, $c_ref);
+ }
+}
+
+## show_separator -- output separator and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_separator {
+ my ($line, $c_ref, $lineno) = @_;
+ if (my ($tr) = trace_cond('q', $line, $lineno)) {
+ my $separator = fu8on weechat::color('chat_prefix_suffix').
+ weechat::config_string(weechat::config_get('weechat.look.prefix_suffix'));
+ right_align($tr, \$separator, $c_ref);
+ $$c_ref .= $separator; # if XXX
+ to_pos($tr, $c_ref);
+ }
+}
+
+## repeat_control_codes -- repeat control codes previously seen (for linebreaks)
+## $text - all codes in this text will be repeated
+## returns control codes as a string
+sub repeat_control_codes {
+ my ($text) = @_;
+ my $id_control = quotemeta fu8on weechat::string_remove_color($text, "\1");
+ $id_control =~ s/\\\01/(.+?)/g;
+ my @res = $text =~ /^()$id_control()$/;
+ join '', @res
+}
+
+## calc_free_image -- create image of free window buffer
+## $wininfo - window info hash with start_line_pos & chat_height
+## $lines_ref - array ref of lines from download_lines
+## returns a array reference to all lines in the image
+sub calc_free_image {
+ my ($wininfo, $lines_ref) = @_;
+ my @image;
+ my $i = 0;
+ my $first_line = $wininfo->{'start_line_pos'};
+ my $prev_line;
+ for my $line (@$lines_ref) {
+ my $max_length = [(length $line->[LINE]{'message'}),''];
+ my @splits = ([0,''], message_splits($line), $max_length, $max_length);
+ for (0 .. $line->[LAYOUT]{'count'}-1) {
+ shift @splits if $splits[0][0] - $splits[1][0] >= 0;
+ last if $i >= $wininfo->{'chat_height'};
+ my $subm = substr $line->[LINE]{'message'}, $splits[0][0], $splits[1][0]-$splits[0][0];
+ if ($_ >= $first_line) {
+ my $construction = '';
+ unless ($_) {
+ show_time($line, \$construction);
+ show_buffername($line, \$construction);
+ show_prefix($line, \$construction, $prev_line);
+ $prev_line = $line if exists $line->[LINE]{'date'};
+ }
+ show_separator($line, \$construction, $_);
+ $construction .= weechat::color('reset');
+ $construction .= repeat_control_codes(substr $line->[LINE]{'message'}, 0, $splits[0][0]);
+ $subm =~ s/^ +// if $splits[0][1] =~ /[bn]/;
+ $construction .= $subm;
+ $construction .= weechat::color('reset');
+
+ push @image, $construction;
+ $i++;
+ }
+ shift @splits;
+ }
+ $first_line = 0;
+ }
+ \@image
+}
+
+## capture_color_codes -- extract all weechat control codes from a message and store them with their original positions
+## $msg - message from which to start
+## returns a array ref with C<RES> (control codes), C<POS_S> (start position), C<POS_E> (end position) and optionally string without all codes
+sub capture_color_codes {
+ my ($msg) = @_;
+ my $id_control = quotemeta fu8on weechat::string_remove_color($msg, "\1");
+ $id_control =~ s/(\\\01)+/(.+?)/g;
+ my @ctrl_res = $msg =~ /^()$id_control()$/;
+ my @ctrl_pos_s = @-;
+ my @ctrl_pos_e = @+;
+
+ shift @ctrl_pos_s; shift @ctrl_pos_e;
+
+ #my @chunks = split "\01+", $color_1, -1;
+
+ my @ret = \( @ctrl_res, @ctrl_pos_s, @ctrl_pos_e );
+ if (wantarray) {
+ ( \@ret, fu8on weechat::string_remove_color($msg, '') );
+ }
+ else {
+ \@ret
+ }
+}
+
+sub DEBUG_bracketing {
+ my ($bracketing_r, $msg_r, $fx) = @_;
+ return unless $fx;
+ my $dumpline = ' ' x length $$msg_r;
+ my @pair_pool = ('a'..'z');
+ for (sort { $a eq 'oc' ? -1 : $b eq 'oc' ? 1 : $a <=> $b } keys %$bracketing_r) {
+ my @l = @{ $bracketing_r->{$_} };
+ if (@l) {
+ my $ch = shift @pair_pool;
+ substr $dumpline, $l[0], 1, $ch;
+ substr $dumpline, $l[-2], 1, "\U$ch";
+ push @pair_pool, $ch;
+ }
+ }
+ print $fx $$msg_r."\n$dumpline\n";
+}
+
+## calculate_bracketing -- try to detect bracketing pairs
+## $msg_r - reference to bracketed string
+## returns hash ref to detected bracketing
+sub calculate_bracketing {
+ my ($msg_r) = @_;
+ my $braces = weechat::config_get_plugin('url_braces');
+ my %br_open;
+ while ($braces =~ s/^(.)(.*)(.)$/$2/) {
+ $br_open{$3} = $1;
+ }
+ $br_open{$braces} = $braces if length $braces;
+ my %bracketing;
+ my $br_open = join '|', map { quotemeta } values %br_open;
+ my $br_close = join '|', map { quotemeta } keys %br_open;
+ while ($$msg_r =~ /($br_open|$br_close)/g) {
+ my $char = $1;
+ my $pos = $-[0];
+ if ($char =~ /$br_close/ && @{ $bracketing{'oc'} || [] } && $bracketing{'oc'}[-1][-1] eq $br_open{$char}) {
+ my $match = pop @{ $bracketing{'oc'} };
+ push @$match, $pos, $char;
+ $bracketing{$pos} = $bracketing{$match->[0]} = $match;
+ }
+ elsif ($char =~ /$br_open/) {
+ push @{ $bracketing{'oc'} }, [ $pos, $char ];
+ }
+ else {
+ $bracketing{$pos} = [ $pos, $char ];
+ }
+ }
+ while (@{ $bracketing{'oc'} || [] }) {
+ my $match = shift @{ $bracketing{'oc'} };
+ $bracketing{$match->[0]} = $match;
+ }
+ \%bracketing
+}
+
+sub DEBUG_hyperlink {
+ my ($s, $plain_msg, $e, $fx) = @_;
+ return unless $fx;
+ my $dumpline = ' ' x length $plain_msg;
+ substr $dumpline, $s, 1, "\\";
+ substr $dumpline, $e-1, 1, '/';
+ print $fx ' '.$dumpline."\n";
+ print $fx "----------\n";
+}
+
+## hyperlink_adjust_region -- removes bracketing and word end markers if detected around/at the end of region
+## $sr - reference to start position of hyperlink
+## $msg_r - reference to string with hyperlink
+## $er - reference to end position of hyperlink
+## $bracketing_r - calculated bracketing for this string
+sub hyperlink_adjust_region {
+ my ($sr, $msg_r, $er, $bracketing_r, $no_url) = @_;
+ my $non_endings = weechat::config_get_plugin('url_non_endings');
+ my $non_beginnings = weechat::config_get_plugin('url_non_beginnings');
+ for (undef) {
+ if (exists $bracketing_r->{$$er-1} && $bracketing_r->{$$er-1}[0] == $$sr) {
+ ++$$sr; --$$er; redo;
+ }
+ elsif (exists $bracketing_r->{$$er-1} && $bracketing_r->{$$er-1}[0] < $$sr) {
+ --$$er; redo;
+ }
+ elsif (exists $bracketing_r->{$$sr} && $bracketing_r->{$$sr}[-2] > $$er-1) {
+ ++$$sr; redo;
+ }
+ unless ($no_url) {
+ if ((substr $$msg_r, $$er-1, 1) =~ /$non_endings/) {
+ --$$er; redo;
+ }
+ elsif ((substr $$msg_r, $$sr, 1) =~ /$non_beginnings/) {
+ ++$$sr; redo;
+ }
+ }
+ }
+}
+
+sub trace_to_u8 {}
+
+sub OLD_trace_to_u8 {
+ my ($line) = @_;
+ my $bytes_msg = $line->[LINE]{'message'};
+ Encode::_utf8_off($bytes_msg);
+
+ for (@{ $line->[TRACE] }) {
+ s{^([bnx]:)(\d+)}{
+ $1 . length fu8on substr $bytes_msg, 0, $2;
+ }ge;
+ }
+}
+
+## hyperlink_replay_code1 -- insert start/end of marking and adjust positions/trace
+## $line - downloaded line with trace info to adjust
+## $msg_r - reference to msg in which to insert
+## $coder - reference to code to insert
+## $tr - seek this much to the left when correcting trace profile
+## @advs - references to arrays of positions to advance, first one will be shifted
+## $fx - debug file handle
+## $DBG - output debug info, prefix with $DBG
+sub hyperlink_replay_code1 {
+ my ($line, $msg_r, $coder, $tr, @advs, $fx, $DBG) = @_;
+ unless (ref $advs[-1]) {
+ $DBG = pop @advs;
+ $fx = pop @advs;
+ }
+ my $pos_r = $advs[0];
+ my $pos = shift @$pos_r;
+ print $fx '[1]'."code:$$coder pos:$pos\n" if $fx;
+ substr $$msg_r, $pos, 0, $$coder;
+ print $fx $DBG.' '.$$msg_r."\n" if $fx;
+ for (@advs) {
+ $_ += length $$coder for @$_;
+ }
+ for (@{ $line->[TRACE] }) {
+ s{^([bnx]:)(\d+)}{
+ $1 . ( $2 + ($2 > $pos-$tr ? length $$coder : 0) )
+ }ge;
+ }
+}
+
+## hyperlink_replay_code2 -- reinsert color codes and adjust positions
+## $ctrl - captured control codes from capture_color_codes
+## $msg_r - reference to msg in which to insert
+## @advs - references to arrays of positions to advance
+## $fx - debug file handle
+## $DBG - output debug info, prefix with $DBG
+sub hyperlink_replay_code2 {
+ my ($ctrl, $msg_r, @advs, $fx, $DBG) = @_;
+ unless (ref $advs[-1]) {
+ $DBG = pop @advs;
+ $fx = pop @advs;
+ }
+ my $ctrl_s = shift @{$ctrl->[POS_S]};
+ my $ctrl_e = shift @{$ctrl->[POS_E]};
+ my $ctrl_r = shift @{$ctrl->[RES]};
+ substr $$msg_r, $ctrl_s, 0, $ctrl_r;
+ print $fx $DBG.' '.$$msg_r."\n" if $fx;
+ for (@advs) {
+ $_ += $ctrl_e-$ctrl_s for @$_;
+ }
+}
+
+
+## hyperlink_replay_codes -- insert marker codes, reinsert control codes into plain msg
+## $line - line with trace data to adjust
+## $url_sr - reference to hyperlink starting position
+## $msg_r - reference to message where positions apply
+## $url_er - reference to hyperlink ending positions
+## $ctrl - capture_color_codes result of control codes
+## $active - reference that counts no of links and actives on "0"
+## $fx - debug file handle
+sub hyperlink_replay_codes {
+ my ($line, $url_sr, $msg_r, $url_er, $ctrl, $active, $fx) = @_;
+
+ my $ul = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight');
+ #my $UL = weechat::color('-underline').weechat::color('-reverse');
+
+ my $ul_active = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight_active');
+ my $UL_active = weechat::color('reset');
+
+ my $last_url_s;
+
+ my $max_loop = 2*( @{$ctrl->[POS_S]} + @$url_sr + @$url_er );
+ while (@{$ctrl->[POS_S]} || @$url_sr || @$url_er) {
+ print $fx "<S> @$url_sr\n<E> @$url_er\n<C> @{$ctrl->[POS_S]}\n" if $fx;
+ #if (@$url_sr && $url_sr->[0] <= $url_er->[0] && (!@{$ctrl->[POS_S]} || $url_sr->[0] <= $ctrl->[POS_S][0])) # code goes before original ctl code
+ if (@$url_sr && $url_sr->[0] <= $url_er->[0] && (!@{$ctrl->[POS_S]} || $url_sr->[0] < $ctrl->[POS_S][0])) {
+ $last_url_s = $url_sr->[0];
+ hyperlink_replay_code1($line, $msg_r, ($$active ? \$ul : \$ul_active), 0,
+ $url_sr, $url_er, @{$ctrl}[POS_S,POS_E], $fx, 'S');
+ }
+ elsif (@$url_er && (!@{$ctrl->[POS_S]} || $url_er->[0] <= $ctrl->[POS_S][0])) {
+ my $UL_active1 = $UL_active;
+ if (defined $last_url_s) {
+ # get part of message constructed thus far, and remove starting bracket
+ my $msg_part1 = substr $$msg_r, 0, $url_er->[0];
+ substr $msg_part1, $last_url_s, length ($$active ? $ul : $ul_active), '';
+ $UL_active1 .= repeat_control_codes($msg_part1);
+ $last_url_s = undef;
+ }
+ hyperlink_replay_code1($line, $msg_r, ($$active ? \$UL_active1 : \$UL_active1), 1,
+ $url_er, $url_sr, @{$ctrl}[POS_S,POS_E], $fx, 'E');
+ --$$active;
+ }
+ else { # ($ctrl->[POS_S][0] <= $url_sr->[0] && $ctrl->[POS_S][0] <= $url_er->[0])
+ my $ip = $ctrl->[POS_E][0];
+ my $needs_fixup = defined $last_url_s && $ctrl->[RES][0] =~ $UL_active;
+ hyperlink_replay_code2($ctrl, $msg_r, $url_sr, $url_er, $fx, 'C');
+ if ($needs_fixup) {
+ $last_url_s = $ip;
+ hyperlink_replay_code1($line, $msg_r, ($$active ? \$ul : \$ul_active), 0,
+ [$ip], $url_sr, $url_er, @{$ctrl}[POS_S,POS_E], $fx, 'F');
+ }
+ }
+ --$max_loop; die 'endless loop' if $max_loop < 0;
+ }
+}
+
+## hyperlink_match_type_filter -- checks if current type filter applies to url info
+## $url_info - hashref with a type property
+## returns true or false
+sub hyperlink_match_type_filter {
+ my ($url_info) = @_;
+ my $t = substr $url_info->{'type'}, 0, 1;
+ $ACT_STR->[URL_TYPE_FILTER] =~ $t
+}
+
+## hyperlink_function -- highlight hyperlinks
+## $lines_ref - downloaded lines
+sub hyperlink_function {
+ my ($lines_ref) = @_;
+ my $fx;
+ #open $fx, '>', ... || weechat::print('', "error:$!");
+ my ($nicklist, $channels);
+ if (Nlib::has_true_value(weechat::config_get_plugin('hyper_nicks'))) {
+ $nicklist = join '|',
+ map { quotemeta }
+ sort { length $b <=> length $a }
+ map { $_->{'name'} }
+ grep { $_->{'type'} eq 'nick' && $_->{'visible'} && length $_->{'name'} }
+ Nlib::i2h('nicklist', $ACT_STR->[WINDOWS]{'buffer'});
+ }
+ else {
+ $nicklist = '(?!)';
+ }
+ $nicklist = '(?!)' unless length $nicklist; # stop infinite loop on empty pair
+ if (Nlib::has_true_value(weechat::config_get_plugin('hyper_channels'))) {
+ $channels = qr,[#]+(?:\w|[][./+^!&|~}{)(:\\*@?'-])+,;
+ }
+ else {
+ $channels = '(?!)';
+ }
+
+ my $re = weechat::config_get_plugin('url_regex');
+ $ACT_STR->[A_LINK] = -1 unless defined ${$ACT_STR}[A_LINK];
+ my $a_link = -1;
+ if (defined $ACT_STR->[URLS] && $ACT_STR->[A_LINK] < @{$ACT_STR->[URLS]}
+ && hyperlink_match_type_filter($ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO])) {
+ for my $i (0 .. $ACT_STR->[A_LINK]) {
+ ++$a_link
+ if hyperlink_match_type_filter($ACT_STR->[URLS][$i][URL_INFO]);
+ }
+ }
+ my @urls;
+ my $i = 0; # line index
+ for my $line (@$lines_ref) {
+ my %prefix_type = (type => 'prefix');
+ my ($pfx_nick) = grep { /^nick_/ } @{ $line->[LINE]{'tag'} };
+
+ if (Nlib::has_true_value(weechat::config_get_plugin('hyper_prefix')) &&
+ $line->[LINE]{'prefix'} &&
+ defined $pfx_nick && $pfx_nick =~ s/^nick_//) {
+ push @urls, [ $line, -1, $pfx_nick, -1, $i, \%prefix_type ];
+
+ if (hyperlink_match_type_filter(\%prefix_type)) {
+ my $ul = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight');
+
+ my $ul_active = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight_active');
+ my $UL_active = weechat::color('reset');
+
+ my ($ctrl, $plain_pfx) = capture_color_codes(fu8on $line->[LINE]{'prefix'});
+ my $my_ul = $a_link ? $ul : $ul_active;
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-2], 0, $my_ul;
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-1]+length $my_ul, 0, $UL_active;
+ --$a_link;
+ }
+ }
+ my $msg = fu8on $line->[LINE]{'message'};
+ my ($ctrl_codes, $plain_msg) = capture_color_codes($msg);
+ my $bracketing_r = calculate_bracketing(\$plain_msg);
+ DEBUG_bracketing($bracketing_r, \$plain_msg, $fx);
+ my (@url_s, @url_res, @url_e);
+ while ($plain_msg =~ /\b($nicklist)(?:(?=\W)|$)|(?:^|(?<=\W))($channels)\b|$re/gx) {
+ my %typeinfo = (type => defined $1 ? 'nick' : defined $2 ? 'channel' : 'url');
+ my ($s, $e) = ($-[0], $+[0]);
+ DEBUG_hyperlink($s, $plain_msg, $e, $fx);
+
+ hyperlink_adjust_region(\($s, $plain_msg, $e), $bracketing_r, $typeinfo{'type'} ne 'url')
+ unless $typeinfo{'type'} eq 'nick';
+ my $t = substr $plain_msg, $s, $e-$s;
+ if (hyperlink_match_type_filter(\%typeinfo)) {
+ push @url_s, $s;
+ push @url_res, $t;
+ push @url_e, $e;
+ }
+ DEBUG_hyperlink($s, $plain_msg, $e, $fx);
+ push @urls, [ $line, $s, $t, $e, $i, \%typeinfo ];
+ }
+
+ print $fx "X $plain_msg\n" if $fx;
+ trace_to_u8($line);
+ hyperlink_replay_codes($line, \(@url_s, $plain_msg, @url_e), $ctrl_codes, \$a_link, $fx);
+ $line->[LINE]{'message'} = $plain_msg;
+
+ ++$i;
+ }
+ $ACT_STR->[URLS] = \@urls;
+ $ACT_STR->[A_LINK] = @{ $ACT_STR->[URLS] }
+ if $ACT_STR->[A_LINK] < 0;
+}
+
+## copy_lines -- make a copy of downloaded lines (dumb dclone)
+## $lines_ref - reference to copy
+## returns a reference copy of the reference content
+sub copy_lines {
+ my ($lines_ref) = @_;
+ my $lines_ref2 = [];
+ push @$lines_ref2, [ +{%{$_->[LINE]}}, +{%{$_->[LAYOUT]}}, [@{$_->[TRACE]}] ]
+ for @$lines_ref;
+ $lines_ref2
+}
+
+## send_clip_external -- send clipboard to external app
+## $text - text for clipboard
+## $xterm_osc - xterm-compatible escape sequence
+sub send_clip_external {
+ my ($text, $xterm_osc) = @_;
+ if (weechat::config_is_set_plugin('clipboard_command')) {
+ my %presets = ( xsel => '|xsel -c', xclip => '|xclip' );
+ my $external = weechat::config_get_plugin('clipboard_command');
+ $external = $presets{$external} if exists $presets{$external};
+ if ($external =~ /^\|/) {
+ if (open my $ext, $external) {
+ print $ext $text;
+ }
+ else {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ }
+ elsif ($external =~ s/%q/\Q$text/ || $external =~ s/%x/\Q$xterm_osc/) {
+ unless (system($external) >= 0) {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ }
+ else {
+ if ($external eq 'tmux') {
+ unless (( system { $external } $external, 'deleteb' ) >= 0) {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ $external .= ' setb';
+ }
+ my @cmd = split ' ', $external;
+ if (grep { $_ eq '%s' } @cmd) {
+ @cmd = map { $_ eq '%s' ? $text : $_ } @cmd;
+ }
+ else {
+ push @cmd, $text;
+ }
+ unless (( system { $cmd[0] } @cmd ) >= 0) {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ }
+ }
+}
+
+## send_clip -- send text to selection clipboard
+## $text - text for clipboard
+## $stor - storage unit (optional, default s0)
+sub send_clip {
+ my ($text, $stor) = @_;
+ $stor = '' unless $stor;
+ my $text_nu = $text;
+ Encode::_utf8_off($text_nu);
+ my $xterm_osc = "\e]52;$stor;".encode_base64($text_nu, '')."\a";
+ my $compatible_terms = join '|', map { split /[,;]/ } split ' ',
+ weechat::config_get_plugin('xterm_compatible');
+ print STDERR $xterm_osc if $ENV{'TERM'} =~ /^xterm|$compatible_terms/;
+ if ($ENV{'TMUX'}) {
+ my @tmux_clients = `tmux lsc`;
+ my $active_term;
+ my $last_time = 0;
+ for (@tmux_clients) {
+ chomp;
+ my ($path, $rest) = split ':', $_;
+ next unless $rest =~ / (?:xterm|$compatible_terms)/;
+ my $atime = -A $path;
+ if ($last_time >= $atime) {
+ $last_time = $atime;
+ $active_term = $path;
+ }
+ }
+ if ($active_term) {
+ open my $pty, '>>', $active_term;
+ print $pty $xterm_osc;
+ }
+ }
+ send_clip_external($text, $xterm_osc);
+}
+
+## hyperlink_to_clip -- send currently active link to clipboard
+sub hyperlink_to_clip {
+ if ($ACT_STR->[A_LINK] >= 0 && $ACT_STR->[A_LINK] < @{ $ACT_STR->[URLS] }) {
+ my $url = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ send_clip($url);
+ }
+}
+
+## hyperlink_urlopen -- send url open signal for currently active link
+sub hyperlink_urlopen {
+ my $url = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ send_clip($url, 's0x');
+ weechat::hook_signal_send('urlopen', weechat::WEECHAT_HOOK_SIGNAL_STRING, $url);
+}
+
+## selection_to_clip -- send currently active selection to clipboard
+sub selection_to_clip {
+ if ($ACT_STR->[CUR][0*2] > -1 && $ACT_STR->[CUR][1*2] > -1) {
+ my @range = sort { $a <=> $b } @{$ACT_STR->[CUR]}[0*2,1*2];
+ my @lines = map { fu8on weechat::string_remove_color($_->[LINE]{'message'},'') } @{$ACT_STR->[LINES]}[$range[0]..$range[1]];
+ my @prefixes = map { fu8on weechat::string_remove_color($_->[LINE]{'prefix'},'') } @{$ACT_STR->[LINES]}[$range[0]..$range[1]];
+ my @cuts = map { $_->[1] } sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } ([@{$ACT_STR->[CUR]}[0*2,0*2+1]], [@{$ACT_STR->[CUR]}[1*2,1*2+1]]);
+ $lines[0] = length $lines[0] >= $cuts[0] ? substr $lines[0], $cuts[0] : ''; #(substr outside of string?)
+ $prefixes[0] = undef;# if $cuts[0];
+ $lines[-1] = substr $lines[-1], 0, $range[0]==$range[1] ? $cuts[1]-$cuts[0] : $cuts[1];
+ my $sel_text = join "\n", map { my $pfx = shift @prefixes; ($pfx ? "$pfx\t" : '') . $_ } @lines;
+ send_clip($sel_text);
+ }
+}
+
+## hyperlink_dispatch_input -- dispatch input from ** commands in hyperlink mode
+## $args - input argument
+## returns true value if processing is continued, false otherwise
+sub hyperlink_dispatch_input {
+ my ($args) = @_;
+ if ($args eq '+') {
+ ++$ACT_STR->[A_LINK];
+ $ACT_STR->[A_LINK] = 0
+ if $ACT_STR->[A_LINK] > @{ $ACT_STR->[URLS] };
+ until ($ACT_STR->[A_LINK] >= @{$ACT_STR->[URLS]}
+ || hyperlink_match_type_filter($ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO])) {
+ ++$ACT_STR->[A_LINK];
+ }
+ hyperlink_to_clip();
+ }
+ elsif ($args eq '-') {
+ --$ACT_STR->[A_LINK];
+ until ($ACT_STR->[A_LINK] < 0
+ || hyperlink_match_type_filter($ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO])) {
+ --$ACT_STR->[A_LINK];
+ }
+ $ACT_STR->[A_LINK] = @{ $ACT_STR->[URLS] }
+ if $ACT_STR->[A_LINK] < 0;
+ hyperlink_to_clip();
+ }
+ elsif ($args eq '!') {
+ if ($ACT_STR->[A_LINK] >= 0 && $ACT_STR->[A_LINK] < @{ $ACT_STR->[URLS] }) {
+ my $link_type = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'};
+ if ($link_type eq 'nick' || $link_type eq 'prefix') {
+ my $nick = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ if (Nlib::has_false_value(weechat::config_get_plugin('use_nick_menu'))) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/query $nick");
+ }
+ else {
+ delayed_nick_menu($nick);
+ close_copywin(); # XXX
+ }
+ }
+ elsif ($link_type eq 'channel') {
+ my $channel = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/join $channel");
+ }
+ else {
+ hyperlink_urlopen();
+ }
+ }
+ else {
+ weechat::command($ACT_STR->[BUFPTR], '/input return');
+ }
+ return;
+ }
+ else {
+ return;
+ }
+ 1
+}
+
+## selection_dispatch_input -- dispatch input from ** commands in selectionmode
+## $args - input argument
+## returns true value if processing is continued, false otherwise
+sub selection_dispatch_input {
+ my ($args) = @_;
+ if ($args eq '+') {
+ ++$ACT_STR->[CUR][0];
+ $ACT_STR->[CUR][0] = -1 if $ACT_STR->[CUR][0] >= @{$ACT_STR->[LINES]};
+ }
+ elsif ($args eq '-') {
+ --$ACT_STR->[CUR][0];
+ $ACT_STR->[CUR][0] = @{$ACT_STR->[LINES]}-1 if $ACT_STR->[CUR][0] < -1;
+ }
+ elsif ($args eq '>') {
+ ++$ACT_STR->[CUR][1];
+ if ($ACT_STR->[CUR][0] < 0 && $ACT_STR->[CUR][1] < 0) {
+ $ACT_STR->[CUR][1] = -1;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen if $ACT_STR->[CUR][1] > $msglen;
+ }
+ }
+ elsif ($args eq '<') {
+ --$ACT_STR->[CUR][1];
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ elsif ($ACT_STR->[CUR][1] < 0) {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen-1 if $ACT_STR->[CUR][1] > $msglen;
+ }
+ }
+ elsif ($args eq 'f') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ $ACT_STR->[CUR][1] = (grep { $_ > $ACT_STR->[CUR][1] } @breaks)[0];
+ unless (defined $ACT_STR->[CUR][1]) {
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen;
+ }
+ }
+ }
+ elsif ($args eq 'b') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ $ACT_STR->[CUR][1] = (grep { $_ < $ACT_STR->[CUR][1] } @breaks)[-1];
+ unless (defined $ACT_STR->[CUR][1]) {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ }
+ }
+ elsif ($args eq 'e') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen;
+ }
+ }
+ elsif ($args eq 'a') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ }
+ elsif ($args eq '@') {
+ if ($ACT_STR->[CUR][2] > -1) {
+ @{$ACT_STR->[CUR]}[2,3] = (-1, -1);
+ }
+ else {
+ @{$ACT_STR->[CUR]}[2,3] = @{$ACT_STR->[CUR]}[0,1];
+ }
+ }
+ else {
+ return;
+ }
+ if ($args =~ /^[><+fbea-]$/) {
+ selection_to_clip();
+ }
+ 1
+}
+
+## selection_replay_codes -- insert marker codes, reinsert control codes into plain msg
+## $line - line with trace data to adjust
+## $sel_s - start position of selection
+## $msg_r - reference to message where positions apply
+## $sel_e - end position of selection
+## $ctrl - capture_color_codes result of control codes
+## $cup - cursor position
+## $fx - debug file handle
+sub selection_replay_codes {
+ my ($line, $sel_s, $msg_r, $sel_e, $ctrl, $cup, $fx) = @_;
+ my @cup = grep { $_ > -1 } ($cup);
+
+ my $se = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.selection');
+
+ my $cu = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.selection_cursor');
+
+ my @starts = grep { $_ > -1 } sort { $a <=> $b } ($sel_s, $cup);
+ my @ends = grep { $_ > -1 } sort { $a <=> $b } ($sel_e, $cup+1);
+
+ my $last_seq_s;
+
+ print $fx "s<@starts> e<@ends> sel_s:$sel_s sel_e:$sel_e cup:$cup\n" if $fx;
+
+ my $max_loop = 2*( @{$ctrl->[POS_S]} + @starts + @ends );
+ while (@{$ctrl->[POS_S]} || @starts || @ends) {
+ #if (@starts && $starts[0] <= $ends[0] && (!@{$ctrl->[POS_S]} || $starts[0] <= $ctrl->[POS_S][0])) #urlonly
+ if (@starts && $starts[0] < $ends[0] && (!@{$ctrl->[POS_S]} || $starts[0] <= $ctrl->[POS_S][0])) {
+ $last_seq_s = $starts[0];
+ hyperlink_replay_code1($line, $msg_r, (@cup && $starts[0] == $cup[0] ? \$cu : \$se), 0,
+ \@starts, \@ends, \@cup, @{$ctrl}[POS_S,POS_E], $fx, 'S');
+ }
+ elsif (@ends && (!@{$ctrl->[POS_S]} || $ends[0] <= $ctrl->[POS_S][0])) {
+ my $active1 = weechat::color('reset');
+ if (defined $last_seq_s) {
+ # get part of message constructed thus far, and remove starting bracket
+ my $msg_part1 = substr $$msg_r, 0, $ends[0];
+ substr $msg_part1, $last_seq_s, length (@cup && $last_seq_s == $cup[0] ? $cu : $se), '';
+ $active1 .= repeat_control_codes($msg_part1);
+ $last_seq_s = undef;
+ }
+ hyperlink_replay_code1($line, $msg_r, \$active1, 1,
+ \@ends, \@starts, \@cup, @{$ctrl}[POS_S,POS_E], $fx, 'E');
+ }
+ else { # ($ctrl->[POS_S][0] <= $starts[0] && $ctrl->[POS_S][0] <= $ends[0])
+ my $ip = $ctrl->[POS_E][0];
+ my $needs_fixup = defined $last_seq_s && $ctrl->[RES][0] =~ weechat::color('reset');
+ hyperlink_replay_code2($ctrl, $msg_r, \@starts, \@ends, \@cup, $fx, 'C');
+ if ($needs_fixup) {
+ $last_seq_s = $ip;
+ hyperlink_replay_code1($line, $msg_r, (@cup && $last_seq_s == $cup[0] ? \$cu : \$se), 0,
+ [$ip], \@starts, \@ends, \@cup, @{$ctrl}[POS_S,POS_E], $fx, 'F');
+ }
+ }
+ --$max_loop; die 'endless loop' if $max_loop < 0;
+ }
+}
+
+## selection_function -- select text with cursor
+## $lines_ref - downloaded lines
+sub selection_function {
+ my ($lines_ref) = @_;
+ $ACT_STR->[CUR] = [ -1, -1, -1, -1 ] unless defined ${$ACT_STR}[CUR];
+ my $cur = $ACT_STR->[CUR];
+ my $lineno = 0;
+ my $fx;
+ #open $fx, '>', ...;
+ print $fx "cur: @$cur\n" if $fx;
+ for my $line (@$lines_ref) {
+ my $msg = fu8on $line->[LINE]{'message'};
+ my ($ctrl_codes, $plain_msg) = capture_color_codes($msg);
+ my ($sel_s, $sel_e) = (-1, -1);
+ my $lcur = $cur->[0*2] == $lineno ? $cur->[0*2+1] : -2;
+ my $msglen = length $plain_msg;
+ $lcur = -1+$msglen if $lcur >= $msglen;
+ if ($cur->[0*2] > -1 && $cur->[1*2] > -1) { # we have a selection
+ if ($cur->[0*2] < $cur->[1*2]) { # cursor is on line before selection
+ if ($cur->[0*2] == $lineno) {
+ #($sel_s, $sel_e) = ($cur->[0*2+1]+1, $msglen);
+ ($sel_s, $sel_e) = ($lcur+1, $msglen);
+ }
+ elsif ($cur->[0*2] < $lineno && $cur->[1*2] > $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $msglen);
+ }
+ elsif ($cur->[1*2] == $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $cur->[1*2+1]);
+ }
+ }
+ elsif ($cur->[0*2] > $cur->[1*2]) { # cursor is on line after selection
+ if ($cur->[0*2] == $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $cur->[0*2+1]);
+ }
+ elsif ($cur->[0*2] > $lineno && $cur->[1*2] < $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $msglen);
+ }
+ elsif ($cur->[1*2] == $lineno) {
+ ($sel_s, $sel_e) = ($cur->[1*2+1], $msglen);
+ }
+ }
+ elsif ($cur->[0*2] == $lineno && $cur->[1*2] == $lineno) { # cursor is on same line as selection
+ if ($cur->[0*2+1] < $cur->[1*2+1]) {
+ ($sel_s, $sel_e) = ($cur->[0*2+1]+1, $cur->[1*2+1]);
+ }
+ elsif ($cur->[0*2+1] > $cur->[1*2+1]) {
+ ($sel_s, $sel_e) = ($cur->[1*2+1], $cur->[0*2+1]);
+ }
+ else {
+ ($sel_s, $sel_e) = ($cur->[0*2+1], $cur->[0*2+1]+1);
+ $lcur = -2;
+ }
+ }
+ }
+ if ($sel_s && $sel_s == 0) {
+ $sel_s+=0;
+ my $hl = weechat::color('reverse');
+ my $HL = weechat::color('-reverse');
+ if ($line->[LINE]{'prefix'}) {
+ my $ctrl = capture_color_codes(fu8on $line->[LINE]{'prefix'});
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-2], 0, $hl;
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-1]+length $hl, 0, $HL;
+ }
+ }
+ $sel_e = $msglen if $sel_e > $msglen;
+ ($sel_s, $sel_e) = (-1, -1)
+ if $sel_s == $sel_e;
+ trace_to_u8($line);
+ selection_replay_codes($line, $sel_s, \$plain_msg, $sel_e, $ctrl_codes, $lcur, $fx);
+ $line->[LINE]{'message'} = $plain_msg;
+ ++$lineno;
+ }
+}
+
+## switchmode -- toggle between modes
+sub switchmode {
+ $ACT_STR->[MODE] = $ACT_STR->[MODE] eq 'hyperlink' ? 'selection' : 'hyperlink';
+ my ($r_, $R_) = (weechat::color('reverse'), weechat::color('-reverse'));
+ my $I = '▐';
+ my $t_flt = hyper_get_valid_keys('t');
+ $t_flt =~ s/$_/$_/i for split '', $ACT_STR->[URL_TYPE_FILTER];
+ weechat::buffer_set($ACT_STR->[BUFPTR], 'title',
+ ($ACT_STR->[MODE] eq 'hyperlink' ?
+ $r_.' ↑ ↓'.$R_.'move to url'.
+ $I.$r_.'RET' .$I.$R_.'send open'.
+ $I.$r_.'/' .$I.$R_.'sel.mode'.
+ $I.$r_.$t_flt.$I.$R_.
+ $I.$r_.'q' .$I.$R_.'close'
+ :
+ $r_.'←↑→↓'.$R_.'move cursor'.
+ $I.$r_.'SPC' .$I.$R_.'start selection'.
+ $I.$r_.'/' .$I.$R_.'url mode'.
+ $I.$r_.'q' .$I.$R_.'close').' '.
+ $r_.$I.$R_.
+ weechat::buffer_get_string($ACT_STR->[WINDOWS]{'buffer'}, 'short_name').
+ $I);
+}
+
+## apply_keybindings -- set up key bindings for copy buffer
+sub apply_keybindings {
+ my @wee_keys = Nlib::i2h('key');
+ my @keys;
+ my $custom_keys = weechat::config_get_plugin('copywin_custom_keys');
+ my %custom_keys;
+ if ($custom_keys) {
+ %custom_keys = ('' => split /(\S+):/, $custom_keys);
+ for (keys %custom_keys) {
+ $custom_keys{$_} = [ grep { length } split ' ', $custom_keys{$_} ];
+ }
+ }
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input history_previous' ||
+ $_->{'command'} eq '/input history_global_previous' } @wee_keys;
+ @keys = 'meta2-A' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **-') for @keys, @{$custom_keys{'-'}//[]}; # up arrow
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input history_next' ||
+ $_->{'command'} eq '/input history_global_next' } @wee_keys;
+ @keys = 'meta2-B' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **+') for @keys, @{$custom_keys{'+'}//[]}; # down arrow
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_next_char' } @wee_keys;
+ @keys = 'meta2-C' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **>') for @keys, @{$custom_keys{'>'}//[]}; # right arrow
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_previous_char' } @wee_keys;
+ @keys = 'meta2-D' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **<') for @keys, @{$custom_keys{'<'}//[]}; # left arrow
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_next_word' } @wee_keys;
+ @keys = 'meta-f' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **f') for @keys, @{$custom_keys{f}//[]}; # back word
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_previous_word' } @wee_keys;
+ @keys = 'meta-b' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **b') for @keys, @{$custom_keys{b}//[]}; # forward word
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_end_of_line' } @wee_keys;
+ @keys = 'ctrl-E' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **e') for @keys, @{$custom_keys{e}//[]};
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_beginning_of_line' } @wee_keys;
+ @keys = 'ctrl-A' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **a') for @keys, @{$custom_keys{a}//[]};
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input return' || $_->{'command'} eq '/input magic_enter' } @wee_keys;
+ @keys = 'ctrl-M' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **!') for @keys, @{$custom_keys{'!'}//[]}; # enter key
+
+ @keys = ('ctrl-@', ' ');
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **@') for @keys, @{$custom_keys{'@'}//[]}; # ctrl+space or ctrl+@
+
+ for my $cmd (@SIMPLE_MODE_KEYS) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **'.$cmd) for $cmd, @{$custom_keys{$cmd}//[]};
+ }
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} =~ "^/@{[CMD_COPYWIN]}" } @wee_keys;
+ push @keys, 'q' unless @{$custom_keys{q}//[]};
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **q') for @keys, @{$custom_keys{q}//[]};
+}
+
+## binding_mouse_fix -- disable one key bindings on mouse input
+## () - signal handler
+## $data - signal has true value if mouse input in progress, false if mouse input finished
+sub binding_mouse_fix {
+ my (undef, undef, $data) = @_;
+ return weechat::WEECHAT_RC_OK unless $ACT_STR && $ACT_STR->[BUFPTR] && weechat::current_buffer() eq $ACT_STR->[BUFPTR];
+ my $custom_keys = weechat::config_get_plugin('copywin_custom_keys');
+ my %custom_keys;
+ if ($custom_keys) {
+ %custom_keys = ('' => split /(\S+):/, $custom_keys);
+ for (keys %custom_keys) {
+ $custom_keys{$_} = [ grep { length } split ' ', $custom_keys{$_} ];
+ }
+ }
+ if ($data) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_unbind_$_", '') for ' ', @SIMPLE_MODE_KEYS, 'q',
+ grep { 1 == length } map { @$_ } values %custom_keys;
+ }
+ else {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **@') for ' ';
+ weechat::buffer_set($ACT_STR->[BUFPTR], 'key_bind_/', '/'.CMD_COPYWIN.' **/');
+ for my $cmd (@SIMPLE_MODE_KEYS) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **'.$cmd) for $cmd, grep { 1 == length } @{$custom_keys{$cmd}//[]};
+ }
+ for my $cmd (split //, '@!aebf<>+-/') {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **'.$cmd) for grep { 1 == length } @{$custom_keys{$cmd}//[]};
+ }
+ if (@{$custom_keys{q}//[]}) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **q') for @{$custom_keys{q}//[]};
+ }
+ else {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_q", '/'.CMD_COPYWIN.' **q');
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## hyper_get_valid_keys -- get keys for type filter according to enabled settings
+## $res - 't' for title, 'u' for upcase
+sub hyper_get_valid_keys {
+ my ($res) = @_;
+ $res = '' unless defined $res;
+ my $title = $res eq 't';
+ my $uc = $res eq 'u';
+ my $keys = 'u';
+ $keys .= 'n' if Nlib::has_true_value(weechat::config_get_plugin('hyper_nicks'));
+ $keys .= 'c' if Nlib::has_true_value(weechat::config_get_plugin('hyper_channels'));
+ $keys .= 'p' if Nlib::has_true_value(weechat::config_get_plugin('hyper_prefix'));
+ $keys = uc $keys if $title || $uc;
+ if ($title) {
+ length $keys == 1 ? " $keys " :
+ length $keys == 2 ? (substr $keys, 0, 1).' '.(substr $keys, 1).' ' :
+ length $keys == 3 ? "$keys " :
+ $keys;
+ }
+ else {
+ $keys = "[$keys]";
+ qr/^$keys$/;
+ }
+}
+
+## hyper_set_type_filter -- sets the type of links shown in url view based on setting and args
+## $args - command line arguments
+## returns new args
+sub hyper_set_type_filter {
+ my ($args) = @_;
+ my $valid_keys = hyper_get_valid_keys();
+ $ACT_STR->[URL_TYPE_FILTER] = join '',
+ keys %{+{ map { $_ => undef } grep { /$valid_keys/ } map { lc substr $_, 0, 1 } map { split /[,;]/ } split ' ',
+ weechat::config_get_plugin('hyper_show') }};
+ $args = '' unless defined $args;
+ my $urlfilter = $args;
+ if ($urlfilter =~ s/^\/// && length $urlfilter) {
+ $args = '/';
+ $ACT_STR->[URL_TYPE_FILTER] = join '',
+ keys %{+{ map { $_ => undef } grep { /$valid_keys/ } map { lc } split '', $urlfilter }};
+ }
+ elsif (length $urlfilter) {
+ $ACT_STR->[URL_TYPE_FILTER] = join '',
+ keys %{+{ map { $_ => undef } grep { /$valid_keys/ } map { lc substr $_, 0, 1 }
+ map { split /[,;]/ } split ' ', $urlfilter }};
+ $args = '/' if length $ACT_STR->[URL_TYPE_FILTER];
+ }
+ $ACT_STR->[URL_TYPE_FILTER] = 'u' unless length $ACT_STR->[URL_TYPE_FILTER];
+ $args
+}
+
+## make_new_copybuf -- creates a new copywin buffer from the current window
+## $args - arguments to command, can set mode
+sub make_new_copybuf {
+ my ($args) = @_;
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+
+ my $copybuf = weechat::buffer_new('['.$wininfo->{'width'}.'x'.$wininfo->{'height'}.
+ '+'.$wininfo->{'x'}.'+'.$wininfo->{'y'}.']',
+ '', '', '', '');
+ # apply scroll keeping to current buffer if possible
+ weechat::hook_signal_send('buffer_sk', weechat::WEECHAT_HOOK_SIGNAL_POINTER, $copybuf);
+ $STR{$copybuf} = $ACT_STR = [ $copybuf ];
+ $ACT_STR->[LINES] = download_lines($wininfo);
+ $ACT_STR->[WINDOWS] = $wininfo;
+ unless ($copybuf) {
+ $ACT_STR->[MODE] = 'FAIL';
+ return;
+ }
+ $ACT_STR->[MODE] = 'hyperlink';
+ $args = hyper_set_type_filter($args);
+ weechat::buffer_set($copybuf, 'short_name', weechat::config_get_plugin('copybuf_short_name')) if $copybuf;
+ weechat::buffer_set($copybuf, 'type', 'free');
+ apply_keybindings();
+ switchmode();
+ switchmode() if $args eq '/';
+}
+
+sub copywin_cmd {
+ my (undef, $bufptr, $args, $int) = @_;
+
+ if ($int) {
+ }
+ elsif ($args =~ s/^\*\*//) {
+ return weechat::WEECHAT_RC_OK unless exists $STR{$bufptr};
+ $ACT_STR = $STR{$bufptr};
+ if ($args eq 'q') {
+ weechat::buffer_close($ACT_STR->[BUFPTR]);
+ return weechat::WEECHAT_RC_OK;
+ }
+ elsif ($args eq '/') {
+ hyper_set_type_filter()
+ unless length $ACT_STR->[URL_TYPE_FILTER];
+ switchmode();
+ }
+ elsif ($args =~ hyper_get_valid_keys()) {
+ switchmode() if $ACT_STR->[MODE] eq 'hyperlink';
+ $ACT_STR->[URL_TYPE_FILTER] = $args;
+ switchmode();
+ }
+ elsif ($args =~ hyper_get_valid_keys('u')) {
+ switchmode() if $ACT_STR->[MODE] eq 'hyperlink';
+ my $t = lc $args;
+ $ACT_STR->[URL_TYPE_FILTER] .= $t
+ unless $ACT_STR->[URL_TYPE_FILTER] =~ s/$t//;
+ switchmode() if length $ACT_STR->[URL_TYPE_FILTER];
+ }
+ elsif ($ACT_STR->[MODE] eq 'hyperlink') {
+ return weechat::WEECHAT_RC_OK
+ unless hyperlink_dispatch_input($args);
+ }
+ else {
+ return weechat::WEECHAT_RC_OK
+ unless selection_dispatch_input($args);
+ }
+ }
+ elsif ($args =~ /^\s*help\s*$/i) {
+ Nlib::read_manpage(SCRIPT_FILE, SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK
+ }
+ else {
+ check_layout()
+ unless $LAYOUT_OK;
+ make_new_copybuf($args);
+ }
+
+ my $wininfo = $ACT_STR->[WINDOWS];
+ my $lines_ref2 = copy_lines($ACT_STR->[LINES]);
+ my $copybuf = $ACT_STR->[BUFPTR];
+
+ if ($ACT_STR->[MODE] eq 'hyperlink') {
+ hyperlink_function($lines_ref2);
+ }
+ elsif ($ACT_STR->[MODE] eq 'selection') {
+ selection_function($lines_ref2);
+ }
+
+
+ my $printy = calc_free_image($wininfo, $lines_ref2);
+
+ for my $i (0..$#$printy) {
+ weechat::print_y($copybuf, $i, $printy->[$i]);
+ }
+
+ weechat::buffer_set($copybuf, 'display', 'auto');
+ weechat::WEECHAT_RC_OK
+}
+
+sub real_screen {
+ my ($screen_cursor, $text) = @_;
+ my $l = length $text;
+ for (my ($i, $j) = (0, 0); $i < $l; ++$i) {
+ $j += screen_length(substr $text, $i, 1);
+ return $i if $j > $screen_cursor;
+ }
+ $l
+}
+
+## mouse_coords_to_cursor -- calculate in line cursor according to rendering
+## $r - local (in window) row of mouse cursor
+## $c - local (in window) column of mouse cursor
+## returns line index and position in plain text if found
+sub mouse_coords_to_cursor {
+ my ($r, $c) = @_; # wanted row & column
+
+ my $wininfo = $ACT_STR->[WINDOWS];
+ my $lines_ref = copy_lines($ACT_STR->[LINES]);
+
+ my ($i, $l, $p) = (1, 0, 0); # current row, current line index, current line position
+ my $first_line = $wininfo->{'start_line_pos'};
+ my $prev_line;
+ for my $line (@$lines_ref) {
+ trace_to_u8($line);
+ my $max_length = [(length fu8on $line->[LINE]{'message'}),''];
+ my @splits = ([0,''], message_splits($line), $max_length, $max_length);
+ for (0 .. $line->[LAYOUT]{'count'}-1) {
+ shift @splits if $splits[0][0] - $splits[1][0] >= 0;
+ last if $i > $wininfo->{'chat_height'};
+ my $subm = substr $line->[LINE]{'message'}, $splits[0][0], $splits[1][0]-$splits[0][0];
+ my $subm_u = fu8on weechat::string_remove_color($subm, '');
+ my $subm_length = length $subm_u;
+ if ($_ >= $first_line) {
+ if ($r == $i) {
+ my $subm_length_screen = screen_length $subm_u;
+ my $construction = '';
+ unless ($_) {
+ show_time($line, \$construction);
+ return ($l, undef, 'time')
+ if (screen_length fu8on weechat::string_remove_color($construction, '')) >= $c;
+ show_buffername($line, \$construction);
+ return ($l, undef, 'buffername')
+ if (screen_length fu8on weechat::string_remove_color($construction, '')) >= $c;
+ show_prefix($line, \$construction, $prev_line);
+ return ($l, undef, 'prefix')
+ if (screen_length fu8on weechat::string_remove_color($construction, '')) >= $c;
+ $prev_line = $line if exists $line->[LINE]{'date'};
+ }
+ show_separator($line, \$construction, $_);
+ my $message_start_screen = screen_length fu8on weechat::string_remove_color($construction, '');
+ return ($l) if $message_start_screen >= $c;
+ if ($splits[0][1] =~ /[bn]/ && $subm =~ s/^( +)//) {
+ my $l = length $1;
+ $p += $l;
+ $subm_u = substr $subm_u, $l;
+ }
+ if ($subm_length_screen >= $c - 1 - $message_start_screen) {
+ return ($l, $p + real_screen($c - 1 - $message_start_screen, $subm_u));
+ }
+ }
+ ++$i;
+ }
+ $p += $subm_length;
+ shift @splits;
+ }
+ $first_line = 0;
+ ++$l; $p = 0;
+ }
+}
+
+## copywin_autoclose -- close copywin buffer
+## () - this small function is a timer handler
+sub copywin_autoclose {
+ weechat::buffer_close($ACT_STR->[BUFPTR])
+ if $ACT_STR && $ACT_STR->[MOUSE_AUTOMODE];
+ $autoclose_in_progress = undef;
+ weechat::WEECHAT_RC_OK
+}
+
+## get_autoclose_delay -- checks delay for autoclose of copywin buffer
+## returns delay or false if autoclose is disabled
+sub get_autoclose_delay {
+ my $autoclose_delay = weechat::config_get_plugin('mouse.close_on_release');
+ if (Nlib::has_false_value($autoclose_delay)) { 0 }
+ elsif ($autoclose_delay =~ /\D/ || $autoclose_delay < 100) { 100 }
+ else { $autoclose_delay }
+}
+
+## get_2nd_click_delay -- checks additional delay for autoclose of copywin buffer if 2nd click is needed to open url
+## returns additional delay
+sub get_2nd_click_delay {
+ my $conf_2nd_click_delay = weechat::config_get_plugin('mouse.url_open_2nd_click');
+ if (Nlib::has_false_value($conf_2nd_click_delay)) { 0 }
+ elsif ($conf_2nd_click_delay =~ /\D/) { 2000 }
+ else { $conf_2nd_click_delay }
+}
+
+## get_nick_2nd_click_delay -- checks additional delay for autoclose of copywin buffer if 2nd click is needed to query nick
+## returns additional delay
+sub get_nick_2nd_click_delay {
+ my $conf_2nd_click_delay = weechat::config_get_plugin('mouse.nick_2nd_click');
+ if (Nlib::has_false_value($conf_2nd_click_delay)) { 0 }
+ elsif ($conf_2nd_click_delay =~ /\D/) { 500 }
+ else { $conf_2nd_click_delay }
+}
+
+## mouse_window_at_pointer -- switch to window where clicked
+## $row - mouse row
+## $col - mouse column
+## $wininfo - currently active window info
+## returns true value if successful
+sub mouse_window_at_pointer {
+ my ($row, $col, $wininfo) = @_;
+ my @all_windows = Nlib::i2h('window');
+ my $in_any_win;
+ for (@all_windows) {
+ $in_any_win = $_ if Nlib::in_window($row, $col, $_);
+ }
+ return unless $in_any_win;
+ my $steps = 0;
+ for (@all_windows) {
+ weechat::command('', '/window +1');
+ ++$steps;
+ last if weechat::current_window() eq $in_any_win->{'pointer'};
+ }
+ my ($act_win_ind) = map { $_->[0] }
+ grep { $_->[-1]{'pointer'} eq $wininfo->{'pointer'} }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @all_windows
+ };
+ my ($click_win_ind) = map { $_->[0] }
+ grep { $_->[-1]{'pointer'} eq $in_any_win->{'pointer'} }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @all_windows
+ };
+ #weechat::print('',"steps:$steps, a:$act_win_ind, c:$click_win_ind, d:@{[$act_win_ind-$click_win_ind]}/@{[$click_win_ind-$act_win_ind]} #:".scalar @all_windows);
+ 1
+}
+
+## mouse_scroll_action -- handle mouse scroll
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## $row - mouse row
+## $col - mouse column
+sub mouse_scroll_action {
+ my (undef, undef, undef, $row, $col) = @_;
+ return weechat::WEECHAT_RC_OK unless Nlib::has_true_value(weechat::config_get_plugin('mouse.handle_scroll'));
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+
+ my $had_to_switch_win;
+
+ unless (Nlib::in_window($row, $col, $wininfo)) {
+ if (Nlib::has_true_value(weechat::config_get_plugin('mouse.scroll_inactive_pane'))) {
+ return weechat::WEECHAT_RC_OK
+ unless mouse_window_at_pointer($row, $col, $wininfo);
+ $had_to_switch_win = $winptr;
+ $winptr = weechat::current_window();
+ ($wininfo) = Nlib::i2h('window', $winptr);
+ }
+ else {
+ if ($_[2] =~ /^`/) { weechat::command('', '/window scroll_up'); } #`
+ elsif ($_[2] =~ /^a/) { weechat::command('', '/window scroll_down'); }
+ }
+ }
+
+ if (Nlib::in_window($row, $col, $wininfo)) {
+ if (Nlib::in_chat_window($row, $col, $wininfo)) {
+ if ($_[2] =~ /^`/) { weechat::command('', '/window scroll_up'); } #`
+ elsif ($_[2] =~ /^a/) { weechat::command('', '/window scroll_down'); }
+ }
+ elsif (my $bar_infos = Nlib::find_bar_window($row, $col)) {
+ my $dir = $bar_infos->[-1]{'filling_'.
+ ($bar_infos->[-1]{'position'} <= 1 ?
+ 'top_bottom' : 'left_right')} ? 'y' : 'x';
+ if ($_[2] =~ /^`/) { $dir .= '-' } #`
+ elsif ($_[2] =~ /^a/) { $dir .= '+' }
+ for ($bar_infos->[-1]{'name'}) {
+ weechat::command('', '/bar scroll '.$_.' * '.$dir.'10%')
+ if ($_ eq 'title' or $_ eq 'status' or $_ eq 'nicklist')
+ }
+ }
+ }
+
+ if ($had_to_switch_win) {
+ my $steps = 0;
+ for (Nlib::i2h('window')) {
+ weechat::command('', '/window +1');
+ ++$steps;
+ last if weechat::current_window() eq $had_to_switch_win;
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## drag_speed_hack -- delay drag events
+## () - timer handler
+sub drag_speed_hack {
+ $drag_speed_timer = undef;
+ mouse_evt(undef, undef, $_[0]);
+ weechat::WEECHAT_RC_OK
+}
+
+sub input_text_hlsel {
+ my (undef, undef, $bufptr, $text) = @_;
+ return $text unless defined $input_sel && $input_sel->[0] eq $bufptr;
+ my ($ctrl_codes, $plain_msg) = capture_color_codes($text);
+ my $npos = weechat::buffer_get_integer($bufptr, 'input_pos');
+ my ($sel_s, $sel_e) = sort { $a <=> $b } ($npos, $input_sel->[-1]);
+ return $text unless $sel_s < $sel_e;
+ selection_replay_codes([[]], $sel_s, \$plain_msg, $sel_e, $ctrl_codes, -2);
+ $plain_msg
+}
+
+sub window_of_bar {
+ my ($barinfo) = @_;
+ my @all_windows = Nlib::i2h('window');
+ my $in_any_win;
+ for (@all_windows) {
+ $in_any_win = $_ if Nlib::in_window(1+$barinfo->[0]{'y'}, 1+$barinfo->[0]{'x'}, $_);
+ }
+ $in_any_win
+}
+
+## mouse_input_sel -- mouse select in input bar
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## $row - mouse row
+## $col - mouse column
+sub mouse_input_sel {
+ my (undef, undef, undef, $row, $col) = @_;
+ my $plain;
+ if (my $bar_infos = Nlib::find_bar_window($row, $col)) {
+ if ($bar_infos->[-1]{'name'} eq 'input') {
+ my $win = window_of_bar($bar_infos);
+ my ($error, $idx, $content, $coords) = Nlib::bar_item_get_subitem_at
+ ($bar_infos, 'input_text', $col - $bar_infos->[0]{'x'}, $row - $bar_infos->[0]{'y'});
+ if ($coords) {
+ my $col_pos = $coords->[1]-$coords->[0] + ($coords->[4]-$coords->[3])*$bar_infos->[0]{'width'};
+ $plain = fu8on weechat::string_remove_color($content, '');
+ $input_sel = [ $win->{'buffer'}, real_screen($col_pos, $plain) ];
+ }
+ elsif ($error eq 'outside') {
+ $plain = fu8on weechat::string_remove_color($content, '');
+ $input_sel = [ $win->{'buffer'}, length $plain ];
+ }
+ }
+ }
+ if (defined $plain) {
+ if ($_[2] =~ /^ /) {
+ weechat::buffer_set($input_sel->[0], 'input_pos', $input_sel->[-1]);
+ }
+ else {
+ my $npos = weechat::buffer_get_integer($input_sel->[0], 'input_pos');
+ my ($sel_s, $sel_e) = sort { $a <=> $b } ($npos, $input_sel->[-1]);
+ send_clip((substr $plain, $sel_s, $sel_e-$sel_s))
+ if $sel_s < $sel_e;
+ }
+ }
+ $input_sel = undef if $_[2] =~ /^#/;
+}
+
+## mouse_click_into_copy -- click when not in copy mode
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## $row - mouse row
+## $col - mouse column
+## returns true value if copy mode should be turned on
+sub mouse_click_into_copy {
+ my (undef, undef, undef, $row, $col) = @_;
+
+ mouse_input_sel(@_[0..2], $row, $col);
+ return unless $_[2] =~ /^ /;
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+
+ unless (Nlib::in_window($row, $col, $wininfo)) {
+ return unless Nlib::has_true_value(weechat::config_get_plugin('mouse.click_select_pane'));
+ return unless mouse_window_at_pointer($row, $col, $wininfo);
+ $winptr = weechat::current_window();
+ ($wininfo) = Nlib::i2h('window', $winptr);
+ return unless Nlib::has_true_value(weechat::config_get_plugin('mouse.click_through_pane'));
+ }
+ return unless Nlib::has_true_value(weechat::config_get_plugin('mouse.copy_on_click'));
+ return unless Nlib::in_chat_window($row, $col, $wininfo);
+ 1
+}
+
+## mouse_delay_drag -- delay drag events
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## returns true if this event should be delayed
+sub mouse_delay_drag {
+ my $drag_speed = weechat::config_get_plugin('mouse.drag_speed');
+ unless (Nlib::has_false_value($drag_speed)) {
+ $drag_speed = 50 if $drag_speed =~ /\D/;
+ weechat::unhook($drag_speed_timer) if defined $drag_speed_timer;
+ $drag_speed_timer = weechat::hook_timer($drag_speed, 0, 1, 'drag_speed_hack', $_[2]);
+ return 1;
+ }
+ undef
+}
+
+## delayed_nick_menu -- open the nick menu
+## () - timer handler
+sub delayed_nick_menu {
+ $delayed_nick_menu_timer = undef;
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/menu nick $_[0]")
+ if $ACT_STR && Nlib::i2h('hook', '', 'command,menu');
+ weechat::WEECHAT_RC_OK
+}
+
+## mouse_evt -- handle mouse clicks
+## () - signal handler
+## $_[2] - mouse code
+sub mouse_evt {
+ Encode::_utf8_on($_[2]);
+
+ my $this_last_mouse_seq = $last_mouse_seq || '';
+ if ($_[1] && $this_last_mouse_seq =~ /^@/ && $_[2] =~ /^@/) {
+ return weechat::WEECHAT_RC_OK if mouse_delay_drag(@_[0..2])
+ }
+ $last_mouse_seq = $_[2];
+
+ #return weechat::WEECHAT_RC_OK unless defined $ACT_STR && $ACT_STR->[BUFPTR] eq weechat::current_buffer();
+
+ my $curbufptr = weechat::current_buffer();
+
+ if ($_[2] =~ /^[#@ `a](.)(.)$/) {
+ my $row = ord($2)-32;
+ my $col = ord($1)-32;
+
+ weechat::unhook($delayed_nick_menu_timer) if defined $delayed_nick_menu_timer;
+ $delayed_nick_menu_timer = undef;
+
+ return mouse_scroll_action(@_[0..2], $row, $col) if $_[2] =~ /^[`a]/;
+
+ if (!defined $ACT_STR || $ACT_STR->[BUFPTR] ne $curbufptr) {
+ return weechat::WEECHAT_RC_OK
+ unless mouse_click_into_copy(@_[0..2], $row, $col);
+ copywin_cmd(undef, $curbufptr, '/');
+ return weechat::WEECHAT_RC_OK unless $ACT_STR->[BUFPTR] eq weechat::current_buffer();
+ $ACT_STR->[MOUSE_AUTOMODE] = 1;
+ }
+ $ACT_STR->[CUR] = [ -1, -1, -1, -1 ] unless defined ${$ACT_STR}[CUR];
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+ return weechat::WEECHAT_RC_OK unless Nlib::in_chat_window($row, $col, $wininfo);
+
+ my $lrow = $row - $wininfo->{'chat_y'};
+ my $lcol = $col - $wininfo->{'chat_x'};
+ #weechat::print_y($ACT_STR->[BUFPTR], $lrow-1, ' 'x($lcol-1).'x'.$lrow.'/'.$lcol);
+ my @cursor = mouse_coords_to_cursor($lrow, $lcol);
+ #weechat::print($ACT_STR->[WINDOWS]{'buffer'}, 'cursor: '.join ',', map { defined $_ ? $_ : '?' } @cursor );
+
+ my $one_click;
+ if (@cursor == 3 && $cursor[2] eq 'prefix' &&
+ $this_last_mouse_seq =~ /^ / && $_[2] =~ /^#/ &&
+ ((substr $this_last_mouse_seq, 1) eq (substr $_[2], 1))) { # click
+
+ my @link = grep { $_->[-1][URL_LINE] == $cursor[LINE] &&
+ $_->[-1][URL_S] == -1 && $_->[-1][URL_E] == -1 }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @{$ACT_STR->[URLS]}
+ };
+
+ if (@link == 1) {
+ $ACT_STR->[A_LINK] = $link[0][0];
+ my $t = substr $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'}, 0, 1;
+ unless ($ACT_STR->[URL_TYPE_FILTER] =~ $t) {
+ switchmode();
+ $ACT_STR->[URL_TYPE_FILTER] = $t;
+ switchmode();
+ }
+ }
+ if ($ACT_STR->[MODE] eq 'hyperlink' && @link == 1) {
+ hyperlink_to_clip();
+
+ my ($nick) = grep { /^nick_/ } @{ $ACT_STR->[LINES][$cursor[LINE]][LINE]{'tag'} };
+ if (defined $nick && $nick =~ s/^nick_//) {
+ if (Nlib::has_false_value(weechat::config_get_plugin('mouse.nick_2nd_click'))) {
+ delayed_nick_menu($nick);
+ }
+ elsif ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'nick' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] eq $ACT_STR->[A_LINK]) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/query $nick");
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'nick', $ACT_STR, $ACT_STR->[A_LINK] ];
+ $delayed_nick_menu_timer = weechat::hook_timer(get_nick_2nd_click_delay(), 0, 1, 'delayed_nick_menu', $nick);
+ }
+ else {
+ delayed_nick_menu($nick);
+ }
+ }
+ }
+ copywin_cmd(undef, $ACT_STR->[BUFPTR], '**', 1);
+ }
+
+ unless (@cursor == 2) { # no valid text here
+ $mouse_2nd_click = $one_click if $_[2] =~ /^#/;
+ my $autoclose_delay = get_autoclose_delay();
+ $autoclose_delay += get_nick_2nd_click_delay() if $one_click;
+ $autoclose_in_progress = weechat::hook_timer($autoclose_delay, 0, 1, 'copywin_autoclose', '')
+ if $autoclose_delay && $ACT_STR->[MOUSE_AUTOMODE] && $_[2] =~ /^#/ && !$autoclose_in_progress;
+ return weechat::WEECHAT_RC_OK;
+ }
+
+ if ($_[2] =~ /^ /) { # button down
+ @{$ACT_STR->[CUR]}[2,3] = (-1, -1);
+ weechat::unhook($autoclose_in_progress) if $autoclose_in_progress;
+ $autoclose_in_progress = undef;
+ }
+ elsif ($this_last_mouse_seq =~ /^ / &&
+ ($_[2] =~ /^@/ ||
+ ($_[2] =~ /^#/ && ((substr $this_last_mouse_seq, 1) ne (substr $_[2], 1)))
+ )) { # switch to drag
+ switchmode() if $ACT_STR->[MOUSE_AUTOMODE] && $ACT_STR->[MODE] eq 'hyperlink';
+ selection_dispatch_input('@');
+ }
+ if ($this_last_mouse_seq =~ /^ / && $_[2] =~ /^#/ &&
+ ((substr $this_last_mouse_seq, 1) eq (substr $_[2], 1))) { # click
+ if ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'sel' &&
+ $mouse_2nd_click->[1] == $ACT_STR) {
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'sel', $ACT_STR, [ @cursor ] ];
+ }
+ }
+ @{$ACT_STR->[CUR]}[0,1] = @cursor;
+ if ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'sel' &&
+ $mouse_2nd_click->[1] == $ACT_STR &&
+ $ACT_STR->[MODE] eq 'selection' &&
+ $ACT_STR->[CUR][2] >= 0 && $ACT_STR->[CUR][3] >= 0) {
+ { # fix cursor to word boundary
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ if (($ACT_STR->[CUR][0] == $mouse_2nd_click->[-1][0] && $ACT_STR->[CUR][1] > $mouse_2nd_click->[-1][1])
+ || $ACT_STR->[CUR][0] > $mouse_2nd_click->[-1][0]) { #forward
+ $ACT_STR->[CUR][1] = (grep { $_ >= $ACT_STR->[CUR][1] } @breaks)[0];
+ unless (defined $ACT_STR->[CUR][1]) {
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen;
+ }
+ }
+ else { #backward
+ $ACT_STR->[CUR][1] = (grep { $_ <= $ACT_STR->[CUR][1] } @breaks)[-1];
+ unless (defined $ACT_STR->[CUR][1]) {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ } }
+
+ { # fix selection to word boundary
+ my $msg = $ACT_STR->[LINES][$mouse_2nd_click->[-1][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ if (($mouse_2nd_click->[-1][0] == $ACT_STR->[CUR][0] && $mouse_2nd_click->[-1][1] > $ACT_STR->[CUR][1])
+ || $mouse_2nd_click->[-1][0] > $ACT_STR->[CUR][0]) { #forward
+ $ACT_STR->[CUR][3] = (grep { $_ >= $mouse_2nd_click->[-1][1] } @breaks)[0];
+ unless (defined $ACT_STR->[CUR][3]) {
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][3] = $msglen;
+ }
+ }
+ else { #backward
+ $ACT_STR->[CUR][3] = (grep { $_ <= $mouse_2nd_click->[-1][1] } @breaks)[-1];
+ unless (defined $ACT_STR->[CUR][3]) {
+ $ACT_STR->[CUR][3] = 0;
+ }
+ } }
+ }
+ my @link = grep { $_->[-1][URL_LINE] == $cursor[LINE] &&
+ $_->[-1][URL_S] <= $cursor[1] && $_->[-1][URL_E] > $cursor[1] }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @{$ACT_STR->[URLS]}
+ };
+
+# open my $fx, '>', ...;
+# print $fx "link:@link cur:@cursor\n";
+# map { print $fx $_->[0].'#url_line:'.$_->[-1][URL_LINE].' url_s:'.$_->[-1][URL_S].' url_e:'.$_->[-1][URL_E].' url:'.$_->[-1][URL]."\n" }
+# do {
+# my $i = 0;
+# map { [ $i++, $_ ] } @{$ACT_STR->[URLS]}
+# };
+
+ if (@link == 1) {
+ $ACT_STR->[A_LINK] = $link[0][0];
+ my $t = substr $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'}, 0, 1;
+ unless ($ACT_STR->[URL_TYPE_FILTER] =~ $t) {
+ switchmode();
+ $ACT_STR->[URL_TYPE_FILTER] = $t;
+ switchmode();
+ }
+ }
+ if ($ACT_STR->[MODE] eq 'hyperlink' && @link == 1) {
+ hyperlink_to_clip();
+ if ($this_last_mouse_seq =~ /^ / && $_[2] =~ /^#/ &&
+ ((substr $this_last_mouse_seq, 1) eq (substr $_[2], 1))) { # click
+ my $link_type = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'};
+ if ($link_type eq 'nick') {
+ my $nick = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ if (Nlib::has_false_value(weechat::config_get_plugin('mouse.nick_2nd_click'))) {
+ delayed_nick_menu($nick);
+ }
+ elsif ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'link' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] == $ACT_STR->[A_LINK]) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/query $nick");
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'link', $ACT_STR, $ACT_STR->[A_LINK] ];
+ $delayed_nick_menu_timer = weechat::hook_timer(get_nick_2nd_click_delay(), 0, 1, 'delayed_nick_menu', $nick);
+ }
+ else {
+ delayed_nick_menu($nick);
+ }
+ }
+ elsif ($link_type eq 'channel') {
+ my $channel = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ if ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'link' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] == $ACT_STR->[A_LINK]) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/join $channel");
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'link', $ACT_STR, $ACT_STR->[A_LINK] ];
+ }
+ }
+ else {
+ if (Nlib::has_false_value(weechat::config_get_plugin('mouse.url_open_2nd_click')) ||
+ ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'link' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] == $ACT_STR->[A_LINK])) {
+ hyperlink_dispatch_input('!');
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'link', $ACT_STR, $ACT_STR->[A_LINK] ];
+ }
+ }
+ }
+ }
+ else {
+ selection_to_clip();
+ }
+ copywin_cmd(undef, $ACT_STR->[BUFPTR], '**', 1);
+ if ($_[2] =~ /^#/) { # button up
+ $mouse_2nd_click = $one_click;
+ my $autoclose_delay = get_autoclose_delay();
+ if ($one_click && $one_click->[0] eq 'link') {
+ if ($one_click->[1][URLS][$one_click->[2]][URL_INFO]{'type'} eq 'nick') {
+ $autoclose_delay += get_nick_2nd_click_delay();
+ }
+ else {
+ $autoclose_delay += get_2nd_click_delay();
+ }
+ }
+ $autoclose_in_progress = weechat::hook_timer($autoclose_delay, 0, 1, 'copywin_autoclose', '')
+ if $autoclose_delay && $ACT_STR->[MOUSE_AUTOMODE] && !$autoclose_in_progress;
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+sub hsignal_evt {
+ my %data = %{$_[2]};
+ if ($data{_key} =~ /^(.*)-event-/) {
+ my $msg = "\@chat($data{_buffer_full_name}):$1";
+ for my $k (Nlib::i2h('key', '', 'mouse')) {
+ next if $k->{'key'} =~ /-event/;
+ (my $match = '^'.(quotemeta $k->{'key'})) =~ s/\\\*/.*/g;
+ my $close = $msg =~ $match;
+ last if $close and $k->{'command'} =~ /hsignal:@{[SCRIPT_NAME]}/;
+ return weechat::WEECHAT_RC_OK if $close;
+ }
+ }
+ if ($data{_key} =~ /-event-down/) {
+ mouse_evt(undef, undef, join '', ' ', (pack 'U', 33+$data{_x2}), (pack 'U', 33+$data{_y2}));
+ $hsignal_mouse_down_sent = 1;
+ }
+ elsif ($data{_key} =~ /-event-drag/) {
+ mouse_evt(undef, undef, join '', '@', (pack 'U', 33+$data{_x2}), (pack 'U', 33+$data{_y2}));
+ }
+ else {
+ mouse_evt(undef, undef, join '', ' ', (pack 'U', 33+$data{_x}), (pack 'U', 33+$data{_y}))
+ unless $hsignal_mouse_down_sent;
+ mouse_evt(undef, undef, join '', '#', (pack 'U', 33+$data{_x2}), (pack 'U', 33+$data{_y2}));
+ $hsignal_mouse_down_sent = undef;
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## check_layout -- check if this weechat version knows about layout infolist
+sub check_layout {
+ return weechat::WEECHAT_RC_OK if $LAYOUT_OK;
+# my $winptr = weechat::current_window();
+# my ($wininfo) = Nlib::i2h('window', $winptr);
+# my ($lineinfo) = Nlib::i2h('buffer_lines', @{$wininfo}{'buffer','start_line'});
+# my ($layoutinfo) = Nlib::i2h('layout', $wininfo->{'pointer'}, $lineinfo->{'line'}, $listptr);
+# Nlib::l2l($listptr, 1);
+# unless ($layoutinfo) {
+# weechat::print('', weechat::prefix('error').
+# "You will need to have layout trace support in your WeeChat. Get it at\n".
+# " http://anti.teamidiot.de/static/nei/*/Code/WeeChat/display_traces.diff");
+# }
+# else {
+ $LAYOUT_OK = 1;
+# }
+# if (($ENV{'STY'} || $ENV{'TERM'} =~ /screen/) && !exists $ENV{'TMUX'}) {
+# weechat::print('', weechat::prefix('error').
+# "Your terminal most likely doesn't support the selection clipboard control.");
+# }
+ weechat::WEECHAT_RC_OK
+}
+
+
+## garbage_str -- remove copywin storage when copywin buffer is closed
+## () - signal handler
+## $bufptr - signal comes with pointer of closed buffer
+sub garbage_str {
+ my (undef, undef, $bufptr) = @_;
+ $ACT_STR = undef if $ACT_STR && $ACT_STR->[BUFPTR] eq $bufptr;
+ delete $STR{$bufptr};
+ weechat::WEECHAT_RC_OK
+}
+
+## decouple_mouse_scroll -- add coords script as mouse scrolling handler
+sub decouple_mouse_scroll {
+ my $main = weechat::buffer_search_main();
+ my $mouse_scroll_ext = weechat::buffer_string_replace_local_var($main, '$mousescroll');
+ unless (grep { $_ eq SCRIPT_NAME } split ',', $mouse_scroll_ext) {
+ $script_old_mouse_scroll = $mouse_scroll_ext;
+ if ($mouse_scroll_ext =~ /^\$/) {
+ weechat::buffer_set($main, 'localvar_set_mousescroll', SCRIPT_NAME);
+ }
+ else {
+ weechat::buffer_set($main, 'localvar_set_mousescroll', $mouse_scroll_ext.','.SCRIPT_NAME);
+ }
+ }
+}
+
+## restore_mouse_scroll -- restore mouse scrolling handler
+sub restore_mouse_scroll {
+ if (defined $script_old_mouse_scroll) {
+ my $main = weechat::buffer_search_main();
+ if ($script_old_mouse_scroll =~ /^\$/) {
+ weechat::buffer_set($main, 'localvar_del_mousescroll', '');
+ }
+ else {
+ weechat::buffer_set($main, 'localvar_set_mousescroll',
+ join ',', grep { $_ ne SCRIPT_NAME } split ',', $script_old_mouse_scroll);
+ }
+ }
+}
+
+## default_options -- set up default option values on start and when unset
+sub default_options {
+ my %defaults = (
+ url_braces => '[({<"'."''".'">})]',
+ url_regex =>
+ '\w+://\S+ | '.
+ '(?:^|(?<=\s))(?:\S+\.){2,}\w{2,5}(?:/\S*|(?=\s)|$) | '.
+ '(?:^|(?<=\s))(?:\S+\.)+\w{2,5}/(?:\S+)?',
+ url_non_endings => '[.,;:?!_-]',
+ url_non_beginnings => '\W',
+ hyper_nicks => 'off',
+ hyper_channels => 'off',
+ hyper_prefix => 'on',
+ hyper_show => 'url',
+ use_nick_menu => 'off',
+ xterm_compatible => 'rxvt-uni',
+ 'mouse.copy_on_click' => 'on',
+ 'mouse.close_on_release' => '110',
+ 'mouse.click_select_pane' => 'on',
+ 'mouse.click_through_pane' => 'off',
+ 'mouse.url_open_2nd_click' => 'off',
+ 'mouse.handle_scroll' => 'off',
+ 'mouse.scroll_inactive_pane' => 'on',
+ copybuf_short_name => '©',
+ 'color.selection_cursor' => 'reverse.underline',
+ 'color.selection' => 'reverse.brown,black',
+ 'color.url_highlight' => 'reverse.underline',
+ 'color.url_highlight_active' => 'reverse.brown,black',
+ );
+ for (keys %defaults) {
+ weechat::config_set_plugin($_, $defaults{$_})
+ unless weechat::config_is_set_plugin($_);
+ }
+ my $sf = SCRIPT_FILE;
+ for (Nlib::get_settings_from_pod($sf)) {
+ weechat::config_set_desc_plugin($_, Nlib::get_desc_from_pod($sf, $_));
+ }
+ if (Nlib::has_true_value(weechat::config_get_plugin('mouse.handle_scroll'))) {
+ decouple_mouse_scroll();
+ }
+ else {
+ restore_mouse_scroll();
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+sub close_copywin {
+ copywin_cmd(undef, $ACT_STR->[BUFPTR], '**q') if $ACT_STR;
+ weechat::WEECHAT_RC_OK
+}
+
+sub init_coords {
+ $listptr = weechat::list_new();
+ weechat::hook_timer(1000, 0, 1, 'check_layout', '');
+ default_options();
+ weechat::WEECHAT_RC_OK
+}
+
+sub stop_coords {
+ close_copywin();
+ restore_mouse_scroll();
+ weechat::list_free($listptr);
+ weechat::WEECHAT_RC_OK
+}