diff options
Diffstat (limited to 'weechat/perl')
l--------- | weechat/perl/autoload/awaylog.pl | 1 | ||||
l--------- | weechat/perl/autoload/beep.pl | 1 | ||||
l--------- | weechat/perl/autoload/buffers.pl | 1 | ||||
l--------- | weechat/perl/autoload/coords.pl | 1 | ||||
-rw-r--r-- | weechat/perl/awaylog.pl | 100 | ||||
-rwxr-xr-x | weechat/perl/beep.pl | 257 | ||||
-rwxr-xr-x | weechat/perl/buffers.pl | 1816 | ||||
-rw-r--r-- | weechat/perl/coords.pl | 3223 |
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 +} |