From 3a97e5bd1ba9463fa3cbf4a59f25de68e445a5a7 Mon Sep 17 00:00:00 2001 From: Alpha Chen Date: Mon, 9 Jan 2017 08:07:36 -0800 Subject: [PATCH] [weechat] moar config --- .weechat/autosort.conf | 11 + .weechat/buffers.conf | 73 + .weechat/colorize_nicks.conf | 12 + .weechat/irc.conf | 43 +- .weechat/iset.conf | 30 + .weechat/perl/autoload/buffers.pl | 1 + .weechat/perl/autoload/highmon.pl | 1 + .weechat/perl/autoload/iset.pl | 1 + .weechat/perl/buffers.pl | 1840 ++++++++++++++++++++ .weechat/perl/highmon.pl | 1139 ++++++++++++ .weechat/perl/iset.pl | 1624 +++++++++++++++++ .weechat/plugins.conf | 13 + .weechat/python/autoload/autosort.py | 1 + .weechat/python/autoload/colorize_nicks.py | 1 + .weechat/python/autosort.py | 885 ++++++++++ .weechat/python/colorize_nicks.py | 350 ++++ .weechat/script/plugins.xml.gz | Bin 0 -> 104794 bytes .weechat/weechat.conf | 41 +- 18 files changed, 6062 insertions(+), 4 deletions(-) create mode 100644 .weechat/autosort.conf create mode 100644 .weechat/buffers.conf create mode 100644 .weechat/colorize_nicks.conf create mode 100644 .weechat/iset.conf create mode 120000 .weechat/perl/autoload/buffers.pl create mode 120000 .weechat/perl/autoload/highmon.pl create mode 120000 .weechat/perl/autoload/iset.pl create mode 100644 .weechat/perl/buffers.pl create mode 100644 .weechat/perl/highmon.pl create mode 100644 .weechat/perl/iset.pl create mode 120000 .weechat/python/autoload/autosort.py create mode 120000 .weechat/python/autoload/colorize_nicks.py create mode 100644 .weechat/python/autosort.py create mode 100644 .weechat/python/colorize_nicks.py create mode 100644 .weechat/script/plugins.xml.gz diff --git a/.weechat/autosort.conf b/.weechat/autosort.conf new file mode 100644 index 0000000..430ff3d --- /dev/null +++ b/.weechat/autosort.conf @@ -0,0 +1,11 @@ +# +# weechat -- autosort.conf +# + +[sorting] +case_sensitive = off +group_irc = on +replacements = "[["##", "#"]]" +rules = "[["core", 0], ["irc", 2], ["*", 1], ["irc.irc_raw", 0], ["irc.server", 1], ["irc.server.*.&*", 0], ["irc.server.*.&*", 0], ["irc.server.*.#*", 1], ["irc.server.*.\\*status", 2]]" +signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed" +sort_on_config_change = on diff --git a/.weechat/buffers.conf b/.weechat/buffers.conf new file mode 100644 index 0000000..dd7a6a0 --- /dev/null +++ b/.weechat/buffers.conf @@ -0,0 +1,73 @@ +# +# weechat -- buffers.conf +# + +[color] +current_bg = red +current_fg = lightcyan +default_bg = default +default_fg = default +hotlist_highlight_bg = default +hotlist_highlight_fg = magenta +hotlist_low_bg = default +hotlist_low_fg = white +hotlist_message_bg = default +hotlist_message_fg = yellow +hotlist_private_bg = default +hotlist_private_fg = lightgreen +none_channel_bg = default +none_channel_fg = default +number = lightgreen +number_char = lightgreen +prefix_bufname = default +queries_default_bg = default +queries_default_fg = default +queries_highlight_bg = default +queries_highlight_fg = default +queries_message_bg = default +queries_message_fg = default +suffix_bufname = default +whitelist_default_bg = default +whitelist_default_fg = default +whitelist_highlight_bg = default +whitelist_highlight_fg = default +whitelist_low_bg = default +whitelist_low_fg = default +whitelist_message_bg = default +whitelist_message_fg = default +whitelist_private_bg = default +whitelist_private_fg = default + +[look] +core_to_front = off +detach = 600 +detach_buffer_immediately = "" +detach_buffer_immediately_level = 2 +detach_display_window_number = off +detach_displayed_buffers = on +detach_free_content = off +detach_query = off +hide_merged_buffers = server +hotlist_counter = off +immune_detach_buffers = "" +indenting = on +indenting_amount = 2 +indenting_number = on +jump_prev_next_visited_buffer = off +mark_inactive = off +mouse_move_buffer = on +mouse_wheel = on +name_crop_suffix = "+" +name_size_max = 0 +number_char = "." +prefix = off +prefix_bufname = "" +prefix_empty = on +prefix_for_query = "" +short_names = on +show_lag = off +show_number = on +sort = number +suffix_bufname = "" +toggle_bar = on +whitelist_buffers = "" diff --git a/.weechat/colorize_nicks.conf b/.weechat/colorize_nicks.conf new file mode 100644 index 0000000..f0f12ec --- /dev/null +++ b/.weechat/colorize_nicks.conf @@ -0,0 +1,12 @@ +# +# weechat -- colorize_nicks.conf +# + +[look] +blacklist_channels = "" +blacklist_nicks = "so,root" +colorize_input = off +greedy_matching = on +ignore_nicks_in_urls = off +ignore_tags = "" +min_nick_length = 2 diff --git a/.weechat/irc.conf b/.weechat/irc.conf index 836157b..54b1497 100644 --- a/.weechat/irc.conf +++ b/.weechat/irc.conf @@ -5,7 +5,7 @@ [look] buffer_open_before_autojoin = on buffer_open_before_join = off -buffer_switch_autojoin = on +buffer_switch_autojoin = off buffer_switch_join = on color_nicks_in_names = off color_nicks_in_nicklist = off @@ -48,7 +48,7 @@ part_closes_buffer = off pv_buffer = independent pv_tags = "notify_private" raw_messages = 256 -server_buffer = merge_with_core +server_buffer = independent smart_filter = on smart_filter_delay = 5 smart_filter_join = on @@ -180,3 +180,42 @@ pivotal.msg_kick pivotal.msg_part pivotal.msg_quit pivotal.notify +mozilla.addresses = "irc.mozilla.org/6697" +mozilla.proxy +mozilla.ipv6 +mozilla.ssl = on +mozilla.ssl_cert +mozilla.ssl_priorities +mozilla.ssl_dhkey_size +mozilla.ssl_fingerprint +mozilla.ssl_verify +mozilla.password +mozilla.capabilities +mozilla.sasl_mechanism +mozilla.sasl_username +mozilla.sasl_password +mozilla.sasl_key +mozilla.sasl_timeout +mozilla.sasl_fail +mozilla.autoconnect = on +mozilla.autoreconnect +mozilla.autoreconnect_delay +mozilla.nicks = "kejadlen" +mozilla.nicks_alternate +mozilla.username = "kejadlen" +mozilla.realname +mozilla.local_hostname +mozilla.command +mozilla.command_delay +mozilla.autojoin = "#rust,#rust-beginners" +mozilla.autorejoin +mozilla.autorejoin_delay +mozilla.connection_timeout +mozilla.anti_flood_prio_high +mozilla.anti_flood_prio_low +mozilla.away_check +mozilla.away_check_max_nicks +mozilla.msg_kick +mozilla.msg_part +mozilla.msg_quit +mozilla.notify diff --git a/.weechat/iset.conf b/.weechat/iset.conf new file mode 100644 index 0000000..cafe04f --- /dev/null +++ b/.weechat/iset.conf @@ -0,0 +1,30 @@ +# +# weechat -- iset.conf +# + +[color] +bg_selected = red +help_default_value = green +help_option_name = white +help_text = default +option = default +option_selected = white +type = brown +type_selected = yellow +value = cyan +value_diff = magenta +value_diff_selected = lightmagenta +value_selected = lightcyan +value_undef = green +value_undef_selected = lightgreen + +[help] +show_help_bar = on +show_help_extra_info = on +show_plugin_description = off + +[look] +scroll_horiz = 10 +show_current_line = on +use_mute = off +value_search_char = "=" 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/highmon.pl b/.weechat/perl/autoload/highmon.pl new file mode 120000 index 0000000..2eb5e1e --- /dev/null +++ b/.weechat/perl/autoload/highmon.pl @@ -0,0 +1 @@ +../highmon.pl \ No newline at end of file diff --git a/.weechat/perl/autoload/iset.pl b/.weechat/perl/autoload/iset.pl new file mode 120000 index 0000000..2746e0d --- /dev/null +++ b/.weechat/perl/autoload/iset.pl @@ -0,0 +1 @@ +../iset.pl \ No newline at end of file diff --git a/.weechat/perl/buffers.pl b/.weechat/perl/buffers.pl new file mode 100644 index 0000000..73eb4b5 --- /dev/null +++ b/.weechat/perl/buffers.pl @@ -0,0 +1,1840 @@ +# +# Copyright (C) 2008-2014 Sebastien Helleu +# Copyright (C) 2011-2013 Nils G +# +# 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 . +# +# +# Display sidebar with list of buffers. +# +# History: +# +# 2016-05-01, mumixam : +# v5.4: added option "detach_buffer_immediately_level" +# 2015-08-21, Matthew Cox +# v5.3: add option "indenting_amount", to adjust the indenting of channel buffers +# 2015-05-02, arza : +# v5.2: truncate long names (name_size_max) more when mark_inactive adds parenthesis +# 2015-03-29, Ed Santiago : +# 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 : +# v4.9: add support for specifying custom buffer names +# 2014-07-19, Sebastien Helleu : +# v4.8: add support of ctrl + mouse wheel to jump to previous/next buffer, +# new option "mouse_wheel" +# 2014-06-22, Sebastien Helleu : +# v4.7: fix typos in options +# 2014-04-05, Sebastien Helleu : +# v4.6: add support of hidden buffers (WeeChat >= 0.4.4) +# 2014-01-01, Sebastien Helleu : +# v4.5: add option "mouse_move_buffer" +# 2013-12-11, Sebastien Helleu : +# 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 : +# 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 : +# v3.7: call menu on right mouse if menu script is loaded. +# 2012-10-06, nils_2 : +# v3.6: add new option "hotlist_counter" (idea by torque). +# 2012-06-02, nils_2 : +# v3.5: add values "server|channel|private|all|keepserver|none" to option "hide_merged_buffers" (suggested by dominikh). +# 2012-05-25, nils_2 : +# v3.4: add new option "show_lag". +# 2012-04-07, Sebastien Helleu : +# v3.3: fix truncation of wide chars in buffer name (option name_size_max) (bug #36034) +# 2012-03-15, nils_2 : +# 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 : +# v3.1: fix reload of config file +# 2012-01-29, nils_2 : +# v3.0: fix: buffers did not update directly during window_switch (reported by FiXato) +# 2012-01-29, nils_2 : +# v2.9: add options "name_size_max" and "name_crop_suffix" +# 2012-01-08, nils_2 : +# 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 : +# v2.7: fix regex lookup in whitelist buffers list +# 2011-12-04, nils_2 : +# 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 : +# v2.5: add new options "show_number_char" and "color_number_char", +# add help-description for options +# 2011-08-24, Sebastien Helleu : +# v2.4: add mouse support +# 2011-06-06, nils_2 : +# v2.3: added: missed option "color_whitelist_default" +# 2011-03-23, Sebastien Helleu : +# v2.2: fix color of nick prefix with WeeChat >= 0.3.5 +# 2011-02-13, nils_2 : +# v2.1: add options "color_whitelist_*" +# 2010-10-05, Sebastien Helleu : +# v2.0: add options "sort" and "show_number" +# 2010-04-12, Sebastien Helleu : +# v1.9: replace call to log() by length() to align buffer numbers +# 2010-04-02, Sebastien Helleu : +# v1.8: fix bug with background color and option indenting_number +# 2010-04-02, Helios : +# v1.7: add indenting_number option +# 2010-02-25, m4v : +# v1.6: add option to hide empty prefixes +# 2010-02-12, Sebastien Helleu : +# v1.5: add optional nick prefix for buffers like IRC channels +# 2009-09-30, Sebastien Helleu : +# v1.4: remove spaces for indenting when bar position is top/bottom +# 2009-06-14, Sebastien Helleu : +# v1.3: add option "hide_merged_buffers" +# 2009-06-14, Sebastien Helleu : +# v1.2: improve display with merged buffers +# 2009-05-02, Sebastien Helleu : +# v1.1: sync with last API changes +# 2009-02-21, Sebastien Helleu : +# v1.0: remove timer used to update bar item first time (not needed any more) +# 2009-02-17, Sebastien Helleu : +# v0.9: fix bug with indenting of private buffers +# 2009-01-04, Sebastien Helleu : +# v0.8: update syntax for command /set (comments) +# 2008-10-20, Jiri Golembiovsky : +# v0.7: add indenting option +# 2008-10-01, Sebastien Helleu : +# v0.6: add default color for buffers, and color for current active buffer +# 2008-09-18, Sebastien Helleu : +# v0.5: fix color for "low" level entry in hotlist +# 2008-09-18, Sebastien Helleu : +# v0.4: rename option "show_category" to "short_names", +# remove option "color_slash" +# 2008-09-15, Sebastien Helleu : +# v0.3: fix bug with priority in hotlist (var not defined) +# 2008-09-02, Sebastien Helleu : +# 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 : +# 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.4"; + +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 ", + $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", "", "", "" + ], + "indenting_amount" => [ + "indenting_amount", "integer", + "amount of indenting to use. This option only takes effect if bar ". + "is left/right positioned, and indenting is enabled", + "", 0, 16, 2, 2, 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. Buffers ". + "will attach again based on notify level set in ". + "\"detach_buffer_immediately_level\". Allows \"*\" wildcard. ". + "Ex: \"BitlBee,freenode.*\"", + "", 0, 0, "", "", 0, + "", "", "buffers_signal_config_detach_buffer_immediately", "", "", "" + ], + "detach_buffer_immediately_level" => [ + "detach_buffer_immediately_level", "integer", + "The value determines what notify level messages are reattached from activity. ". + " This option works in conjunction with \"detach_buffer_immediately\" ". + "0: low priority (like join/part messages), ". + "1: message, ". + "2: private, ". + "3: highlight", + "", 0, 3, 2, 2, 0, + "", "", "buffers_signal_config", "", "", "" + ], + "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 + my $maxlevel = weechat::config_integer($options{"detach_buffer_immediately_level"}); + 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"}} < $maxlevel) ); # 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 .= (" " x weechat::config_integer( $options{"indenting_amount"} ) ); + } + 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 .= (" " x weechat::config_integer( $options{"indenting_amount"} ) ); + } + else + { + $str .= ( (" " x ( $max_number_digits - length($buffer->{"number"}) )).(" " x weechat::config_integer( $options{"indenting_amount"} ) ) ); + } + } + } + } + + $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/highmon.pl b/.weechat/perl/highmon.pl new file mode 100644 index 0000000..1c07712 --- /dev/null +++ b/.weechat/perl/highmon.pl @@ -0,0 +1,1139 @@ +# +# highmon.pl - Highlight Monitoring for weechat 0.3.0 +# Version 2.5 +# +# Add 'Highlight Monitor' buffer/bar to log all highlights in one spot +# +# Usage: +# /highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar +# Command wrapper for highmon commands +# +# /highmon clean default|orphan|all will clean the config section of default 'on' entries, +# channels you are no longer joined, or both +# +# /highmon clearbar will clear the contents of highmon's bar output +# +# /highmon monitor [channel] [server] is used to toggle a highlight monitoring on and off, this +# can be used in the channel buffer for the channel you wish to toggle, or be given +# with arguments e.g. /highmon monitor #weechat freenode +# +# /set plugins.var.perl.highmon.alignment +# The config setting "alignment" can be changed to; +# "channel", "schannel", "nchannel", "channel,nick", "schannel,nick", "nchannel,nick" +# to change how the monitor appears +# The 'channel' value will show: "#weechat" +# The 'schannel' value will show: "6" +# The 'nchannel' value will show: "6:#weechat" +# +# /set plugins.var.perl.highmon.short_names +# Setting this to 'on' will trim the network name from highmon, ala buffers.pl +# +# /set plugins.var.perl.highmon.merge_private +# Setting this to 'on' will merge private messages to highmon's display +# +# /set plugins.var.perl.highmon.color_buf +# This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name. +# This *must* be a valid color name, or weechat will likely do unexpected things :) +# +# /set plugins.var.perl.highmon.hotlist_show +# Setting this to 'on' will let the highmon buffer appear in hotlists +# (status bar/buffer.pl) +# +# /set plugins.var.perl.highmon.away_only +# Setting this to 'on' will only put messages in the highmon buffer when +# you set your status to away +# +# /set plugins.var.perl.highmon.logging +# Toggles logging status for highmon buffer (default: off) +# +# /set plugins.var.perl.highmon.output +# Changes where output method of highmon; takes either "bar" or "buffer" (default; buffer) +# /set plugins.var.perl.highmon.bar_lines +# Changes the amount of lines the output bar will hold. +# (Only appears once output has been set to bar, defaults to 10) +# /set plugins.var.perl.highmon.bar_scrolldown +# Toggles the bar scrolling at the bottom when new highlights are received +# (Only appears once output has been set to bar, defaults to off) +# +# /set plugins.var.perl.highmon.nick_prefix +# /set plugins.var.perl.highmon.nick_suffix +# Sets the prefix and suffix chars in the highmon buffer +# (Defaults to <> if nothing set, and blank if there is) +# +# servername.#channel +# servername is the internal name for the server (set when you use /server add) +# #channel is the channel name, (where # is whatever channel type that channel happens to be) +# +# Optional, set up tweaks; Hide the status and input lines on highmon +# +# /set weechat.bar.status.conditions "${window.buffer.full_name} != perl.highmon" +# /set weechat.bar.input.conditions "${window.buffer.full_name} != perl.highmon" +# + +# Bugs and feature requests at: https://github.com/KenjiE20/highmon + +# History: +# 2014-08-16, KenjiE20 : +# v2.5: -add: clearbar command to clear bar output +# -add: firstrun output prompt to check the help text for set up hints as they were being missed +# and update hint for conditions to use eval +# -change: Make all outputs use the date callback for more accurate timestamps (thanks Germainz) +# 2013-12-04, KenjiE20 : +# v2.4: -add: Support for eval style colour codes in time format used for bar output +# 2013-10-22, KenjiE20 : +# v2.3.3.2: -fix: Typo in fix command +# 2013-10-10, KenjiE20 : +# v2.3.3.1: -fix: Typo in closed buffer warning +# 2013-10-07, KenjiE20 : +# v2.3.3: -add: Warning and fixer for accidental buffer closes +# 2013-01-15, KenjiE20 : +# v2.3.2: -fix: Let bar output use the string set in weechat's config option +# -add: github info +# 2012-04-15, KenjiE20 : +# v2.3.1: -fix: Colour tags in bar timestamp string +# 2012-02-28, KenjiE20 : +# v2.3: -feature: Added merge_private option to display private messages (default: off) +# -fix: Channel name colours now show correctly when set to on +# 2011-08-07, Sitaktif : +# v2.2.1: -feature: Add "bar_scrolldown" option to have the bar display the latest hl at anytime +# -fix: Set up bar-specific config at startup if 'output' is already configured as 'bar' +# 2010-12-22, KenjiE20 : +# v2.2: -change: Use API instead of config to find channel colours, ready for 0.3.4 and 256 colours +# 2010-12-13, idl0r & KenjiE20 : +# v2.1.3: -fix: perl errors caused by bar line counter +# -fix: Add command list to inbuilt help +# 2010-09-30, KenjiE20 : +# v2.1.2: -fix: logging config was not correctly toggling back on (thanks to sleo for noticing) +# -version sync w/ chanmon +# 2010-08-27, KenjiE20 : +# v2.1: -feature: Add 'nchannel' option to alignment to display buffer and name +# 2010-04-25, KenjiE20 : +# v2.0: Release as version 2.0 +# 2010-04-24, KenjiE20 : +# v1.9: Rewrite for v2.0 +# Bring feature set in line with chanmon 2.0 +# -code change: Made more subs to shrink the code down in places +# -fix: Stop highmon attempting to double load/hook +# -fix: Add version dependant check for away status +# 2010-01-25, KenjiE20 : +# v1.7: -fixture: Let highmon be aware of nick_prefix/suffix +# and allow custom prefix/suffix for chanmon buffer +# (Defaults to <> if nothing set, and blank if there is) +# (Thanks to m4v for these) +# 2009-09-07, KenjiE20 : +# v1.6: -feature: colored buffer names +# -change: version sync with chanmon +# 2009-09-05, KenjiE20 : +# v1.2: -fix: disable buffer highlight +# 2009-09-02, KenjiE20 : +# v.1.1.1 -change: Stop unsightly text block on '/help' +# 2009-08-10, KenjiE20 : +# v1.1: In-client help added +# 2009-08-02, KenjiE20 : +# v1.0: Initial Public Release + +# Copyright (c) 2009 by KenjiE20 +# +# 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 . +# + +@bar_lines = (); +@bar_lines_time = (); +# Replicate info earlier for in-client help + +$highmonhelp = weechat::color("bold")."/highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar".weechat::color("-bold")." + Command wrapper for highmon commands + +".weechat::color("bold")."/highmon clean default|orphan|all".weechat::color("-bold")." will clean the config section of default 'on' entries, channels you are no longer joined, or both + +".weechat::color("bold")."/highmon clearbar".weechat::color("-bold")." will clear the contents of highmon's bar output + +".weechat::color("bold")."/highmon monitor [channel] [server]".weechat::color("-bold")." is used to toggle a highlight monitoring on and off, this can be used in the channel buffer for the channel you wish to toggle, or be given with arguments e.g. /highmon monitor #weechat freenode + +".weechat::color("bold")."/set plugins.var.perl.highmon.alignment".weechat::color("-bold")." + The config setting \"alignment\" can be changed to; + \"channel\", \"schannel\", \"nchannel\", \"channel,nick\", \"schannel,nick\", \"nchannel,nick\" + to change how the monitor appears + The 'channel' value will show: \"#weechat\" + The 'schannel' value will show: \"6\" + The 'nchannel' value will show: \"6:#weechat\" + +".weechat::color("bold")."/set plugins.var.perl.highmon.short_names".weechat::color("-bold")." + Setting this to 'on' will trim the network name from highmon, ala buffers.pl + +".weechat::color("bold")."/set plugins.var.perl.highmon.merge_private".weechat::color("-bold")." + Setting this to 'on' will merge private messages to highmon's display + +".weechat::color("bold")."/set plugins.var.perl.highmon.color_buf".weechat::color("-bold")." + This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name. + This ".weechat::color("bold")."must".weechat::color("-bold")." be a valid color name, or weechat will likely do unexpected things :) + +".weechat::color("bold")."/set plugins.var.perl.highmon.hotlist_show".weechat::color("-bold")." +Setting this to 'on' will let the highmon buffer appear in hotlists (status bar/buffer.pl) + +".weechat::color("bold")."/set plugins.var.perl.highmon.away_only".weechat::color("-bold")." +Setting this to 'on' will only put messages in the highmon buffer when you set your status to away + +".weechat::color("bold")."/set plugins.var.perl.highmon.logging".weechat::color("-bold")." + Toggles logging status for highmon buffer (default: off) + +".weechat::color("bold")."/set plugins.var.perl.highmon.output".weechat::color("-bold")." + Changes where output method of highmon; takes either \"bar\" or \"buffer\" (default; buffer) +".weechat::color("bold")."/set plugins.var.perl.highmon.bar_lines".weechat::color("-bold")." + Changes the amount of lines the output bar will hold. + (Only appears once output has been set to bar, defaults to 10) +".weechat::color("bold")."/set plugins.var.perl.highmon.bar_scrolldown".weechat::color("-bold")." + Toggles the bar scrolling at the bottom when new highlights are received + (Only appears once output has been set to bar, defaults to off) + +".weechat::color("bold")."/set plugins.var.perl.highmon.nick_prefix".weechat::color("-bold")." +".weechat::color("bold")."/set plugins.var.perl.highmon.nick_suffix".weechat::color("-bold")." + Sets the prefix and suffix chars in the highmon buffer + (Defaults to <> if nothing set, and blank if there is) + +".weechat::color("bold")."servername.#channel".weechat::color("-bold")." + servername is the internal name for the server (set when you use /server add) + #channel is the channel name, (where # is whatever channel type that channel happens to be) + +".weechat::color("bold")."Optional, set up tweaks;".weechat::color("-bold")." Hide the status and input lines on highmon + +".weechat::color("bold")."/set weechat.bar.status.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold")." +".weechat::color("bold")."/set weechat.bar.input.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold"); +# Print verbose help +sub print_help +{ + weechat::print("", "\t".weechat::color("bold")."Highmon Help".weechat::color("-bold")."\n\n"); + weechat::print("", "\t".$highmonhelp); + return weechat::WEECHAT_RC_OK; +} + +# Bar item build +sub highmon_bar_build +{ + # Get max lines + $max_lines = weechat::config_get_plugin("bar_lines"); + $max_lines = $max_lines ? $max_lines : 10; + $str = ''; + $align_num = 0; + $count = 0; + # Keep lines within max + while ($#bar_lines > $max_lines) + { + shift(@bar_lines); + shift(@bar_lines_time); + } + # So long as we have some lines, build a string + if (@bar_lines) + { + # Build loop + $sep = " ".weechat::config_string(weechat::config_get("weechat.look.prefix_suffix"))." "; + foreach(@bar_lines) + { + # Find max align needed + $prefix_num = (index(weechat::string_remove_color($_, ""), $sep)); + $align_num = $prefix_num if ($prefix_num > $align_num); + } + foreach(@bar_lines) + { + # Get align for this line + $prefix_num = (index(weechat::string_remove_color($_, ""), $sep)); + + # Make string + $str = $str.$bar_lines_time[$count]." ".(" " x ($align_num - $prefix_num)).$_."\n"; + # Increment count for sync with time list + $count++; + } + } + return $str; +} + +# Make a new bar +sub highmon_bar_open +{ + # Make the bar item + weechat::bar_item_new("highmon", "highmon_bar_build", ""); + + $highmon_bar = weechat::bar_new ("highmon", "off", 100, "root", "", "bottom", "vertical", "vertical", 0, 0, "default", "cyan", "default", "on", "highmon"); + + return weechat::WEECHAT_RC_OK; +} +# Close bar +sub highmon_bar_close +{ + # Find if bar exists + $highmon_bar = weechat::bar_search("highmon"); + # If is does, close it + if ($highmon_bar ne "") + { + weechat::bar_remove($highmon_bar); + } + + # Find if bar item exists + $highmon_bar_item = weechat::bar_item_search("highmon_bar"); + # If is does, close it + if ($highmon_bar_item ne "") + { + weechat::bar_remove($highmon_bar_item); + } + + @bar_lines = (); + return weechat::WEECHAT_RC_OK; +} + +# Make a new buffer +sub highmon_buffer_open +{ + # Search for pre-existing buffer + $highmon_buffer = weechat::buffer_search("perl", "highmon"); + + # Make a new buffer + if ($highmon_buffer eq "") + { + $highmon_buffer = weechat::buffer_new("highmon", "highmon_buffer_input", "", "highmon_buffer_close", ""); + } + + # Turn off notify, highlights + if ($highmon_buffer ne "") + { + if (weechat::config_get_plugin("hotlist_show" eq "off")) + { + weechat::buffer_set($highmon_buffer, "notify", "0"); + } + weechat::buffer_set($highmon_buffer, "highlight_words", "-"); + weechat::buffer_set($highmon_buffer, "title", "Highlight Monitor"); + # Set no_log + if (weechat::config_get_plugin("logging") eq "off") + { + weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1"); + } + } + return weechat::WEECHAT_RC_OK; +} +# Buffer input has no action +sub highmon_buffer_input +{ + return weechat::WEECHAT_RC_OK; +} +# Close up +sub highmon_buffer_close +{ + $highmon_buffer = ""; + # If user hasn't changed output style warn user + if (weechat::config_get_plugin("output") eq "buffer") + { + weechat::print("", "\tHighmon buffer has been closed but output is still set to buffer, unusual results may occur. To recreate the buffer use ".weechat::color("bold")."/highmon fix".weechat::color("-bold")); + } + return weechat::WEECHAT_RC_OK; +} + +# Highmon command wrapper +sub highmon_command_cb +{ + $data = $_[0]; + $buffer = $_[1]; + $args = $_[2]; + my $cmd = ''; + my $arg = ''; + + if ($args ne "") + { + # Split argument up + @arg_array = split(/ /,$args); + # Take first as command + $cmd = shift(@arg_array); + # Rebuild string to pass to subs + if (@arg_array) + { + $arg = join(" ", @arg_array); + } + } + + # Help command + if ($cmd eq "" || $cmd eq "help") + { + print_help(); + } + # /monitor command + elsif ($cmd eq "monitor") + { + highmon_toggle($data, $buffer, $arg); + } + # /highclean command + elsif ($cmd eq "clean") + { + highmon_config_clean($data, $buffer, $arg); + } + # clearbar command + elsif ($cmd eq "clearbar") + { + if (weechat::config_get_plugin("output") eq "bar") + { + @bar_lines = (); + weechat::bar_item_update("highmon"); + } + } + # Fix closed buffer + elsif ($cmd eq "fix") + { + if (weechat::config_get_plugin("output") eq "buffer" && $highmon_buffer eq "") + { + highmon_buffer_open(); + } + } + return weechat::WEECHAT_RC_OK; +} + +# Clean up config entries +sub highmon_config_clean +{ + $data = $_[0]; + $buffer = $_[1]; + $args = $_[2]; + + # Don't do anything if bad option given + if ($args ne "default" && $args ne "orphan" && $args ne "all") + { + weechat::print("", "\thighmon.pl: Unknown option"); + return weechat::WEECHAT_RC_OK; + } + + @chans = (); + # Load an infolist of highmon options + $infolist = weechat::infolist_get("option", "", "*highmon*"); + while (weechat::infolist_next($infolist)) + { + $name = weechat::infolist_string($infolist, "option_name"); + $name =~ s/perl\.highmon\.(\w*)\.([#&\+!])(.*)/$1.$2$3/; + if ($name =~ /^(.*)\.([#&\+!])(.*)$/) + { + $action = 0; + # Clean up all 'on's + if ($args eq "default" || $args eq "all") + { + # If value in config is "on" + if (weechat::config_get_plugin($name) eq "on") + { + # Unset and if successful flag as changed + $rc = weechat::config_unset_plugin($name); + if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED) + { + $action = 1; + } + } + } + # Clean non joined + if ($args eq "orphan" || $args eq "all") + { + # If we can't find the buffer for this entry + if (weechat::buffer_search("irc", $name) eq "") + { + # Unset and if successful flag as changed + $rc = weechat::config_unset_plugin($name); + if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED) + { + $action = 1; + } + } + } + # Add changed entry names to list + push (@chans, $name) if ($action); + } + } + weechat::infolist_free($infolist); + # If channels were cleaned from config + if (@chans) + { + # If only one entry + if (@chans == 1) + { + $str = "\thighmon.pl: Cleaned ".@chans." entry from the config:"; + } + else + { + $str = "\thighmon.pl: Cleaned ".@chans." entries from the config:"; + } + # Build a list of channels + foreach(@chans) + { + $str = $str." ".$_; + } + # Print what happened + weechat::print("",$str); + } + # Config seemed to be clean + else + { + weechat::print("", "\thighmon.pl: No entries removed"); + } + return weechat::WEECHAT_RC_OK; +} + +# Check config elements +sub highmon_config_init +{ + # First run default + if (!(weechat::config_is_set_plugin ("first_run"))) + { + if (weechat::config_get_plugin("first_run") ne "true") + { + weechat::print("", "\tThis appears to be the first time highmon has been run. For help and common set up hints see /highmon help"); + weechat::config_set_plugin("first_run", "true"); + } + } + # Alignment default + if (!(weechat::config_is_set_plugin ("alignment"))) + { + weechat::config_set_plugin("alignment", "channel"); + } + if (weechat::config_get_plugin("alignment") eq "") + { + weechat::config_set_plugin("alignment", "none"); + } + + # Short name default + if (!(weechat::config_is_set_plugin ("short_names"))) + { + weechat::config_set_plugin("short_names", "off"); + } + + # Coloured names default + if (!(weechat::config_is_set_plugin ("color_buf"))) + { + weechat::config_set_plugin("color_buf", "on"); + } + + # Hotlist show default + if (!(weechat::config_is_set_plugin ("hotlist_show"))) + { + weechat::config_set_plugin("hotlist_show", "off"); + } + + # Away only default + if (!(weechat::config_is_set_plugin ("away_only"))) + { + weechat::config_set_plugin("away_only", "off"); + } + + # highmon log default + if (!(weechat::config_is_set_plugin ("logging"))) + { + weechat::config_set_plugin("logging", "off"); + } + + # Output default + if (!(weechat::config_is_set_plugin ("output"))) + { + weechat::config_set_plugin("output", "buffer"); + } + + # Private message merging + if (!(weechat::config_is_set_plugin ("merge_private"))) + { + weechat::config_set_plugin("merge_private", "off"); + } + + # Set bar config in case output was set to "bar" before even changing the setting + if (weechat::config_get_plugin("output") eq "bar") + { + # Output bar lines default + if (!(weechat::config_is_set_plugin ("bar_lines"))) + { + weechat::config_set_plugin("bar_lines", "10"); + } + if (!(weechat::config_is_set_plugin ("bar_scrolldown"))) + { + weechat::config_set_plugin("bar_scrolldown", "off"); + } + } + + # Check for exisiting prefix/suffix chars, and setup accordingly + $prefix = weechat::config_get("irc.look.nick_prefix"); + $prefix = weechat::config_string($prefix); + $suffix = weechat::config_get("irc.look.nick_suffix"); + $suffix = weechat::config_string($suffix); + + if (!(weechat::config_is_set_plugin("nick_prefix"))) + { + if ($prefix eq "" && $suffix eq "") + { + weechat::config_set_plugin("nick_prefix", "<"); + } + else + { + weechat::config_set_plugin("nick_prefix", ""); + } + } + + if (!(weechat::config_is_set_plugin("nick_suffix"))) + { + if ($prefix eq "" && $suffix eq "") + { + weechat::config_set_plugin("nick_suffix", ">"); + } + else + { + weechat::config_set_plugin("nick_suffix", ""); + } + } +} + +# Get config updates +sub highmon_config_cb +{ + $point = $_[0]; + $name = $_[1]; + $value = $_[2]; + + $name =~ s/^plugins\.var\.perl\.highmon\.//; + + # Set logging on buffer + if ($name eq "logging") + { + # Search for pre-existing buffer + $highmon_buffer = weechat::buffer_search("perl", "highmon"); + if ($value eq "off") + { + weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1"); + } + else + { + weechat::buffer_set($highmon_buffer, "localvar_del_no_log", ""); + } + } + # Output changer + elsif ($name eq "output") + { + if ($value eq "bar") + { + # Search for pre-existing buffer + $highmon_buffer = weechat::buffer_search("perl", "highmon"); + # Close if it exists + if ($highmon_buffer ne "") + { + weechat::buffer_close($highmon_buffer) + } + + # Output bar lines default + if (!(weechat::config_is_set_plugin ("bar_lines"))) + { + weechat::config_set_plugin("bar_lines", "10"); + } + if (!(weechat::config_is_set_plugin ("bar_scrolldown"))) + { + weechat::config_set_plugin("bar_scrolldown", "off"); + } + # Make a bar if doesn't exist + highmon_bar_open(); + } + elsif ($value eq "buffer") + { + # If a bar exists, close it + highmon_bar_close(); + # Open buffer + highmon_buffer_open(); + } + + } + # Change if hotlist config changes + elsif ($name eq "hotlist_show") + { + # Search for pre-existing buffer + $highmon_buffer = weechat::buffer_search("perl", "highmon"); + if ($value eq "off" && $highmon_buffer) + { + weechat::buffer_set($highmon_buffer, "notify", "0"); + } + elsif ($value ne "off" && $highmon_buffer) + { + weechat::buffer_set($highmon_buffer, "notify", "3"); + } + } + elsif ($name eq "weechat.look.prefix_suffix") + { + if (weechat::config_get_plugin("output") eq "bar") + { + @bar_lines = (); + weechat::print("", "\thighmon: weechat.look.prefix_suffix changed, clearing highmon bar"); + weechat::bar_item_update("highmon"); + } + } + return weechat::WEECHAT_RC_OK; +} + +# Set up weechat hooks / commands +sub highmon_hook +{ + weechat::hook_print("", "", "", 0, "highmon_new_message", ""); + weechat::hook_command("highclean", "Highmon config clean up", "default|orphan|all", " default: Cleans all config entries with the default \"on\" value\n orphan: Cleans all config entries for channels you aren't currently joined\n all: Does both defaults and orphan", "default|orphan|all", "highmon_config_clean", ""); + + weechat::hook_command("highmon", "Highmon help", "[help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar", " help: Print help on config options for highmon\n monitor: Toggles monitoring for a channel\n clean: Highmon config clean up (/highclean)\nclearbar: Clear Highmon bar", "help || monitor %(irc_channels) %(irc_servers) || clean default|orphan|all || clearbar", "highmon_command_cb", ""); + + weechat::hook_config("plugins.var.perl.highmon.*", "highmon_config_cb", ""); + weechat::hook_config("weechat.look.prefix_suffix", "highmon_config_cb", ""); +} + +# Main body, Callback for hook_print +sub highmon_new_message +{ + my $net = ""; + my $chan = ""; + my $nick = ""; + my $outstr = ""; + my $window_displayed = ""; + my $dyncheck = "0"; + +# DEBUG point +# $string = "\t"."0: ".$_[0]." 1: ".$_[1]." 2: ".$_[2]." 3: ".$_[3]." 4: ".$_[4]." 5: ".$_[5]." 6: ".$_[6]." 7: ".$_[7]; +# weechat::print("", "\t".$string); + + $cb_datap = $_[0]; + $cb_bufferp = $_[1]; + $cb_date = $_[2]; + $cb_tags = $_[3]; + $cb_disp = $_[4]; + $cb_high = $_[5]; + $cb_prefix = $_[6]; + $cb_msg = $_[7]; + + # Only work on highlighted messages or private message when enabled + if ($cb_high == "1" || (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/)) + { + # Pre bug #29618 (0.3.3) away detect + if (weechat::info_get("version_number", "") <= 197120) + { + $away = ''; + # Get infolist for this server + $infolist = weechat::infolist_get("irc_server", "", weechat::buffer_get_string($cb_bufferp, "localvar_server")); + while (weechat::infolist_next($infolist)) + { + # Get away message is is_away is on + $away = weechat::infolist_string($infolist, "away_message") if (weechat::infolist_integer($infolist, "is_away")); + } + weechat::infolist_free($infolist); + } + # Post bug #29618 fix + else + { + $away = weechat::buffer_get_string($cb_bufferp, "localvar_away"); + } + if (weechat::config_get_plugin("away_only") ne "on" || ($away ne "")) + { + # Check buffer name is an IRC channel + $bufname = weechat::buffer_get_string($cb_bufferp, 'name'); + if ($bufname =~ /(.*)\.([#&\+!])(.*)/) + { + # Are we running on this channel + if (weechat::config_get_plugin($bufname) ne "off" && $cb_disp eq "1") + { + # Format nick + # Line isn't action or topic notify + if (!($cb_tags =~ /irc_action/) && !($cb_tags =~ /irc_topic/)) + { + # Strip nick colour + $uncolnick = weechat::string_remove_color($cb_prefix, ""); + # Format nick + $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix"); + } + # Topic line + elsif ($cb_tags =~ /irc_topic/) + { + $nick = " ".$cb_prefix.weechat::color("reset"); + } + # Action line + else + { + $uncolnick = weechat::string_remove_color($cb_prefix, ""); + $nick = weechat::color("chat_highlight").$uncolnick.weechat::color("reset"); + } + # Send to output + highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags); + } + } + # Or is private message + elsif (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/) + { + # Strip nick colour + $uncolnick = weechat::buffer_get_string($cb_bufferp, 'short_name'); + # Format nick + $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix"); + #Send to output + highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags); + } + } + } + return weechat::WEECHAT_RC_OK; +} + +# Output formatter and printer takes (msg bufpointer nick) +sub highmon_print +{ + $cb_msg = $_[0]; + my $cb_bufferp = $_[1] if ($_[1]); + my $nick = $_[2] if ($_[2]); + my $cb_date = $_[3] if ($_[3]); + my $cb_tags = $_[4] if ($_[4]); + + #Normal channel message + if ($cb_bufferp && $nick) + { + # Format buffer name + $bufname = format_buffer_name($cb_bufferp); + + # If alignment is #channel | nick msg + if (weechat::config_get_plugin("alignment") eq "channel") + { + $nick =~ s/\s(.*)/$1/; + # Build string + $outstr = $bufname."\t".$nick." ".$cb_msg; + } + # or if it is channel number | nick msg + elsif (weechat::config_get_plugin("alignment") eq "schannel") + { + $nick =~ s/\s(.*)/$1/; + # Use channel number instead + $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); + # Build string + $outstr = $bufname."\t".$nick." ".$cb_msg; + } + # or if it is number:#channel | nick msg + elsif (weechat::config_get_plugin("alignment") eq "nchannel") + { + $nick =~ s/\s(.*)/$1/; + # Place channel number in front of formatted name + $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; + # Build string + $outstr = $bufname."\t".$nick." ".$cb_msg; + } + # or if it is #channel nick | msg + elsif (weechat::config_get_plugin("alignment") eq "channel,nick") + { + # Build string + $outstr = $bufname.":".$nick."\t".$cb_msg; + } + # or if it is channel number nick | msg + elsif (weechat::config_get_plugin("alignment") eq "schannel,nick") + { + # Use channel number instead + $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); + # Build string + $outstr = $bufname.":".$nick."\t".$cb_msg; + } + # or if it is number:#channel nick | msg + elsif (weechat::config_get_plugin("alignment") eq "nchannel,nick") + { + # Place channel number in front of formatted name + $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; + # Build string + $outstr = $bufname.":".$nick."\t".$cb_msg; + } + # or finally | #channel nick msg + else + { + # Build string + $outstr = "\t".$bufname.":".$nick." ".$cb_msg; + } + } + # highmon channel toggle message + elsif ($cb_bufferp && !$nick) + { + # Format buffer name + $bufname = format_buffer_name($cb_bufferp); + + # If alignment is #channel * | * + if (weechat::config_get_plugin("alignment") =~ /channel/) + { + # If it's actually channel number * | * + if (weechat::config_get_plugin("alignment") =~ /schannel/) + { + # Use channel number instead + $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); + } + # Or if it's actually number:#channel * | * + if (weechat::config_get_plugin("alignment") =~ /nchannel/) + { + # Place channel number in front of formatted name + $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; + } + $outstr = $bufname."\t".$cb_msg; + } + # or if alignment is | * + else + { + $outstr = $bufname.": ".$cb_msg; + } + } + # highmon dynmon + elsif (!$cb_bufferp && !$nick) + { + $outstr = "\t".$cb_msg; + } + + # Send string to buffer + if (weechat::config_get_plugin("output") eq "buffer") + { + # Search for and confirm buffer + $highmon_buffer = weechat::buffer_search("perl", "highmon"); + # Print + if ($cb_date) + { + weechat::print_date_tags($highmon_buffer, $cb_date, $cb_tags, $outstr); + } + else + { + weechat::print($highmon_buffer, $outstr); + } + } + elsif (weechat::config_get_plugin("output") eq "bar") + { + # Add time string + use POSIX qw(strftime); + if ($cb_date) + { + $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime($cb_date)); + } + else + { + $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime); + } + # Colourise + if ($time =~ /\$\{(?:color:)?[\w,]+\}/) # Coloured string + { + while ($time =~ /\$\{(?:color:)?([\w,]+)\}/) + { + $color = weechat::color($1); + $time =~ s/\$\{(?:color:)?[\w,]+\}/$color/; + } + $time .= weechat::color("reset"); + } + else # Default string + { + $colour = weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_time_delimiters"))); + $reset = weechat::color("reset"); + $time =~ s/(\d*)(.)(\d*)/$1$colour$2$reset$3/g; + } + # Push updates to bar lists + push (@bar_lines_time, $time); + + # Change tab char + $delim = " ".weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_delimiters"))).weechat::config_string(weechat::config_get("weechat.look.prefix_suffix")).weechat::color("reset")." "; + $outstr =~ s/\t/$delim/; + + push (@bar_lines, $outstr); + # Trigger update + weechat::bar_item_update("highmon"); + + if (weechat::config_get_plugin("bar_scrolldown") eq "on") + { + weechat::command("", "/bar scroll highmon * ye") + } + } +} + +# Start the output display +sub highmon_start +{ + if (weechat::config_get_plugin("output") eq "buffer") + { + highmon_buffer_open(); + } + elsif (weechat::config_get_plugin("output") eq "bar") + { + highmon_bar_open(); + } +} + +# Takes two optional args (channel server), toggles monitoring on/off +sub highmon_toggle +{ + $data = $_[0]; + $buffer = $_[1]; + $args = $_[2]; + + # Check if we've been told what channel to act on + if ($args ne "") + { + # Split argument up + @arg_array = split(/ /,$args); + # Check if a server was given + if ($arg_array[1]) + { + # Find matching + $bufp = weechat::buffer_search("irc", $arg_array[1].".".$arg_array[0]); + } + else + { + $found_chans = 0; + # Loop through defined servers + $infolist = weechat::infolist_get("buffer", "", ""); + while (weechat::infolist_next($infolist)) + { + # Only interesting in IRC buffers + if (weechat::infolist_string($infolist, "plugin_name") eq "irc") + { + # Find buffers that maych + $sname = weechat::infolist_string($infolist, "short_name"); + if ($sname eq $arg_array[0]) + { + $found_chans++; + $bufp = weechat::infolist_pointer($infolist, "pointer"); + } + } + } + weechat::infolist_free($infolist); + # If the infolist found more than one channel, halt as we need to know which one + if ($found_chans > 1) + { + weechat::print("", "Channel name is not unique, please define server"); + return weechat::WEECHAT_RC_OK; + } + } + # Something didn't return right + if ($bufp eq "") + { + weechat::print("", "Could not find buffer"); + return weechat::WEECHAT_RC_OK; + } + } + else + { + # Get pointer from where we are + $bufp = weechat::current_buffer(); + } + # Get buffer name + $bufname = weechat::buffer_get_string($bufp, 'name'); + # Test if buffer is an IRC channel + if ($bufname =~ /(.*)\.([#&\+!])(.*)/) + { + if (weechat::config_get_plugin($bufname) eq "off") + { + # If currently off, set on + weechat::config_set_plugin($bufname, "on"); + + # Send to output formatter + highmon_print("Highlight Monitoring Enabled", $bufp); + return weechat::WEECHAT_RC_OK; + } + elsif (weechat::config_get_plugin($bufname) eq "on" || weechat::config_get_plugin($bufname) eq "") + { + # If currently on, set off + weechat::config_set_plugin($bufname, "off"); + + # Send to output formatter + highmon_print("Highlight Monitoring Disabled", $bufp); + return weechat::WEECHAT_RC_OK; + } + } +} + +# Takes a buffer pointer and returns a formatted name +sub format_buffer_name +{ + $cb_bufferp = $_[0]; + $bufname = weechat::buffer_get_string($cb_bufferp, 'name'); + + # Set colour from buffer name + if (weechat::config_get_plugin("color_buf") eq "on") + { + # Determine what colour to use + $color = weechat::info_get("irc_nick_color", $bufname); + if (!$color) + { + $color = 0; + @char_array = split(//,$bufname); + foreach $char (@char_array) + { + $color += ord($char); + } + $color %= 10; + $color = sprintf "weechat.color.chat_nick_color%02d", $color+1; + $color = weechat::config_get($color); + $color = weechat::config_string($color); + $color = weechat::color($color); + } + + # Private message just show network + if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") + { + $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); + } + # Format name to short or 'nicename' + elsif (weechat::config_get_plugin("short_names") eq "on") + { + $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); + } + else + { + $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; + } + + # Build a coloured string + $bufname = $color.$bufname.weechat::color("reset"); + } + # User set colour name + elsif (weechat::config_get_plugin("color_buf") ne "off") + { + # Private message just show network + if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") + { + $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); + } + # Format name to short or 'nicename' + elsif (weechat::config_get_plugin("short_names") eq "on") + { + $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); + } + else + { + $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; + } + + $color = weechat::config_get_plugin("color_buf"); + $bufname = weechat::color($color).$bufname.weechat::color("reset"); + } + # Stick with default colour + else + { + # Private message just show network + if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") + { + $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); + } + # Format name to short or 'nicename' + elsif (weechat::config_get_plugin("short_names") eq "on") + { + $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); + } + else + { + $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; + } + } + + return $bufname; +} + +# Check result of register, and attempt to behave in a sane manner +if (!weechat::register("highmon", "KenjiE20", "2.5", "GPL3", "Highlight Monitor", "", "")) +{ + # Double load + weechat::print ("", "\tHighmon is already loaded"); + return weechat::WEECHAT_RC_OK; +} +else +{ + # Start everything + highmon_hook(); + highmon_config_init(); + highmon_start(); +} diff --git a/.weechat/perl/iset.pl b/.weechat/perl/iset.pl new file mode 100644 index 0000000..163dfb5 --- /dev/null +++ b/.weechat/perl/iset.pl @@ -0,0 +1,1624 @@ +# +# Copyright (C) 2008-2014 Sebastien Helleu +# Copyright (C) 2010-2015 Nils Görs +# +# 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 . +# +# Set WeeChat and plugins options interactively. +# +# History: +# +# 2016-07-08, nils_2 +# version 4.2: add diff function +# 2016-02-06, Sebastien Helleu : +# version 4.1: remove debug print +# 2015-12-24, Sebastien Helleu : +# version 4.0: add support of parent options (inherited values in irc servers) +# with WeeChat >= 1.4 +# 2015-05-16, Sebastien Helleu : +# version 3.9: fix cursor position when editing an option with WeeChat >= 1.2 +# 2015-05-02, arza : +# version 3.8: don't append "null" to /set when setting an undefined setting +# 2015-05-01, nils_2 : +# version 3.7: fix two perl warnings (reported by t3chguy) +# 2014-09-30, arza : +# version 3.6: fix current line counter when options aren't found +# 2014-06-03, nils_2 : +# version 3.5: add new option "use_mute" +# 2014-01-30, stfn : +# version 3.4: add new options "color_value_diff" and "color_value_diff_selected" +# 2014-01-16, luz : +# version 3.3: fix bug with column alignment in iset buffer when option +# name contains unicode characters +# 2013-08-03, Sebastien Helleu : +# version 3.2: allow "q" as input in iset buffer to close it +# 2013-07-14, Sebastien Helleu : +# version 3.1: remove unneeded calls to iset_refresh() in mouse callback +# (faster mouse actions when lot of options are displayed), +# fix bug when clicking on a line after the last option displayed +# 2013-04-30, arza : +# version 3.0: simpler title, fix refresh on unset +# 2012-12-16, nils_2 : +# version 2.9: fix focus window with iset buffer on mouse click +# 2012-08-25, nils_2 : +# version 2.8: most important key and mouse bindings for iset buffer added to title-bar (idea The-Compiler) +# 2012-07-31, nils_2 : +# version 2.7: add combined option and value search (see /help iset) +# : add exact value search (see /help iset) +# : fix problem with metacharacter in value search +# : fix use of uninitialized value for unset option and reset value of option +# 2012-07-25, nils_2 : +# version 2.6: switch to iset buffer (if existing) when command /iset is called with arguments +# 2012-03-17, Sebastien Helleu : +# version 2.5: fix check of sections when creating config file +# 2012-03-09, Sebastien Helleu : +# version 2.4: fix reload of config file +# 2012-02-02, nils_2 : +# version 2.3: fixed: refresh problem with new search results and cursor was outside window. +# : add: new option "current_line" in title bar +# version 2.2: fixed: refresh error when toggling plugins description +# 2011-11-05, nils_2 : +# version 2.1: use own config file (iset.conf), fix own help color (used immediately) +# 2011-10-16, nils_2 : +# version 2.0: add support for left-mouse-button and more sensitive mouse gesture (for integer/color options) +# add help text for mouse support +# 2011-09-20, Sebastien Helleu : +# version 1.9: add mouse support, fix iset buffer, fix errors on first load under FreeBSD +# 2011-07-21, nils_2 : +# version 1.8: added: option "show_plugin_description" (alt+p) +# fixed: typos in /help iset (lower case for alt+'x' keys) +# 2011-05-29, nils_2 : +# version 1.7: added: version check for future needs +# added: new option (scroll_horiz) and usage of scroll_horiz function (weechat >= 0.3.6 required) +# fixed: help_bar did not pop up immediately using key-shortcut +# 2011-02-19, nils_2 : +# version 1.6: added: display of all possible values in help bar (show_help_extra_info) +# fixed: external user options never loaded when starting iset first time +# 2011-02-13, Sebastien Helleu : +# version 1.5: use new help format for command arguments +# 2011-02-03, nils_2 : +# version 1.4: fixed: restore value filter after /upgrade using buffer local variable. +# 2011-01-14, nils_2 : +# version 1.3: added function to search for values (option value_search_char). +# code optimization. +# 2010-12-26, Sebastien Helleu : +# version 1.2: improve speed of /upgrade when iset buffer is open, +# restore filter used after /upgrade using buffer local variable, +# use /iset filter argument if buffer is open. +# 2010-11-21, drubin : +# version 1.1.1: fix bugs with cursor position +# 2010-11-20, nils_2 : +# version 1.1: cursor position set to value +# 2010-08-03, Sebastien Helleu : +# version 1.0: move misplaced call to infolist_free() +# 2010-02-02, rettub : +# version 0.9: turn all the help stuff off if option 'show_help_bar' is 'off', +# new key binding - to toggle help_bar and help stuff on/off +# 2010-01-30, nils_2 : +# version 0.8: fix error when option does not exist +# 2010-01-24, Sebastien Helleu : +# version 0.7: display iset bar only on iset buffer +# 2010-01-22, nils_2 and drubin: +# version 0.6: add description in a bar, fix singular/plural bug in title bar, +# fix selected line when switching buffer +# 2009-06-21, Sebastien Helleu : +# version 0.5: fix bug with iset buffer after /upgrade +# 2009-05-02, Sebastien Helleu : +# version 0.4: sync with last API changes +# 2009-01-04, Sebastien Helleu : +# version 0.3: open iset buffer when /iset command is executed +# 2009-01-04, Sebastien Helleu : +# version 0.2: use null values for options, add colors, fix refresh bugs, +# use new keys to reset/unset options, sort options by name, +# display number of options in buffer's title +# 2008-11-05, Sebastien Helleu : +# version 0.1: first official version +# 2008-04-19, Sebastien Helleu : +# script creation + +use strict; + +my $PRGNAME = "iset"; +my $VERSION = "4.2"; +my $DESCR = "Interactive Set for configuration options"; +my $AUTHOR = "Sebastien Helleu "; +my $LICENSE = "GPL3"; +my $LANG = "perl"; +my $ISET_CONFIG_FILE_NAME = "iset"; + +my $iset_config_file; +my $iset_buffer = ""; +my $wee_version_number = 0; +my @iset_focus = (); +my @options_names = (); +my @options_parent_names = (); +my @options_types = (); +my @options_values = (); +my @options_default_values = (); +my @options_parent_values = (); +my @options_is_null = (); +my $option_max_length = 0; +my $current_line = 0; +my $filter = "*"; +my $description = ""; +my $options_name_copy = ""; +my $iset_filter_title = ""; +# search modes: 0 = index() on value, 1 = grep() on value, 2 = grep() on option, 3 = grep on option & value, 4 = diff all, 5 = diff parts +my $search_mode = 2; +my $search_value = ""; +my $help_text_keys = "alt + space: toggle, +/-: increase/decrease, enter: change, ir: reset, iu: unset, v: toggle help bar"; +my $help_text_mouse = "Mouse: left: select, right: toggle/set, right + drag left/right: increase/decrease"; +my %options_iset; + +my %mouse_keys = ("\@chat(perl.$PRGNAME):button1" => "hsignal:iset_mouse", + "\@chat(perl.$PRGNAME):button2*" => "hsignal:iset_mouse", + "\@chat(perl.$PRGNAME):wheelup" => "/repeat 5 /iset **up", + "\@chat(perl.$PRGNAME):wheeldown" => "/repeat 5 /iset **down"); + + +sub iset_title +{ + if ($iset_buffer ne "") + { + my $current_line_counter = ""; + if (weechat::config_boolean($options_iset{"show_current_line"}) == 1) + { + if (@options_names eq 0) + { + $current_line_counter = "0/"; + } + else + { + $current_line_counter = ($current_line + 1) . "/"; + } + } + my $show_filter = ""; + if ($search_mode eq 0) + { + $iset_filter_title = "(value) "; + $show_filter = $search_value; + if ( substr($show_filter,0,1) eq weechat::config_string($options_iset{"value_search_char"}) ) + { + $show_filter = substr($show_filter,1,length($show_filter)); + } + } + elsif ($search_mode eq 1) + { + $iset_filter_title = "(value) "; + $show_filter = "*".$search_value."*"; + } + elsif ($search_mode eq 2) + { + $iset_filter_title = ""; + $filter = "*" if ($filter eq ""); + $show_filter = $filter; + } + elsif ($search_mode == 4 or $search_mode == 5) + { + $iset_filter_title = "diff: "; + $show_filter = "all"; + $show_filter = $search_value if $search_mode == 5; + } + elsif ($search_mode eq 3) + { + $iset_filter_title = "(option) "; + $show_filter = $filter + .weechat::color("default") + ." / (value) " + .weechat::color("yellow") + ."*".$search_value."*"; + } + weechat::buffer_set($iset_buffer, "title", + $iset_filter_title + .weechat::color("yellow") + .$show_filter + .weechat::color("default")." | " + .$current_line_counter + .@options_names + ." | " + .$help_text_keys + ." | " + .$help_text_mouse); + } +} + +sub iset_create_filter +{ + $filter = $_[0]; + if ( $search_mode == 3 ) + { + my @cmd_array = split(/ /,$filter); + my $array_count = @cmd_array; + $filter = $cmd_array[0]; + $filter = $cmd_array[0] . " " . $cmd_array[1] if ( $array_count >2 ); + } + $filter = "$1.*" if ($filter =~ /f (.*)/); # search file + $filter = "*.$1.*" if ($filter =~ /s (.*)/); # search section + if ((substr($filter, 0, 1) ne "*") && (substr($filter, -1, 1) ne "*")) + { + $filter = "*".$filter."*"; + } + if ($iset_buffer ne "") + { + weechat::buffer_set($iset_buffer, "localvar_set_iset_filter", $filter); + } +} + +sub iset_buffer_input +{ + my ($data, $buffer, $string) = ($_[0], $_[1], $_[2]); + + # string begins with space? + return weechat::WEECHAT_RC_OK if (substr($string, 0, 1 ) eq " "); + + if ($string eq "q") + { + weechat::buffer_close($buffer); + return weechat::WEECHAT_RC_OK; + } + $search_value = ""; + my @cmd_array = split(/ /,$string); + my $array_count = @cmd_array; + my $string2 = substr($string, 0, 1); + if ($string2 eq weechat::config_string($options_iset{"value_search_char"}) + or (defined $cmd_array[0] and $cmd_array[0] eq weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})) ) + { + $search_mode = 1; + $search_value = substr($string, 1); + iset_get_values($search_value); + if ($iset_buffer ne "") + { + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); + } + } + # show all diff values + elsif ($string eq "d") + { + $search_mode = 4; +# iset_title(); + iset_create_filter("*"); + iset_get_options("*"); + } + elsif ( $array_count >= 2 and $cmd_array[0] eq "d") + { + $search_mode = 5; + $search_value = substr($cmd_array[1], 0); # cut value_search_char + $search_value = substr($cmd_array[2], 0) if ( $array_count > 2); # cut value_search_char + iset_create_filter($search_value); + iset_get_options($search_value); + + } + else + { + $search_mode = 2; + if ( $array_count >= 2 and $cmd_array[0] ne "f" or $cmd_array[0] ne "s" ) + { + if ( defined $cmd_array[1] and substr($cmd_array[1], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) + or defined $cmd_array[2] and substr($cmd_array[2], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) ) + { + $search_mode = 3; + $search_value = substr($cmd_array[1], 1); # cut value_search_char + $search_value = substr($cmd_array[2], 1) if ( $array_count > 2); # cut value_search_char + } + } + if ( $search_mode == 3) + { + iset_create_filter($string); + iset_get_options($search_value); + } + else + { + iset_create_filter($string); + iset_get_options(""); + } + } + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); + weechat::buffer_clear($buffer); + $current_line = 0; + iset_refresh(); + return weechat::WEECHAT_RC_OK; +} + +sub iset_buffer_close +{ + $iset_buffer = ""; + + return weechat::WEECHAT_RC_OK; +} + +sub iset_init +{ + $current_line = 0; + $iset_buffer = weechat::buffer_search($LANG, $PRGNAME); + if ($iset_buffer eq "") + { + $iset_buffer = weechat::buffer_new($PRGNAME, "iset_buffer_input", "", "iset_buffer_close", ""); + } + else + { + my $new_filter = weechat::buffer_get_string($iset_buffer, "localvar_iset_filter"); + $search_mode = weechat::buffer_get_string($iset_buffer, "localvar_iset_search_mode"); + $search_value = weechat::buffer_get_string($iset_buffer, "localvar_iset_search_value"); + $filter = $new_filter if ($new_filter ne ""); + } + if ($iset_buffer ne "") + { + weechat::buffer_set($iset_buffer, "type", "free"); + iset_title(); + weechat::buffer_set($iset_buffer, "key_bind_ctrl-L", "/iset **refresh"); + weechat::buffer_set($iset_buffer, "key_bind_meta2-A", "/iset **up"); + weechat::buffer_set($iset_buffer, "key_bind_meta2-B", "/iset **down"); + weechat::buffer_set($iset_buffer, "key_bind_meta2-23~", "/iset **left"); + weechat::buffer_set($iset_buffer, "key_bind_meta2-24~" , "/iset **right"); + weechat::buffer_set($iset_buffer, "key_bind_meta- ", "/iset **toggle"); + weechat::buffer_set($iset_buffer, "key_bind_meta-+", "/iset **incr"); + weechat::buffer_set($iset_buffer, "key_bind_meta--", "/iset **decr"); + weechat::buffer_set($iset_buffer, "key_bind_meta-imeta-r", "/iset **reset"); + weechat::buffer_set($iset_buffer, "key_bind_meta-imeta-u", "/iset **unset"); + weechat::buffer_set($iset_buffer, "key_bind_meta-ctrl-J", "/iset **set"); + weechat::buffer_set($iset_buffer, "key_bind_meta-ctrl-M", "/iset **set"); + weechat::buffer_set($iset_buffer, "key_bind_meta-meta2-1~", "/iset **scroll_top"); + weechat::buffer_set($iset_buffer, "key_bind_meta-meta2-4~", "/iset **scroll_bottom"); + weechat::buffer_set($iset_buffer, "key_bind_meta-v", "/iset **toggle_help"); + weechat::buffer_set($iset_buffer, "key_bind_meta-p", "/iset **toggle_show_plugin_desc"); + weechat::buffer_set($iset_buffer, "localvar_set_iset_filter", $filter); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); + } +} + +sub iset_get_options +{ + my $var_value = $_[0]; + $var_value = "" if (not defined $var_value); + $var_value = lc($var_value); + $search_value = $var_value; + @iset_focus = (); + @options_names = (); + @options_parent_names = (); + @options_types = (); + @options_values = (); + @options_default_values = (); + @options_parent_values = (); + @options_is_null = (); + $option_max_length = 0; + my %options_internal = (); + my $i = 0; + my $key; + my $iset_struct; + my %iset_struct; + + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $var_value) if ($search_mode == 3); + + my $infolist = weechat::infolist_get("option", "", $filter); + while (weechat::infolist_next($infolist)) + { + $key = sprintf("%08d", $i); + my $name = weechat::infolist_string($infolist, "full_name"); + my $parent_name = weechat::infolist_string($infolist, "parent_name"); + next if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 0 and index ($name, "plugins.desc.") != -1); + my $type = weechat::infolist_string($infolist, "type"); + my $value = weechat::infolist_string($infolist, "value"); + my $default_value = weechat::infolist_string($infolist, "default_value"); + my $parent_value; + if ($parent_name && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) + { + $parent_value = weechat::infolist_string($infolist, "parent_value"); + } + my $is_null = weechat::infolist_integer($infolist, "value_is_null"); + + if ($search_mode == 3) + { + my $value = weechat::infolist_string($infolist, "value"); + if ( grep /\Q$var_value/,lc($value) ) + { + $options_internal{$name}{"parent_name"} = $parent_name; + $options_internal{$name}{"type"} = $type; + $options_internal{$name}{"value"} = $value; + $options_internal{$name}{"default_value"} = $default_value; + $options_internal{$name}{"parent_value"} = $parent_value; + $options_internal{$name}{"is_null"} = $is_null; + $option_max_length = length($name) if (length($name) > $option_max_length); + $iset_struct{$key} = $options_internal{$name}; + push(@iset_focus, $iset_struct{$key}); + } + } + # search for diff? + elsif ( $search_mode == 4 or $search_mode == 5) + { + if ($value ne $default_value ) + { + $options_internal{$name}{"parent_name"} = $parent_name; + $options_internal{$name}{"type"} = $type; + $options_internal{$name}{"value"} = $value; + $options_internal{$name}{"default_value"} = $default_value; + $options_internal{$name}{"parent_value"} = $parent_value; + $options_internal{$name}{"is_null"} = $is_null; + $option_max_length = length($name) if (length($name) > $option_max_length); + $iset_struct{$key} = $options_internal{$name}; + push(@iset_focus, $iset_struct{$key}); + } + } + else + { + $options_internal{$name}{"parent_name"} = $parent_name; + $options_internal{$name}{"type"} = $type; + $options_internal{$name}{"value"} = $value; + $options_internal{$name}{"default_value"} = $default_value; + $options_internal{$name}{"parent_value"} = $parent_value; + $options_internal{$name}{"is_null"} = $is_null; + $option_max_length = length($name) if (length($name) > $option_max_length); + $iset_struct{$key} = $options_internal{$name}; + push(@iset_focus, $iset_struct{$key}); + } + $i++; + } + weechat::infolist_free($infolist); + + foreach my $name (sort keys %options_internal) + { + push(@options_names, $name); + push(@options_parent_names, $options_internal{$name}{"parent_name"}); + push(@options_types, $options_internal{$name}{"type"}); + push(@options_values, $options_internal{$name}{"value"}); + push(@options_default_values, $options_internal{$name}{"default_value"}); + push(@options_parent_values, $options_internal{$name}{"parent_value"}); + push(@options_is_null, $options_internal{$name}{"is_null"}); + } +} + +sub iset_get_values +{ + my $var_value = $_[0]; + $var_value = lc($var_value); + if (substr($var_value,0,1) eq weechat::config_string($options_iset{"value_search_char"}) and $var_value ne weechat::config_string($options_iset{"value_search_char"})) + { + $var_value = substr($var_value,1,length($var_value)); + $search_mode = 0; + } + iset_search_values($var_value,$search_mode); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $var_value); + $search_value = $var_value; +} +sub iset_search_values +{ + my ($var_value,$search_mode) = ($_[0],$_[1]); + @options_names = (); + @options_parent_names = (); + @options_types = (); + @options_values = (); + @options_default_values = (); + @options_parent_values = (); + @options_is_null = (); + $option_max_length = 0; + my %options_internal = (); + my $i = 0; + my $infolist = weechat::infolist_get("option", "", "*"); + while (weechat::infolist_next($infolist)) + { + my $name = weechat::infolist_string($infolist, "full_name"); + my $parent_name = weechat::infolist_string($infolist, "parent_name"); + next if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 0 and index ($name, "plugins.desc.") != -1); + my $type = weechat::infolist_string($infolist, "type"); + my $is_null = weechat::infolist_integer($infolist, "value_is_null"); + my $value = weechat::infolist_string($infolist, "value"); + my $default_value = weechat::infolist_string($infolist, "default_value"); + my $parent_value; + if ($parent_name && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) + { + $parent_value = weechat::infolist_string($infolist, "parent_value"); + } + if ($search_mode) + { + if ( grep /\Q$var_value/,lc($value) ) + { + $options_internal{$name}{"parent_name"} = $parent_name; + $options_internal{$name}{"type"} = $type; + $options_internal{$name}{"value"} = $value; + $options_internal{$name}{"default_value"} = $default_value; + $options_internal{$name}{"parent_value"} = $parent_value; + $options_internal{$name}{"is_null"} = $is_null; + $option_max_length = length($name) if (length($name) > $option_max_length); + } + } + else + { +# if ($value =~ /\Q$var_value/si) + if (lc($value) eq $var_value) + { + $options_internal{$name}{"parent_name"} = $parent_name; + $options_internal{$name}{"type"} = $type; + $options_internal{$name}{"value"} = $value; + $options_internal{$name}{"default_value"} = $default_value; + $options_internal{$name}{"parent_value"} = $parent_value; + $options_internal{$name}{"is_null"} = $is_null; + $option_max_length = length($name) if (length($name) > $option_max_length); + } + } + $i++; + } + weechat::infolist_free($infolist); + foreach my $name (sort keys %options_internal) + { + push(@options_names, $name); + push(@options_parent_names, $options_internal{$name}{"parent_name"}); + push(@options_types, $options_internal{$name}{"type"}); + push(@options_values, $options_internal{$name}{"value"}); + push(@options_default_values, $options_internal{$name}{"default_value"}); + push(@options_parent_values, $options_internal{$name}{"parent_value"}); + push(@options_is_null, $options_internal{$name}{"is_null"}); + } +} + +sub iset_refresh_line +{ + if ($iset_buffer ne "") + { + my $y = $_[0]; + if ($y <= $#options_names) + { + return if (! defined($options_types[$y])); + my $format = sprintf("%%s%%s%%s %%s %%-7s %%s %%s%%s%%s"); + my $padding; + if ($wee_version_number >= 0x00040200) + { + $padding = " " x ($option_max_length - weechat::strlen_screen($options_names[$y])); + } + else + { + $padding = " " x ($option_max_length - length($options_names[$y])); + } + my $around = ""; + $around = "\"" if ((!$options_is_null[$y]) && ($options_types[$y] eq "string")); + + my $color1 = weechat::color(weechat::config_color($options_iset{"color_option"})); + my $color2 = weechat::color(weechat::config_color($options_iset{"color_type"})); + my $color3 = ""; + my $color4 = ""; + if ($options_is_null[$y]) + { + $color3 = weechat::color(weechat::config_color($options_iset{"color_value_undef"})); + $color4 = weechat::color(weechat::config_color($options_iset{"color_value"})); + } + elsif ($options_values[$y] ne $options_default_values[$y]) + { + $color3 = weechat::color(weechat::config_color($options_iset{"color_value_diff"})); + } + else + { + $color3 = weechat::color(weechat::config_color($options_iset{"color_value"})); + } + if ($y == $current_line) + { + $color1 = weechat::color(weechat::config_color($options_iset{"color_option_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); + $color2 = weechat::color(weechat::config_color($options_iset{"color_type_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); + if ($options_is_null[$y]) + { + $color3 = weechat::color(weechat::config_color($options_iset{"color_value_undef_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); + $color4 = weechat::color(weechat::config_color($options_iset{"color_value_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); + } + elsif ($options_values[$y] ne $options_default_values[$y]) + { + $color3 = weechat::color(weechat::config_color($options_iset{"color_value_diff_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); + } + else + { + $color3 = weechat::color(weechat::config_color($options_iset{"color_value_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); + } + } + my $value = $options_values[$y]; + if ($options_is_null[$y]) + { + $value = "null"; + if ($options_parent_names[$y]) + { + if (defined $options_parent_values[$y]) + { + my $around_parent = ""; + $around_parent = "\"" if ($options_types[$y] eq "string"); + $value .= $color1." -> ".$color4.$around_parent.$options_parent_values[$y].$around_parent; + } + else + { + $value .= $color1." -> ".$color3."null"; + } + } + } + my $strline = sprintf($format, + $color1, $options_names[$y], $padding, + $color2, $options_types[$y], + $color3, $around, $value, $around); + weechat::print_y($iset_buffer, $y, $strline); + } + } +} + +sub iset_refresh +{ + iset_title(); + if (($iset_buffer ne "") && ($#options_names >= 0)) + { + foreach my $y (0 .. $#options_names) + { + iset_refresh_line($y); + } + } + + weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); +} + +sub iset_full_refresh +{ + $iset_buffer = weechat::buffer_search($LANG, $PRGNAME); + if ($iset_buffer ne "") + { + weechat::buffer_clear($iset_buffer) unless defined $_[0]; # iset_full_refresh(1) does a full refresh without clearing buffer + # search for "*" in $filter. + if ($filter =~ m/\*/ and $search_mode == 2) + { + iset_get_options(""); + } + else + { + if ($search_mode == 0) + { + $search_value = "=" . $search_value; + iset_get_values($search_value); + } + elsif ($search_mode == 1) + { + iset_get_values($search_value); + } + elsif ($search_mode == 3) + { + iset_create_filter($filter); + iset_get_options($search_value); + } + } + if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 1) + { + iset_set_current_line($current_line); + }else + { + $current_line = $#options_names if ($current_line > $#options_names); + } + iset_refresh(); + weechat::command($iset_buffer, "/window refresh"); + } +} + +sub iset_set_current_line +{ + my $new_current_line = $_[0]; + if ($new_current_line >= 0) + { + my $old_current_line = $current_line; + $current_line = $new_current_line; + $current_line = $#options_names if ($current_line > $#options_names); + if ($old_current_line != $current_line) + { + iset_refresh_line($old_current_line); + iset_refresh_line($current_line); + weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); + } + } +} + +sub iset_signal_window_scrolled_cb +{ + my ($data, $signal, $signal_data) = ($_[0], $_[1], $_[2]); + if ($iset_buffer ne "") + { + my $infolist = weechat::infolist_get("window", $signal_data, ""); + if (weechat::infolist_next($infolist)) + { + if (weechat::infolist_pointer($infolist, "buffer") eq $iset_buffer) + { + my $old_current_line = $current_line; + my $new_current_line = $current_line; + my $start_line_y = weechat::infolist_integer($infolist, "start_line_y"); + my $chat_height = weechat::infolist_integer($infolist, "chat_height"); + $new_current_line += $chat_height if ($new_current_line < $start_line_y); + $new_current_line -= $chat_height if ($new_current_line >= $start_line_y + $chat_height); + $new_current_line = $start_line_y if ($new_current_line < $start_line_y); + $new_current_line = $start_line_y + $chat_height - 1 if ($new_current_line >= $start_line_y + $chat_height); + iset_set_current_line($new_current_line); + } + } + weechat::infolist_free($infolist); + } + + return weechat::WEECHAT_RC_OK; +} + +sub iset_get_window_number +{ + if ($iset_buffer ne "") + { + my $window = weechat::window_search_with_buffer($iset_buffer); + return "-window ".weechat::window_get_integer ($window, "number")." " if ($window ne ""); + } + return ""; +} + +sub iset_check_line_outside_window +{ + if ($iset_buffer ne "") + { + undef my $infolist; + if ($wee_version_number >= 0x00030500) + { + my $window = weechat::window_search_with_buffer($iset_buffer); + $infolist = weechat::infolist_get("window", $window, "") if $window; + } + else + { + $infolist = weechat::infolist_get("window", "", "current"); + } + if ($infolist) + { + if (weechat::infolist_next($infolist)) + { + my $start_line_y = weechat::infolist_integer($infolist, "start_line_y"); + my $chat_height = weechat::infolist_integer($infolist, "chat_height"); + my $window_number = ""; + if ($wee_version_number >= 0x00030500) + { + $window_number = "-window ".weechat::infolist_integer($infolist, "number")." "; + } + if ($start_line_y > $current_line) + { + weechat::command($iset_buffer, "/window scroll ".$window_number."-".($start_line_y - $current_line)); + } + else + { + if ($start_line_y <= $current_line - $chat_height) + { + weechat::command($iset_buffer, "/window scroll ".$window_number."+".($current_line - $start_line_y - $chat_height + 1)); + + } + } + } + weechat::infolist_free($infolist); + } + } +} + +sub iset_get_option_name_index +{ + my $option_name = $_[0]; + my $index = 0; + while ($index <= $#options_names) + { + return -1 if ($options_names[$index] gt $option_name); + return $index if ($options_names[$index] eq $option_name); + $index++; + } + return -1; +} + +sub iset_refresh_option +{ + my $option_name = $_[0]; + my $index = $_[1]; + my $infolist = weechat::infolist_get("option", "", $option_name); + if ($infolist) + { + weechat::infolist_next($infolist); + if (weechat::infolist_fields($infolist)) + { + $options_parent_names[$index] = weechat::infolist_string($infolist, "parent_name"); + $options_types[$index] = weechat::infolist_string($infolist, "type"); + $options_values[$index] = weechat::infolist_string($infolist, "value"); + $options_default_values[$index] = weechat::infolist_string($infolist, "default_value"); + $options_is_null[$index] = weechat::infolist_integer($infolist, "value_is_null"); + $options_parent_values[$index] = undef; + if ($options_parent_names[$index] + && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) + { + $options_parent_values[$index] = weechat::infolist_string($infolist, "parent_value"); + } + iset_refresh_line($index); + iset_title() if ($option_name eq "iset.look.show_current_line"); + } + else + { + iset_full_refresh(1); # if not found, refresh fully without clearing buffer + weechat::print_y($iset_buffer, $#options_names + 1, ""); + } + weechat::infolist_free($infolist); + } +} + +sub iset_config_cb +{ + my ($data, $option_name, $value) = ($_[0], $_[1], $_[2]); + + if ($iset_buffer ne "") + { + return weechat::WEECHAT_RC_OK if (weechat::info_get("weechat_upgrading", "") eq "1"); + + my $index = iset_get_option_name_index($option_name); + if ($index >= 0) + { + # refresh info about changed option + iset_refresh_option($option_name, $index); + # refresh any other option having this changed option as parent + foreach my $i (0 .. $#options_names) + { + if ($options_parent_names[$i] eq $option_name) + { + iset_refresh_option($options_names[$i], $i); + } + } + } + else + { + iset_full_refresh() if ($option_name ne "weechat.bar.isetbar.hidden"); + } + } + + return weechat::WEECHAT_RC_OK; +} + +sub iset_set_option +{ + my ($option, $value) = ($_[0],$_[1]); + if (defined $option and defined $value) + { + $option = weechat::config_get($option); + weechat::config_option_set($option, $value, 1) if ($option ne ""); + } +} + +sub iset_reset_option +{ + my $option = $_[0]; + if (defined $option) + { + $option = weechat::config_get($option); + weechat::config_option_reset($option, 1) if ($option ne ""); + } +} + +sub iset_unset_option +{ + my $option = $_[0]; + if (defined $option) + { + $option = weechat::config_get($option); + weechat::config_option_unset($option) if ($option ne ""); + } +} + + +sub iset_cmd_cb +{ + my ($data, $buffer, $args) = ($_[0], $_[1], $_[2]); + my $filter_set = 0; +# $search_value = ""; + if (($args ne "") && (substr($args, 0, 2) ne "**")) + { + my @cmd_array = split(/ /,$args); + my $array_count = @cmd_array; + if (substr($args, 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) + or (defined $cmd_array[0] and $cmd_array[0] eq weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})) ) + { + $search_mode = 1; + my $search_value = substr($args, 1); # cut value_search_char + if ($iset_buffer ne "") + { + weechat::buffer_clear($iset_buffer); + weechat::command($iset_buffer, "/window refresh"); + } + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); + iset_init(); + iset_get_values($search_value); + iset_refresh(); + weechat::buffer_set($iset_buffer, "display", "1"); +# $filter = $var_value; + return weechat::WEECHAT_RC_OK; + } + else + { + # f/s option =value + # option =value + $search_mode = 2; # grep on option + if ( $array_count >= 2 and $cmd_array[0] ne "f" or $cmd_array[0] ne "s") + { + if ( defined $cmd_array[1] and substr($cmd_array[1], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) + or defined $cmd_array[2] and substr($cmd_array[2], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) ) + { + $search_mode = 3; # grep on option and value + $search_value = substr($cmd_array[1], 1); # cut value_search_char + $search_value = substr($cmd_array[2], 1) if ( $array_count > 2); # cut value_search_char + } + } + + # show all diff values + if ( $args eq "d") + { + $search_mode = 4; + $search_value = "*"; + $args = $search_value; + } + if ( $array_count >= 2 and $cmd_array[0] eq "d") + { + $search_mode = 5; + $search_value = substr($cmd_array[1], 0); # cut value_search_char + $search_value = substr($cmd_array[2], 0) if ( $array_count > 2); # cut value_search_char + $args = $search_value; + } + + iset_create_filter($args); + $filter_set = 1; + my $ptrbuf = weechat::buffer_search($LANG, $PRGNAME); + + if ($ptrbuf eq "") + { + iset_init(); + iset_get_options($search_value); + iset_full_refresh(); + weechat::buffer_set(weechat::buffer_search($LANG, $PRGNAME), "display", "1"); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); + return weechat::WEECHAT_RC_OK; + } + else + { + iset_get_options($search_value); + iset_full_refresh(); + weechat::buffer_set($ptrbuf, "display", "1"); + } + } + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); + weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); + } + if ($iset_buffer eq "") + { + iset_init(); + iset_get_options(""); + iset_refresh(); + } + else + { +# iset_get_options($search_value); + iset_full_refresh() if ($filter_set); + } + + if ($args eq "") + { + weechat::buffer_set($iset_buffer, "display", "1"); + } + else + { + if ($args eq "**refresh") + { + iset_full_refresh(); + } + if ($args eq "**up") + { + if ($current_line > 0) + { + $current_line--; + iset_refresh_line($current_line + 1); + iset_refresh_line($current_line); + iset_check_line_outside_window(); + } + } + if ($args eq "**down") + { + if ($current_line < $#options_names) + { + $current_line++; + iset_refresh_line($current_line - 1); + iset_refresh_line($current_line); + iset_check_line_outside_window(); + } + } + if ($args eq "**left" && $wee_version_number >= 0x00030600) + { + weechat::command($iset_buffer, "/window scroll_horiz ".iset_get_window_number()."-".weechat::config_integer($options_iset{"scroll_horiz"})."%"); + } + if ($args eq "**right" && $wee_version_number >= 0x00030600) + { + weechat::command($iset_buffer, "/window scroll_horiz ".iset_get_window_number().weechat::config_integer($options_iset{"scroll_horiz"})."%"); + } + if ($args eq "**scroll_top") + { + my $old_current_line = $current_line; + $current_line = 0; + iset_refresh_line ($old_current_line); + iset_refresh_line ($current_line); + iset_title(); + weechat::command($iset_buffer, "/window scroll_top ".iset_get_window_number()); + } + if ($args eq "**scroll_bottom") + { + my $old_current_line = $current_line; + $current_line = $#options_names; + iset_refresh_line ($old_current_line); + iset_refresh_line ($current_line); + iset_title(); + weechat::command($iset_buffer, "/window scroll_bottom ".iset_get_window_number()); + } + if ($args eq "**toggle") + { + if ($options_types[$current_line] eq "boolean") + { + iset_set_option($options_names[$current_line], "toggle"); + } + } + if ($args eq "**incr") + { + if (($options_types[$current_line] eq "integer") + || ($options_types[$current_line] eq "color")) + { + iset_set_option($options_names[$current_line], "++1"); + } + } + if ($args eq "**decr") + { + if (($options_types[$current_line] eq "integer") + || ($options_types[$current_line] eq "color")) + { + iset_set_option($options_names[$current_line], "--1"); + } + } + if ($args eq "**reset") + { + iset_reset_option($options_names[$current_line]); + } + if ($args eq "**unset") + { + iset_unset_option($options_names[$current_line]); + } + if ($args eq "**toggle_help") + { + if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1) + { + weechat::config_option_set($options_iset{"show_help_bar"},0,1); + iset_show_bar(0); + } + else + { + weechat::config_option_set($options_iset{"show_help_bar"},1,1); + iset_show_bar(1); + } + } + if ($args eq "**toggle_show_plugin_desc") + { + if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 1) + { + weechat::config_option_set($options_iset{"show_plugin_description"},0,1); + iset_full_refresh(); + iset_check_line_outside_window(); + iset_title(); + } + else + { + weechat::config_option_set($options_iset{"show_plugin_description"},1,1); + iset_full_refresh(); + iset_check_line_outside_window(); + iset_title(); + } + } + if ($args eq "**set") + { + my $quote = ""; + my $value = $options_values[$current_line]; + if ($options_is_null[$current_line]) + { + $value = ""; + } + else + { + $quote = "\"" if ($options_types[$current_line] eq "string"); + } + $value = " ".$quote.$value.$quote if ($value ne "" or $quote ne ""); + + my $set_command = "/set"; + my $start_index = 5; + if (weechat::config_boolean($options_iset{"use_mute"}) == 1) + { + $set_command = "/mute ".$set_command; + $start_index += 11; + } + $set_command = $set_command." ".$options_names[$current_line].$value; + my $pos_space = index($set_command, " ", $start_index); + if ($pos_space < 0) + { + $pos_space = 9999; + } + else + { + $pos_space = $pos_space + 1; + $pos_space = $pos_space + 1 if ($quote ne ""); + } + weechat::buffer_set($iset_buffer, "input", $set_command); + weechat::buffer_set($iset_buffer, "input_pos", "".$pos_space); + } + } + weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); + return weechat::WEECHAT_RC_OK; +} + +sub iset_get_help +{ + my ($redraw) = ($_[0]); + + return '' if (weechat::config_boolean($options_iset{"show_help_bar"}) == 0); + + if (not defined $options_names[$current_line]) + { + return "No option selected. Set a new filter using command line (use '*' to see all options)"; + } + if ($options_name_copy eq $options_names[$current_line] and not defined $redraw) + { + return $description; + } + $options_name_copy = $options_names[$current_line]; + my $optionlist =""; + $optionlist = weechat::infolist_get("option", "", $options_names[$current_line]); + weechat::infolist_next($optionlist); + my $full_name = weechat::infolist_string($optionlist,"full_name"); + my $option_desc = ""; + my $option_default_value = ""; + my $option_range = ""; + my $possible_values = ""; + my $re = qq(\Q$full_name); + if (grep (/^$re$/,$options_names[$current_line])) + { + $option_desc = weechat::infolist_string($optionlist, "description_nls"); + $option_desc = weechat::infolist_string($optionlist, "description") if ($option_desc eq ""); + $option_desc = "No help found" if ($option_desc eq ""); + $option_default_value = weechat::infolist_string($optionlist, "default_value"); + $possible_values = weechat::infolist_string($optionlist, "string_values") if (weechat::infolist_string($optionlist, "string_values") ne ""); + if ((weechat::infolist_string($optionlist, "type") eq "integer") && ($possible_values eq "")) + { + $option_range = weechat::infolist_integer($optionlist, "min") + ." .. ".weechat::infolist_integer($optionlist, "max"); + } + } + weechat::infolist_free($optionlist); + iset_title(); + + $description = weechat::color(weechat::config_color($options_iset{"color_help_option_name"})).$options_names[$current_line] + .weechat::color("bar_fg").": " + .weechat::color(weechat::config_color($options_iset{"color_help_text"})).$option_desc; + + # show additional infos like default value and possible values + + if (weechat::config_boolean($options_iset{"show_help_extra_info"}) == 1) + { + $description .= + weechat::color("bar_delim")." [" + .weechat::color("bar_fg")."default: " + .weechat::color("bar_delim")."\"" + .weechat::color(weechat::config_color($options_iset{"color_help_default_value"})).$option_default_value + .weechat::color("bar_delim")."\""; + if ($option_range ne "") + { + $description .= weechat::color("bar_fg").", values: ".$option_range; + } + if ($possible_values ne "") + { + $possible_values =~ s/\|/", "/g; # replace '|' to '", "' + $description .= weechat::color("bar_fg").", values: ". "\"" . $possible_values . "\""; + + } + $description .= weechat::color("bar_delim")."]"; + } + return $description; +} + +sub iset_check_condition_isetbar_cb +{ + my ($data, $modifier, $modifier_data, $string) = ($_[0], $_[1], $_[2], $_[3]); + my $buffer = weechat::window_get_pointer($modifier_data, "buffer"); + if ($buffer ne "") + { + if ((weechat::buffer_get_string($buffer, "plugin") eq $LANG) + && (weechat::buffer_get_string($buffer, "name") eq $PRGNAME)) + { + return "1"; + } + } + return "0"; +} + +sub iset_show_bar +{ + my $show = $_[0]; + my $barhidden = weechat::config_get("weechat.bar.isetbar.hidden"); + if ($barhidden) + { + if ($show) + { + if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1) + { + if (weechat::config_boolean($barhidden)) + { + weechat::config_option_set($barhidden, 0, 1); + } + } + } + else + { + if (!weechat::config_boolean($barhidden)) + { + weechat::config_option_set($barhidden, 1, 1); + } + } + } +} + +sub iset_signal_buffer_switch_cb +{ + my $buffer_pointer = $_[2]; + my $show_bar = 0; + $show_bar = 1 if (weechat::buffer_get_integer($iset_buffer, "num_displayed") > 0); + iset_show_bar($show_bar); + iset_check_line_outside_window() if ($buffer_pointer eq $iset_buffer); + return weechat::WEECHAT_RC_OK; +} + +sub iset_item_cb +{ + return iset_get_help(); +} + +sub iset_upgrade_ended +{ + iset_full_refresh(); +} + +sub iset_end +{ + # when script is unloaded, we hide bar + iset_show_bar(0); +} + +# -------------------------------[ mouse support ]------------------------------------- + +sub hook_focus_iset_cb +{ + my %info = %{$_[1]}; + my $bar_item_line = int($info{"_bar_item_line"}); + undef my $hash; + if (($info{"_buffer_name"} eq $PRGNAME) && $info{"_buffer_plugin"} eq $LANG && ($bar_item_line >= 0) && ($bar_item_line <= $#iset_focus)) + { + $hash = $iset_focus[$bar_item_line]; + } + else + { + $hash = {}; + my $hash_focus = $iset_focus[0]; + foreach my $key (keys %$hash_focus) + { + $hash->{$key} = "?"; + } + } + return $hash; +} + +# _chat_line_y contains selected line +sub iset_hsignal_mouse_cb +{ + my ($data, $signal, %hash) = ($_[0], $_[1], %{$_[2]}); + + return weechat::WEECHAT_RC_OK unless (@options_types); + + if ($hash{"_buffer_name"} eq $PRGNAME && ($hash{"_buffer_plugin"} eq $LANG)) + { + if ($hash{"_key"} eq "button1") + { + iset_set_current_line($hash{"_chat_line_y"}); + } + elsif ($hash{"_key"} eq "button2") + { + if ($options_types[$hash{"_chat_line_y"}] eq "boolean") + { + iset_set_option($options_names[$hash{"_chat_line_y"}], "toggle"); + iset_set_current_line($hash{"_chat_line_y"}); + } + elsif ($options_types[$hash{"_chat_line_y"}] eq "string") + { + iset_set_current_line($hash{"_chat_line_y"}); + weechat::command("", "/$PRGNAME **set"); + } + } + elsif ($hash{"_key"} eq "button2-gesture-left" or $hash{"_key"} eq "button2-gesture-left-long") + { + if ($options_types[$hash{"_chat_line_y"}] eq "integer" or ($options_types[$hash{"_chat_line_y"}] eq "color")) + { + iset_set_current_line($hash{"_chat_line_y"}); + my $distance = distance($hash{"_chat_line_x"},$hash{"_chat_line_x2"}); + weechat::command("", "/repeat $distance /$PRGNAME **decr"); + } + } + elsif ($hash{"_key"} eq "button2-gesture-right" or $hash{"_key"} eq "button2-gesture-right-long") + { + if ($options_types[$hash{"_chat_line_y"}] eq "integer" or ($options_types[$hash{"_chat_line_y"}] eq "color")) + { + iset_set_current_line($hash{"_chat_line_y"}); + my $distance = distance($hash{"_chat_line_x"},$hash{"_chat_line_x2"}); + weechat::command("", "/repeat $distance /$PRGNAME **incr"); + } + } + } + window_switch(); +} + +sub window_switch +{ + my $current_window = weechat::current_window(); + my $dest_window = weechat::window_search_with_buffer(weechat::buffer_search("perl","iset")); + return 0 if ($dest_window eq "" or $current_window eq $dest_window); + + my $infolist = weechat::infolist_get("window", $dest_window, ""); + weechat::infolist_next($infolist); + my $number = weechat::infolist_integer($infolist, "number"); + weechat::infolist_free($infolist); + weechat::command("","/window " . $number); +} + +sub distance +{ + my ($x1,$x2) = ($_[0], $_[1]); + my $distance; + $distance = $x1 - $x2; + $distance = abs($distance); + if ($distance > 0) + { + use integer; + $distance = $distance / 3; + $distance = 1 if ($distance == 0); + } + elsif ($distance == 0) + { + $distance = 1; + } + return $distance; +} + +# -----------------------------------[ config ]--------------------------------------- + +sub iset_config_init +{ + $iset_config_file = weechat::config_new($ISET_CONFIG_FILE_NAME,"iset_config_reload_cb",""); + return if ($iset_config_file eq ""); + + # section "color" + my $section_color = weechat::config_new_section($iset_config_file,"color", 0, 0, "", "", "", "", "", "", "", "", "", ""); + if ($section_color eq "") + { + weechat::config_free($iset_config_file); + return; + } + $options_iset{"color_option"} = weechat::config_new_option( + $iset_config_file, $section_color, + "option", "color", "Color for option name in iset buffer", "", 0, 0, + "default", "default", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_option_selected"} = weechat::config_new_option( + $iset_config_file, $section_color, + "option_selected", "color", "Color for selected option name in iset buffer", "", 0, 0, + "white", "white", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_type"} = weechat::config_new_option( + $iset_config_file, $section_color, + "type", "color", "Color for option type (integer, boolean, string)", "", 0, 0, + "brown", "brown", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_type_selected"} = weechat::config_new_option( + $iset_config_file, $section_color, + "type_selected", "color", "Color for selected option type (integer, boolean, string)", "", 0, 0, + "yellow", "yellow", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_value"} = weechat::config_new_option( + $iset_config_file, $section_color, + "value", "color", "Color for option value", "", 0, 0, + "cyan", "cyan", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_value_selected"} = weechat::config_new_option( + $iset_config_file, $section_color, + "value_selected", "color", "Color for selected option value", "", 0, 0, + "lightcyan", "lightcyan", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_value_diff"} = weechat::config_new_option( + $iset_config_file, $section_color, + "value_diff", "color", "Color for option value different from default", "", 0, 0, + "magenta", "magenta", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_value_diff_selected"} = weechat::config_new_option( + $iset_config_file, $section_color, + "value_diff_selected", "color", "Color for selected option value different from default", "", 0, 0, + "lightmagenta", "lightmagenta", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_value_undef"} = weechat::config_new_option( + $iset_config_file, $section_color, + "value_undef", "color", "Color for option value undef", "", 0, 0, + "green", "green", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_value_undef_selected"} = weechat::config_new_option( + $iset_config_file, $section_color, + "value_undef_selected", "color", "Color for selected option value undef", "", 0, 0, + "lightgreen", "lightgreen", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_bg_selected"} = weechat::config_new_option( + $iset_config_file, $section_color, + "bg_selected", "color", "Background color for current selected option", "", 0, 0, + "red", "red", 0, "", "", "full_refresh_cb", "", "", ""); + $options_iset{"color_help_option_name"} = weechat::config_new_option( + $iset_config_file, $section_color, + "help_option_name", "color", "Color for option name in help-bar", "", 0, 0, + "white", "white", 0, "", "", "bar_refresh", "", "", ""); + $options_iset{"color_help_text"} = weechat::config_new_option( + $iset_config_file, $section_color, + "help_text", "color", "Color for option description in help-bar", "", 0, 0, + "default", "default", 0, "", "", "bar_refresh", "", "", ""); + $options_iset{"color_help_default_value"} = weechat::config_new_option( + $iset_config_file, $section_color, + "help_default_value", "color", "Color for default option value in help-bar", "", 0, 0, + "green", "green", 0, "", "", "bar_refresh", "", "", ""); + + # section "help" + my $section_help = weechat::config_new_section($iset_config_file,"help", 0, 0, "", "", "", "", "", "", "", "", "", ""); + if ($section_help eq "") + { + weechat::config_free($iset_config_file); + return; + } + $options_iset{"show_help_bar"} = weechat::config_new_option( + $iset_config_file, $section_help, + "show_help_bar", "boolean", "Show help bar", "", 0, 0, + "on", "on", 0, "", "", "toggle_help_cb", "", "", ""); + $options_iset{"show_help_extra_info"} = weechat::config_new_option( + $iset_config_file, $section_help, + "show_help_extra_info", "boolean", "Show additional information in help bar (default value, max./min. value) ", "", 0, 0, + "on", "on", 0, "", "", "", "", "", ""); + $options_iset{"show_plugin_description"} = weechat::config_new_option( + $iset_config_file, $section_help, + "show_plugin_description", "boolean", "Show plugin description in iset buffer", "", 0, 0, + "off", "off", 0, "", "", "full_refresh_cb", "", "", ""); + + # section "look" + my $section_look = weechat::config_new_section($iset_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", ""); + if ($section_look eq "") + { + weechat::config_free($iset_config_file); + return; + } + $options_iset{"value_search_char"} = weechat::config_new_option( + $iset_config_file, $section_look, + "value_search_char", "string", "Trigger char to tell iset to search for value instead of option (for example: =red)", "", 0, 0, + "=", "=", 0, "", "", "", "", "", ""); + $options_iset{"scroll_horiz"} = weechat::config_new_option( + $iset_config_file, $section_look, + "scroll_horiz", "integer", "scroll content of iset buffer n%", "", 1, 100, + "10", "10", 0, "", "", "", "", "", ""); + $options_iset{"show_current_line"} = weechat::config_new_option( + $iset_config_file, $section_look, + "show_current_line", "boolean", "show current line in title bar.", "", 0, 0, + "on", "on", 0, "", "", "", "", "", ""); + $options_iset{"use_mute"} = weechat::config_new_option( + $iset_config_file, $section_look, + "use_mute", "boolean", "/mute command will be used in input bar", "", 0, 0, + "off", "off", 0, "", "", "", "", "", ""); +} + +sub iset_config_reload_cb +{ + my ($data,$config_file) = ($_[0], $_[1]); + return weechat::config_reload($config_file) +} + +sub iset_config_read +{ + return weechat::config_read($iset_config_file) if ($iset_config_file ne ""); +} + +sub iset_config_write +{ + return weechat::config_write($iset_config_file) if ($iset_config_file ne ""); +} + +sub full_refresh_cb +{ + iset_full_refresh(); + return weechat::WEECHAT_RC_OK; +} + +sub bar_refresh +{ + iset_get_help(1); + weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); + return weechat::WEECHAT_RC_OK; +} + +sub toggle_help_cb +{ + my $value = weechat::config_boolean($options_iset{"show_help_bar"}); + iset_show_bar($value); + return weechat::WEECHAT_RC_OK; +} + +# -----------------------------------[ main ]----------------------------------------- + +weechat::register($PRGNAME, $AUTHOR, $VERSION, $LICENSE, + $DESCR, "iset_end", ""); + +$wee_version_number = weechat::info_get("version_number", "") || 0; + +iset_config_init(); +iset_config_read(); + +weechat::hook_command($PRGNAME, "Interactive set", "d || f || s
|| [=][=]", + "d : show only changed options\n". + "f file : show options for a file\n". + "s section: show options for a section\n". + "text : show options with 'text' in name\n". + weechat::config_string($options_iset{"value_search_char"})."text : show options with 'text' in value\n". + weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})."text : show options with exact 'text' in value\n\n". + "Keys for iset buffer:\n". + "f11,f12 : move iset content left/right\n". + "up,down : move one option up/down\n". + "pgup,pdwn : move one page up/down\n". + "home,end : move to first/last option\n". + "ctrl+'L' : refresh options and screen\n". + "alt+space : toggle boolean on/off\n". + "alt+'+' : increase value (for integer or color)\n". + "alt+'-' : decrease value (for integer or color)\n". + "alt+'i',alt+'r': reset value of option\n". + "alt+'i',alt+'u': unset option\n". + "alt+enter : set new value for option (edit it with command line)\n". + "text,enter : set a new filter using command line (use '*' to see all options)\n". + "alt+'v' : toggle help bar on/off\n". + "alt+'p' : toggle option \"show_plugin_description\" on/off\n". + "q : as input in iset buffer to close it\n". + "\n". + "Mouse actions:\n". + "wheel up/down : move cursor up/down\n". + "left button : select an option from list\n". + "right button : toggle boolean (on/off) or set a new value for option (edit it with command line)\n". + "right button + drag left/right: increase/decrease value (for integer or color)\n". + "\n". + "Examples:\n". + " show changed options in 'aspell' plugin\n". + " /iset d aspell\n". + " show options for file 'irc'\n". + " /iset f irc\n". + " show options for section 'look'\n". + " /iset s look\n". + " show all options with text 'nicklist' in name\n". + " /iset nicklist\n". + " show all values which contain 'red'. ('" . weechat::config_string($options_iset{"value_search_char"}) . "' is a trigger char).\n". + " /iset ". weechat::config_string($options_iset{"value_search_char"}) ."red\n". + " show all values which hit 'off'. ('" . weechat::config_string($options_iset{"value_search_char"}) . weechat::config_string($options_iset{"value_search_char"}) . "' is a trigger char).\n". + " /iset ". weechat::config_string($options_iset{"value_search_char"}) . weechat::config_string($options_iset{"value_search_char"}) ."off\n". + " show options for file 'weechat' which contains value 'off'\n". + " /iset f weechat ".weechat::config_string($options_iset{"value_search_char"})."off\n". + "", + "", "iset_cmd_cb", ""); +weechat::hook_signal("upgrade_ended", "iset_upgrade_ended", ""); +weechat::hook_signal("window_scrolled", "iset_signal_window_scrolled_cb", ""); +weechat::hook_signal("buffer_switch", "iset_signal_buffer_switch_cb",""); +weechat::bar_item_new("isetbar_help", "iset_item_cb", ""); +weechat::bar_new("isetbar", "on", "0", "window", "", "top", "horizontal", + "vertical", "3", "3", "default", "cyan", "default", "1", + "isetbar_help"); +weechat::hook_modifier("bar_condition_isetbar", "iset_check_condition_isetbar_cb", ""); +weechat::hook_config("*", "iset_config_cb", ""); +$iset_buffer = weechat::buffer_search($LANG, $PRGNAME); +iset_init() if ($iset_buffer ne ""); + +if ($wee_version_number >= 0x00030600) +{ + weechat::hook_focus("chat", "hook_focus_iset_cb", ""); + weechat::hook_hsignal($PRGNAME."_mouse", "iset_hsignal_mouse_cb", ""); + weechat::key_bind("mouse", \%mouse_keys); +} diff --git a/.weechat/plugins.conf b/.weechat/plugins.conf index c0b3234..c6975b7 100644 --- a/.weechat/plugins.conf +++ b/.weechat/plugins.conf @@ -4,6 +4,19 @@ [var] fifo.fifo = "on" +perl.check_license = "off" +perl.highmon.alignment = "channel" +perl.highmon.away_only = "off" +perl.highmon.color_buf = "on" +perl.highmon.first_run = "true" +perl.highmon.hotlist_show = "off" +perl.highmon.logging = "off" +perl.highmon.merge_private = "off" +perl.highmon.nick_prefix = "<" +perl.highmon.nick_suffix = ">" +perl.highmon.output = "buffer" +perl.highmon.short_names = "off" +python.check_license = "off" tcl.check_license = "off" [desc] diff --git a/.weechat/python/autoload/autosort.py b/.weechat/python/autoload/autosort.py new file mode 120000 index 0000000..1850897 --- /dev/null +++ b/.weechat/python/autoload/autosort.py @@ -0,0 +1 @@ +../autosort.py \ No newline at end of file diff --git a/.weechat/python/autoload/colorize_nicks.py b/.weechat/python/autoload/colorize_nicks.py new file mode 120000 index 0000000..3ee34e9 --- /dev/null +++ b/.weechat/python/autoload/colorize_nicks.py @@ -0,0 +1 @@ +../colorize_nicks.py \ No newline at end of file diff --git a/.weechat/python/autosort.py b/.weechat/python/autosort.py new file mode 100644 index 0000000..6854b9e --- /dev/null +++ b/.weechat/python/autosort.py @@ -0,0 +1,885 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2014 Maarten de Vries +# +# 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 . +# + +# +# Autosort automatically keeps your buffers sorted and grouped by server. +# You can define your own sorting rules. See /help autosort for more details. +# +# http://github.com/de-vri.es/weechat-autosort +# + +# +# Changelog: +# 2.8: +# * Fix compatibility with python 3 regarding unicode handling. +# 2.7: +# * Fix sorting of buffers with spaces in their name. +# 2.6: +# * Ignore case in rules when doing case insensitive sorting. +# 2.5: +# * Fix handling unicode buffer names. +# * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on. +# 2.4: +# * Make script python3 compatible. +# 2.3: +# * Fix sorting items without score last (regressed in 2.2). +# 2.2: +# * Add configuration option for signals that trigger a sort. +# * Add command to manually trigger a sort (/autosort sort). +# * Add replacement patterns to apply before sorting. +# 2.1: +# * Fix some minor style issues. +# 2.0: +# * Allow for custom sort rules. +# + + +import weechat +import re +import json + +SCRIPT_NAME = 'autosort' +SCRIPT_AUTHOR = 'Maarten de Vries ' +SCRIPT_VERSION = '2.8' +SCRIPT_LICENSE = 'GPL3' +SCRIPT_DESC = 'Automatically or manually keep your buffers sorted and grouped by server.' + + +config = None +hooks = [] + +class HumanReadableError(Exception): + pass + + +def parse_int(arg, arg_name = 'argument'): + ''' Parse an integer and provide a more human readable error. ''' + arg = arg.strip() + try: + return int(arg) + except ValueError: + raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg)) + + +class Pattern: + ''' A simple glob-like pattern for matching buffer names. ''' + + def __init__(self, pattern, case_sensitive): + ''' Construct a pattern from a string. ''' + escaped = False + char_class = 0 + chars = '' + regex = '' + for c in pattern: + if escaped and char_class: + escaped = False + chars += re.escape(c) + elif escaped: + escaped = False + regex += re.escape(c) + elif c == '\\': + escaped = True + elif c == '*' and not char_class: + regex += '[^.]*' + elif c == '?' and not char_class: + regex += '[^.]' + elif c == '[' and not char_class: + char_class = 1 + chars = '' + elif c == '^' and char_class and not chars: + chars += '^' + elif c == ']' and char_class and chars not in ('', '^'): + char_class = False + regex += '[' + chars + ']' + elif c == '-' and char_class: + chars += '-' + elif char_class: + chars += re.escape(c) + else: + regex += re.escape(c) + + if char_class: + raise ValueError("unmatched opening '['") + if escaped: + raise ValueError("unexpected trailing '\\'") + + if case_sensitive: + self.regex = re.compile('^' + regex + '$') + else: + self.regex = re.compile('^' + regex + '$', flags = re.IGNORECASE) + self.pattern = pattern + + def match(self, input): + ''' Match the pattern against a string. ''' + return self.regex.match(input) + + +class FriendlyList(object): + ''' A list with human readable errors. ''' + + def __init__(self): + self.__data = [] + + def raw(self): + return self.__data + + def append(self, value): + ''' Add a rule to the list. ''' + self.__data.append(value) + + def insert(self, index, value): + ''' Add a rule to the list. ''' + if not 0 <= index <= len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}], got {1}.'.format(len(self), index)) + self.__data.insert(index, value) + + def pop(self, index): + ''' Remove a rule from the list and return it. ''' + if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index)) + return self.__data.pop(index) + + def move(self, index_a, index_b): + ''' Move a rule to a new position in the list. ''' + self.insert(index_b, self.pop(index_a)) + + def swap(self, index_a, index_b): + ''' Swap two elements in the list. ''' + self[index_a], self[index_b] = self[index_b], self[index_a] + + def __len__(self): + return len(self.__data) + + def __getitem__(self, index): + if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index)) + return self.__data[index] + + def __setitem__(self, index, value): + if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index)) + self.__data[index] = value + + def __iter__(self): + return iter(self.__data) + + +class RuleList(FriendlyList): + ''' A list of rules to test buffer names against. ''' + rule_regex = re.compile(r'^(.*)=\s*([+-]?[^=]*)$') + + def __init__(self, rules): + ''' Construct a RuleList from a list of rules. ''' + super(RuleList, self).__init__() + for rule in rules: self.append(rule) + + def get_score(self, name): + ''' Get the sort score of a partial name according to a rule list. ''' + for rule in self: + if rule[0].match(name): return rule[1] + return 999999999 + + def encode(self): + ''' Encode the rules for storage. ''' + return json.dumps(list(map(lambda x: (x[0].pattern, x[1]), self))) + + @staticmethod + def decode(blob, case_sensitive): + ''' Parse rules from a string blob. ''' + result = [] + + try: + decoded = json.loads(blob) + except ValueError: + log('Invalid rules: expected JSON encoded list of pairs, got "{0}".'.format(blob)) + return [], 0 + + for rule in decoded: + # Rules must be a pattern,score pair. + if len(rule) != 2: + log('Invalid rule: expected (pattern, score), got "{0}". Rule ignored.'.format(rule)) + continue + + # Rules must have a valid pattern. + try: + pattern = Pattern(rule[0], case_sensitive) + except ValueError as e: + log('Invalid pattern: {0} in "{1}". Rule ignored.'.format(e, rule[0])) + continue + + # Rules must have a valid score. + try: + score = int(rule[1]) + except ValueError as e: + log('Invalid score: expected an integer, got "{0}". Rule ignored.'.format(score)) + continue + + result.append((pattern, score)) + + return RuleList(result) + + @staticmethod + def parse_rule(arg, case_sensitive): + ''' Parse a rule argument. ''' + arg = arg.strip() + match = RuleList.rule_regex.match(arg) + if not match: + raise HumanReadableError('Invalid rule: expected " = ", got "{0}".'.format(arg)) + + pattern = match.group(1).strip() + try: + pattern = Pattern(pattern, case_sensitive) + except ValueError as e: + raise HumanReadableError('Invalid pattern: {0} in "{1}".'.format(e, pattern)) + + score = parse_int(match.group(2), 'score') + return (pattern, score) + + +def decode_replacements(blob): + ''' Decode a replacement list encoded as JSON. ''' + result = FriendlyList() + try: + decoded = json.loads(blob) + except ValueError: + log('Invalid replacement list: expected JSON encoded list of pairs, got "{0}".'.format(blob)) + return [], 0 + + for replacement in decoded: + # Replacements must be a (string, string) pair. + if len(replacement) != 2: + log('Invalid replacement pattern: expected (pattern, replacement), got "{0}". Replacement ignored.'.format(rule)) + continue + result.append(replacement) + + return result + + +def encode_replacements(replacements): + ''' Encode a list of replacement patterns as JSON. ''' + return json.dumps(replacements.raw()) + + +class Config: + ''' The autosort configuration. ''' + + default_rules = json.dumps([ + ('core', 0), + ('irc', 2), + ('*', 1), + + ('irc.irc_raw', 0), + ('irc.server', 1), + ]) + + default_replacements = '[]' + default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed' + + def __init__(self, filename): + ''' Initialize the configuration. ''' + + self.filename = filename + self.config_file = weechat.config_new(self.filename, '', '') + self.sorting_section = None + + self.case_sensitive = False + self.group_irc = True + self.rules = [] + self.replacements = [] + self.signals = [] + self.sort_on_config = True + + self.__case_sensitive = None + self.__group_irc = None + self.__rules = None + self.__replacements = None + self.__signals = None + self.__sort_on_config = None + + if not self.config_file: + log('Failed to initialize configuration file "{0}".'.format(self.filename)) + return + + self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '') + + if not self.sorting_section: + log('Failed to initialize section "sorting" of configuration file.') + weechat.config_free(self.config_file) + return + + self.__case_sensitive = weechat.config_new_option( + self.config_file, self.sorting_section, + 'case_sensitive', 'boolean', + 'If this option is on, sorting is case sensitive.', + '', 0, 0, 'off', 'off', 0, + '', '', '', '', '', '' + ) + + self.__group_irc = weechat.config_new_option( + self.config_file, self.sorting_section, + 'group_irc', 'boolean', + 'If this option is on, the script pretends that IRC channel/private buffers are renamed to "irc.server.{network}.{channel}" rather than "irc.{network}.{channel}".' + + 'This ensures that these buffers are grouped with their respective server buffer.', + '', 0, 0, 'on', 'on', 0, + '', '', '', '', '', '' + ) + + self.__rules = weechat.config_new_option( + self.config_file, self.sorting_section, + 'rules', 'string', + 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.', + '', 0, 0, Config.default_rules, Config.default_rules, 0, + '', '', '', '', '', '' + ) + + self.__replacements = weechat.config_new_option( + self.config_file, self.sorting_section, + 'replacements', 'string', + 'An ordered list of replacement patterns to use on buffer name components, encoded as JSON. See /help autosort for commands to manipulate these replacements.', + '', 0, 0, Config.default_replacements, Config.default_replacements, 0, + '', '', '', '', '', '' + ) + + self.__signals = weechat.config_new_option( + self.config_file, self.sorting_section, + 'signals', 'string', + 'The signals that will cause autosort to resort your buffer list. Seperate signals with spaces.', + '', 0, 0, Config.default_signals, Config.default_signals, 0, + '', '', '', '', '', '' + ) + + self.__sort_on_config = weechat.config_new_option( + self.config_file, self.sorting_section, + 'sort_on_config_change', 'boolean', + 'Decides if the buffer list should be sorted when autosort configuration changes.', + '', 0, 0, 'on', 'on', 0, + '', '', '', '', '', '' + ) + + if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK: + log('Failed to load configuration file.') + + if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK: + log('Failed to write configuration file.') + + self.reload() + + def reload(self): + ''' Load configuration variables. ''' + + self.case_sensitive = weechat.config_boolean(self.__case_sensitive) + self.group_irc = weechat.config_boolean(self.__group_irc) + + rules_blob = weechat.config_string(self.__rules) + replacements_blob = weechat.config_string(self.__replacements) + signals_blob = weechat.config_string(self.__signals) + + self.rules = RuleList.decode(rules_blob, self.case_sensitive) + self.replacements = decode_replacements(replacements_blob) + self.signals = signals_blob.split() + self.sort_on_config = weechat.config_boolean(self.__sort_on_config) + + def save_rules(self, run_callback = True): + ''' Save the current rules to the configuration. ''' + weechat.config_option_set(self.__rules, RuleList.encode(self.rules), run_callback) + + def save_replacements(self, run_callback = True): + ''' Save the current replacement patterns to the configuration. ''' + weechat.config_option_set(self.__replacements, encode_replacements(self.replacements), run_callback) + + +def pad(sequence, length, padding = None): + ''' Pad a list until is has a certain length. ''' + return sequence + [padding] * max(0, (length - len(sequence))) + + +def log(message, buffer = 'NULL'): + weechat.prnt(buffer, 'autosort: {0}'.format(message)) + + +def get_buffers(): + ''' Get a list of all the buffers in weechat. ''' + buffers = [] + + buffer_list = weechat.infolist_get('buffer', '', '') + + while weechat.infolist_next(buffer_list): + name = weechat.infolist_string (buffer_list, 'full_name') + number = weechat.infolist_integer(buffer_list, 'number') + + # Buffer is merged with one we already have in the list, skip it. + if number <= len(buffers): + continue + buffers.append((name, number - 1)) + + weechat.infolist_free(buffer_list) + return buffers + + +def preprocess(buffer, config): + ''' + Preprocess a buffers names. + ''' + + # Make sure the name is a unicode string. + # On python3 this is a NOP since the string type is already decoded as UTF-8. + if isinstance(buffer, bytes): + buffer = buffer.decode('utf-8') + + if not config.case_sensitive: + buffer = buffer.lower() + + for replacement in config.replacements: + buffer = buffer.replace(replacement[0], replacement[1]) + + buffer = buffer.split('.') + if config.group_irc and len(buffer) >= 2 and buffer[0] == 'irc' and buffer[1] not in ('server', 'irc_raw'): + buffer.insert(1, 'server') + + return buffer + + +def buffer_sort_key(rules): + ''' Create a sort key function for a buffer list from a rule list. ''' + def key(buffer): + result = [] + name = '' + for word in preprocess(buffer[0], config): + name += ('.' if name else '') + word + result.append((rules.get_score(name), word)) + return result + + return key + + +def apply_buffer_order(order): + ''' Sort the buffers in weechat according to the given order. ''' + indices = list(order) + reverse = [0] * len(indices) + for i, index in enumerate(indices): + reverse[index] = i + + for i in range(len(indices)): + wanted = indices[i] + if wanted == i: continue + # Weechat buffers are 1-indexed, but our indices aren't. + weechat.command('', '/buffer swap {0} {1}'.format(i + 1, wanted + 1)) + indices[reverse[i]] = wanted + reverse[wanted] = reverse[i] + + +def split_args(args, expected, optional = 0): + ''' Split an argument string in the desired number of arguments. ''' + split = args.split(' ', expected - 1) + if (len(split) < expected): + raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split))) + return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '') + + +def command_sort(buffer, command, args): + ''' Sort the buffers and print a confirmation. ''' + on_buffers_changed() + log("Finished sorting buffers.", buffer) + return weechat.WEECHAT_RC_OK + + +def command_rule_list(buffer, command, args): + ''' Show the list of sorting rules. ''' + output = 'Sorting rules:\n' + for i, rule in enumerate(config.rules): + output += ' {0}: {1} = {2}\n'.format(i, rule[0].pattern, rule[1]) + if not len(config.rules): + output += ' No sorting rules configured.\n' + log(output, buffer) + + return weechat.WEECHAT_RC_OK + + +def command_rule_add(buffer, command, args): + ''' Add a rule to the rule list. ''' + rule = RuleList.parse_rule(args, config.case_sensitive) + + config.rules.append(rule) + config.save_rules() + command_rule_list(buffer, command, '') + + return weechat.WEECHAT_RC_OK + + +def command_rule_insert(buffer, command, args): + ''' Insert a rule at the desired position in the rule list. ''' + index, rule = split_args(args, 2) + index = parse_int(index, 'index') + rule = RuleList.parse_rule(rule, config.case_sensitive) + + config.rules.insert(index, rule) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_update(buffer, command, args): + ''' Update a rule in the rule list. ''' + index, rule = split_args(args, 2) + index = parse_int(index, 'index') + rule = RuleList.parse_rule(rule, config.case_sensitive) + + config.rules[index] = rule + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_delete(buffer, command, args): + ''' Delete a rule from the rule list. ''' + index = args.strip() + index = parse_int(index, 'index') + + config.rules.pop(index) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_move(buffer, command, args): + ''' Move a rule to a new position. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.rules.move(index_a, index_b) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_rule_swap(buffer, command, args): + ''' Swap two rules. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.rules.swap(index_a, index_b) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_list(buffer, command, args): + ''' Show the list of sorting rules. ''' + output = 'Replacement patterns:\n' + for i, pattern in enumerate(config.replacements): + output += ' {0}: {1} -> {2}\n'.format(i, pattern[0], pattern[1]) + if not len(config.replacements): + output += ' No replacement patterns configured.' + log(output, buffer) + + return weechat.WEECHAT_RC_OK + + +def command_replacement_add(buffer, command, args): + ''' Add a rule to the rule list. ''' + pattern, replacement = split_args(args, 1, 1) + + config.replacements.append((pattern, replacement)) + config.save_replacements() + command_replacement_list(buffer, command, '') + + return weechat.WEECHAT_RC_OK + + +def command_replacement_insert(buffer, command, args): + ''' Insert a rule at the desired position in the rule list. ''' + index, pattern, replacement = split_args(args, 2, 1) + index = parse_int(index, 'index') + + config.replacements.insert(index, (pattern, replacement)) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_update(buffer, command, args): + ''' Update a rule in the rule list. ''' + index, pattern, replacement = split_args(args, 2, 1) + index = parse_int(index, 'index') + + config.replacements[index] = (pattern, replacement) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_delete(buffer, command, args): + ''' Delete a rule from the rule list. ''' + index = args.strip() + index = parse_int(index, 'index') + + config.replacements.pop(index) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_move(buffer, command, args): + ''' Move a rule to a new position. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.replacements.move(index_a, index_b) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_replacement_swap(buffer, command, args): + ''' Swap two rules. ''' + index_a, index_b = split_args(args, 2) + index_a = parse_int(index_a, 'index') + index_b = parse_int(index_b, 'index') + + config.replacements.swap(index_a, index_b) + config.save_replacements() + command_replacement_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + + + +def call_command(buffer, command, args, subcommands): + ''' Call a subccommand from a dictionary. ''' + subcommand, tail = pad(args.split(' ', 1), 2, '') + subcommand = subcommand.strip() + if (subcommand == ''): + child = subcommands.get(' ') + else: + command = command + [subcommand] + child = subcommands.get(subcommand) + + if isinstance(child, dict): + return call_command(buffer, command, tail, child) + elif callable(child): + return child(buffer, command, tail) + + log('{0}: command not found'.format(' '.join(command))) + return weechat.WEECHAT_RC_ERROR + + +def on_buffers_changed(*args, **kwargs): + ''' Called whenever the buffer list changes. ''' + buffers = get_buffers() + buffers.sort(key=buffer_sort_key(config.rules)) + apply_buffer_order([i for _, i in buffers]) + return weechat.WEECHAT_RC_OK + + +def on_config_changed(*args, **kwargs): + ''' Called whenever the configuration changes. ''' + config.reload() + + # Unhook all signals and hook the new ones. + for hook in hooks: + weechat.unhook(hook) + for signal in config.signals: + hooks.append(weechat.hook_signal(signal, 'on_buffers_changed', '')) + + if config.sort_on_config: + on_buffers_changed() + + return weechat.WEECHAT_RC_OK + + +def on_autosort_command(data, buffer, args): + ''' Called when the autosort command is invoked. ''' + try: + return call_command(buffer, ['/autosort'], args, { + ' ': command_sort, + 'sort': command_sort, + + 'rules': { + ' ': command_rule_list, + 'list': command_rule_list, + 'add': command_rule_add, + 'insert': command_rule_insert, + 'update': command_rule_update, + 'delete': command_rule_delete, + 'move': command_rule_move, + 'swap': command_rule_swap, + }, + 'replacements': { + ' ': command_replacement_list, + 'list': command_replacement_list, + 'add': command_replacement_add, + 'insert': command_replacement_insert, + 'update': command_replacement_update, + 'delete': command_replacement_delete, + 'move': command_replacement_move, + 'swap': command_replacement_swap, + }, + 'sort': on_buffers_changed, + }) + except HumanReadableError as e: + log(e, buffer) + return weechat.WEECHAT_RC_ERROR + + +command_description = r''' +NOTE: For the best effect, you may want to consider setting the option irc.look.server_buffer to independent and buffers.look.indenting to on. + +# Commands + +## Miscellaneous +/autosort sort +Manually trigger the buffer sorting. + + +## Sorting rules + +/autosort rules list +Print the list of sort rules. + +/autosort rules add = +Add a new rule at the end of the list. + +/autosort rules insert = +Insert a new rule at the given index in the list. + +/autosort rules update = +Update a rule in the list with a new pattern and score. + +/autosort rules delete +Delete a rule from the list. + +/autosort rules move +Move a rule from one position in the list to another. + +/autosort rules swap +Swap two rules in the list + + +## Replacement patterns + +/autosort replacements list +Print the list of replacement patterns. + +/autosort replacements add +Add a new replacement pattern at the end of the list. + +/autosort replacements insert +Insert a new replacement pattern at the given index in the list. + +/autosort replacements update +Update a replacement pattern in the list. + +/autosort replacements delete +Delete a replacement pattern from the list. + +/autosort replacements move +Move a replacement pattern from one position in the list to another. + +/autosort replacements swap +Swap two replacement pattern in the list + + +# Introduction +Autosort is a weechat script to automatically keep your buffers sorted. +The sort order can be customized by defining your own sort rules, +but the default should be sane enough for most people. +It can also group IRC channel/private buffers under their server buffer if you like. + +Autosort first turns buffer names into a list of their components by splitting on them on the period character. +For example, the buffer name "irc.server.freenode" is turned into ['irc', 'server', 'freenode']. +The list of buffers is then lexicographically sorted. + +To facilitate custom sort orders, it is possible to assign a score to each component individually before the sorting is done. +Any name component that did not get a score assigned will be sorted after those that did receive a score. +Components are always sorted on their score first and on their name second. +Lower scores are sorted first. + +## Automatic or manual sorting +By default, autosort will automatically sort your buffer list whenever a buffer is opened, merged, unmerged or renamed. +This should keep your buffers sorted in almost all situations. +However, you may wish to change the list of signals that cause your buffer list to be sorted. +Simply edit the "autosort.sorting.signals" option to add or remove any signal you like. +If you remove all signals you can still sort your buffers manually with the "/autosort sort" command. +To prevent all automatic sorting, "autosort.sorting.sort_on_config_change" should also be set to off. + +## Grouping IRC buffers +In weechat, IRC channel/private buffers are named "irc..<#channel>", +and IRC server buffers are named "irc.server.". +This does not work very well with lexicographical sorting if you want all buffers for one network grouped together. +That is why autosort comes with the "autosort.sorting.group_irc" option, +which secretly pretends IRC channel/private buffers are called "irc.server..<#channel>". +The buffers are not actually renamed, autosort simply pretends they are for sorting purposes. + +## Replacement patterns +Sometimes you may want to ignore some characters for sorting purposes. +On Freenode for example, you may wish to ignore the difference between channels starting with a double or a single hash sign. +To do so, simply add a replacement pattern that replaces ## with # with the following command: +/autosort replacements add ## # + +Replacement patterns do not support wildcards or special characters at the moment. + +## Sort rules +You can assign scores to name components by defining sort rules. +The first rule that matches a component decides the score. +Further rules are not examined. +Sort rules use the following syntax: + = + +You can use the "/autosort rules" command to show and manipulate the list of sort rules. + + +Allowed special characters in the glob patterns are: + +Pattern | Meaning +--------|-------- +* | Matches a sequence of any characters except for periods. +? | Matches a single character, but not a period. +[a-z] | Matches a single character in the given regex-like character class. +[^ab] | A negated regex-like character class. +\* | A backslash escapes the next characters and removes its special meaning. +\\ | A literal backslash. + + +## Example +As an example, consider the following rule list: +0: core = 0 +1: irc = 2 +2: * = 1 + +3: irc.server.*.#* = 1 +4: irc.server.*.* = 0 + +Rule 0 ensures the core buffer is always sorted first. +Rule 1 sorts IRC buffers last and rule 2 puts all remaining buffers in between the two. + +Rule 3 and 4 would make no sense with the group_irc option off. +With the option on though, these rules will sort private buffers before regular channel buffers. +Rule 3 matches channel buffers and assigns them a higher score, +while rule 4 matches the buffers that remain and assigns them a lower score. +The same effect could also be achieved with a single rule: +irc.server.*.[^#]* = 0 +''' + +command_completion = 'sort||rules list|add|insert|update|delete|move|swap||replacements list|add|insert|update|delete|move|swap' + + +if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + config = Config('autosort') + + weechat.hook_config('autosort.*', 'on_config_changed', '') + weechat.hook_command('autosort', command_description, '', '', command_completion, 'on_autosort_command', 'NULL') + on_config_changed() diff --git a/.weechat/python/colorize_nicks.py b/.weechat/python/colorize_nicks.py new file mode 100644 index 0000000..506a3ab --- /dev/null +++ b/.weechat/python/colorize_nicks.py @@ -0,0 +1,350 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2010 by xt +# +# 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 . +# + +# This script colors nicks in IRC channels in the actual message +# not just in the prefix section. +# +# +# History: +# 2016-05-01, Simmo Saan +# version 22: invalidate cached colors on hash algorithm change +# 2015-07-28, xt +# version 21: fix problems with nicks with commas in them +# 2015-04-19, xt +# version 20: fix ignore of nicks in URLs +# 2015-04-18, xt +# version 19: new option ignore nicks in URLs +# 2015-03-03, xt +# version 18: iterate buffers looking for nicklists instead of servers +# 2015-02-23, holomorph +# version 17: fix coloring in non-channel buffers (#58) +# 2014-09-17, holomorph +# version 16: use weechat config facilities +# clean unused, minor linting, some simplification +# 2014-05-05, holomorph +# version 15: fix python2-specific re.search check +# 2013-01-29, nils_2 +# version 14: make script compatible with Python 3.x +# 2012-10-19, ldvx +# version 13: Iterate over every word to prevent incorrect colorization of +# nicks. Added option greedy_matching. +# 2012-04-28, ldvx +# version 12: added ignore_tags to avoid colorizing nicks if tags are present +# 2012-01-14, nesthib +# version 11: input_text_display hook and modifier to colorize nicks in input bar +# 2010-12-22, xt +# version 10: hook config option for updating blacklist +# 2010-12-20, xt +# version 0.9: hook new config option for weechat 0.3.4 +# 2010-11-01, nils_2 +# version 0.8: hook_modifier() added to communicate with rainbow_text +# 2010-10-01, xt +# version 0.7: changes to support non-irc-plugins +# 2010-07-29, xt +# version 0.6: compile regexp as per patch from Chris quigybo@hotmail.com +# 2010-07-19, xt +# version 0.5: fix bug with incorrect coloring of own nick +# 2010-06-02, xt +# version 0.4: update to reflect API changes +# 2010-03-26, xt +# version 0.3: fix error with exception +# 2010-03-24, xt +# version 0.2: use ignore_channels when populating to increase performance. +# 2010-02-03, xt +# version 0.1: initial (based on ruby script by dominikh) +# +# Known issues: nicks will not get colorized if they begin with a character +# such as ~ (which some irc networks do happen to accept) + +import weechat +import re +w = weechat + +SCRIPT_NAME = "colorize_nicks" +SCRIPT_AUTHOR = "xt " +SCRIPT_VERSION = "22" +SCRIPT_LICENSE = "GPL" +SCRIPT_DESC = "Use the weechat nick colors in the chat area" + +VALID_NICK = r'([@~&!%+])?([-a-zA-Z0-9\[\]\\`_^\{|\}]+)' +valid_nick_re = re.compile(VALID_NICK) +ignore_channels = [] +ignore_nicks = [] + +# Dict with every nick on every channel with its color as lookup value +colored_nicks = {} + +CONFIG_FILE_NAME = "colorize_nicks" + +# config file and options +colorize_config_file = "" +colorize_config_option = {} + +def colorize_config_init(): + ''' + Initialization of configuration file. + Sections: look. + ''' + global colorize_config_file, colorize_config_option + colorize_config_file = weechat.config_new(CONFIG_FILE_NAME, + "colorize_config_reload_cb", "") + if colorize_config_file == "": + return + + # section "look" + section_look = weechat.config_new_section( + colorize_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") + if section_look == "": + weechat.config_free(colorize_config_file) + return + colorize_config_option["blacklist_channels"] = weechat.config_new_option( + colorize_config_file, section_look, "blacklist_channels", + "string", "Comma separated list of channels", "", 0, 0, + "", "", 0, "", "", "", "", "", "") + colorize_config_option["blacklist_nicks"] = weechat.config_new_option( + colorize_config_file, section_look, "blacklist_nicks", + "string", "Comma separated list of nicks", "", 0, 0, + "so,root", "so,root", 0, "", "", "", "", "", "") + colorize_config_option["min_nick_length"] = weechat.config_new_option( + colorize_config_file, section_look, "min_nick_length", + "integer", "Minimum length nick to colorize", "", + 2, 20, "", "", 0, "", "", "", "", "", "") + colorize_config_option["colorize_input"] = weechat.config_new_option( + colorize_config_file, section_look, "colorize_input", + "boolean", "Whether to colorize input", "", 0, + 0, "off", "off", 0, "", "", "", "", "", "") + colorize_config_option["ignore_tags"] = weechat.config_new_option( + colorize_config_file, section_look, "ignore_tags", + "string", "Comma separated list of tags to ignore; i.e. irc_join,irc_part,irc_quit", "", 0, 0, + "", "", 0, "", "", "", "", "", "") + colorize_config_option["greedy_matching"] = weechat.config_new_option( + colorize_config_file, section_look, "greedy_matching", + "boolean", "If off, then use lazy matching instead", "", 0, + 0, "on", "on", 0, "", "", "", "", "", "") + colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option( + colorize_config_file, section_look, "ignore_nicks_in_urls", + "boolean", "If on, don't colorize nicks inside URLs", "", 0, + 0, "off", "off", 0, "", "", "", "", "", "") + +def colorize_config_read(): + ''' Read configuration file. ''' + global colorize_config_file + return weechat.config_read(colorize_config_file) + +def colorize_nick_color(nick, my_nick): + ''' Retrieve nick color from weechat. ''' + if nick == my_nick: + return w.color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) + else: + return w.info_get('irc_nick_color', nick) + +def colorize_cb(data, modifier, modifier_data, line): + ''' Callback that does the colorizing, and returns new line if changed ''' + + global ignore_nicks, ignore_channels, colored_nicks + + + full_name = modifier_data.split(';')[1] + channel = '.'.join(full_name.split('.')[1:]) + + buffer = w.buffer_search('', full_name) + # Check if buffer has colorized nicks + if buffer not in colored_nicks: + return line + + if channel and channel in ignore_channels: + return line + + min_length = w.config_integer(colorize_config_option['min_nick_length']) + reset = w.color('reset') + + # Don't colorize if the ignored tag is present in message + tags_line = modifier_data.rsplit(';') + if len(tags_line) >= 3: + tags_line = tags_line[2].split(',') + for i in w.config_string(colorize_config_option['ignore_tags']).split(','): + if i in tags_line: + return line + + for words in valid_nick_re.findall(line): + nick = words[1] + # Check that nick is not ignored and longer than minimum length + if len(nick) < min_length or nick in ignore_nicks: + continue + + # Check that nick is in the dictionary colored_nicks + if nick in colored_nicks[buffer]: + nick_color = colored_nicks[buffer][nick] + + # Let's use greedy matching. Will check against every word in a line. + if w.config_boolean(colorize_config_option['greedy_matching']): + for word in line.split(): + if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \ + word.startswith(('http://', 'https://')): + continue + + if nick in word: + # Is there a nick that contains nick and has a greater lenght? + # If so let's save that nick into var biggest_nick + biggest_nick = "" + for i in colored_nicks[buffer]: + if nick in i and nick != i and len(i) > len(nick): + if i in word: + # If a nick with greater len is found, and that word + # also happens to be in word, then let's save this nick + biggest_nick = i + # If there's a nick with greater len, then let's skip this + # As we will have the chance to colorize when biggest_nick + # iterates being nick. + if len(biggest_nick) > 0 and biggest_nick in word: + pass + elif len(word) < len(biggest_nick) or len(biggest_nick) == 0: + new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset)) + line = line.replace(word, new_word) + # Let's use lazy matching for nick + else: + nick_color = colored_nicks[buffer][nick] + # The two .? are in case somebody writes "nick:", "nick,", etc + # to address somebody + regex = r"(\A|\s).?(%s).?(\Z|\s)" % re.escape(nick) + match = re.search(regex, line) + if match is not None: + new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):] + line = new_line + return line + +def colorize_input_cb(data, modifier, modifier_data, line): + ''' Callback that does the colorizing in input ''' + + global ignore_nicks, ignore_channels, colored_nicks + + min_length = w.config_integer(colorize_config_option['min_nick_length']) + + if not w.config_boolean(colorize_config_option['colorize_input']): + return line + + buffer = w.current_buffer() + # Check if buffer has colorized nicks + if buffer not in colored_nicks: + return line + + channel = w.buffer_get_string(buffer, 'name') + if channel and channel in ignore_channels: + return line + + reset = w.color('reset') + + for words in valid_nick_re.findall(line): + nick = words[1] + # Check that nick is not ignored and longer than minimum length + if len(nick) < min_length or nick in ignore_nicks: + continue + if nick in colored_nicks[buffer]: + nick_color = colored_nicks[buffer][nick] + line = line.replace(nick, '%s%s%s' % (nick_color, nick, reset)) + + return line + +def populate_nicks(*args): + ''' Fills entire dict with all nicks weechat can see and what color it has + assigned to it. ''' + global colored_nicks + + colored_nicks = {} + + buffers = w.infolist_get('buffer', '', '') + while w.infolist_next(buffers): + buffer_ptr = w.infolist_pointer(buffers, 'pointer') + my_nick = w.buffer_get_string(buffer_ptr, 'localvar_nick') + nicklist = w.infolist_get('nicklist', buffer_ptr, '') + while w.infolist_next(nicklist): + if buffer_ptr not in colored_nicks: + colored_nicks[buffer_ptr] = {} + + nick = w.infolist_string(nicklist, 'name') + nick_color = colorize_nick_color(nick, my_nick) + + colored_nicks[buffer_ptr][nick] = nick_color + + w.infolist_free(nicklist) + + w.infolist_free(buffers) + + return w.WEECHAT_RC_OK + +def add_nick(data, signal, type_data): + ''' Add nick to dict of colored nicks ''' + global colored_nicks + + # Nicks can have , in them in some protocols + splitted = type_data.split(',') + pointer = splitted[0] + nick = ",".join(splitted[1:]) + if pointer not in colored_nicks: + colored_nicks[pointer] = {} + + my_nick = w.buffer_get_string(pointer, 'localvar_nick') + nick_color = colorize_nick_color(nick, my_nick) + + colored_nicks[pointer][nick] = nick_color + + return w.WEECHAT_RC_OK + +def remove_nick(data, signal, type_data): + ''' Remove nick from dict with colored nicks ''' + global colored_nicks + + # Nicks can have , in them in some protocols + splitted = type_data.split(',') + pointer = splitted[0] + nick = ",".join(splitted[1:]) + + if pointer in colored_nicks and nick in colored_nicks[pointer]: + del colored_nicks[pointer][nick] + + return w.WEECHAT_RC_OK + +def update_blacklist(*args): + ''' Set the blacklist for channels and nicks. ''' + global ignore_channels, ignore_nicks + ignore_channels = w.config_string(colorize_config_option['blacklist_channels']).split(',') + ignore_nicks = w.config_string(colorize_config_option['blacklist_nicks']).split(',') + return w.WEECHAT_RC_OK + +if __name__ == "__main__": + if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, + SCRIPT_DESC, "", ""): + colorize_config_init() + colorize_config_read() + + # Run once to get data ready + update_blacklist() + populate_nicks() + + w.hook_signal('nicklist_nick_added', 'add_nick', '') + w.hook_signal('nicklist_nick_removed', 'remove_nick', '') + w.hook_modifier('weechat_print', 'colorize_cb', '') + # Hook config for changing colors + w.hook_config('weechat.color.chat_nick_colors', 'populate_nicks', '') + w.hook_config('weechat.look.nick_color_hash', 'populate_nicks', '') + # Hook for working togheter with other scripts (like colorize_lines) + w.hook_modifier('colorize_nicks', 'colorize_cb', '') + # Hook for modifying input + w.hook_modifier('250|input_text_display', 'colorize_input_cb', '') + # Hook for updating blacklist (this could be improved to use fnmatch) + weechat.hook_config('%s.look.blacklist*' % SCRIPT_NAME, 'update_blacklist', '') diff --git a/.weechat/script/plugins.xml.gz b/.weechat/script/plugins.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..dceeed90d8455aa5810d87dad4222fe4169c2538 GIT binary patch literal 104794 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 CzWOQv literal 0 HcmV?d00001 diff --git a/.weechat/weechat.conf b/.weechat/weechat.conf index 49fc4ac..ec3369c 100644 --- a/.weechat/weechat.conf +++ b/.weechat/weechat.conf @@ -143,7 +143,7 @@ chat_host = cyan chat_inactive_buffer = default chat_inactive_window = default chat_nick = lightcyan -chat_nick_colors = "cyan,magenta,green,brown,lightblue,default,lightcyan,lightmagenta,lightgreen,blue" +chat_nick_colors = "red,green,brown,blue,magenta,cyan,white,lightred,lightgreen,yellow,lightblue,lightmagenta,lightcyan" chat_nick_offline = default chat_nick_offline_highlight = default chat_nick_offline_highlight_bg = blue @@ -232,6 +232,20 @@ path = "%h/plugins" save_config_on_unload = on [bar] +buffers.color_bg = default +buffers.color_delim = default +buffers.color_fg = default +buffers.conditions = "" +buffers.filling_left_right = vertical +buffers.filling_top_bottom = columns_vertical +buffers.hidden = on +buffers.items = "buffers" +buffers.position = left +buffers.priority = 0 +buffers.separator = on +buffers.size = 0 +buffers.size_max = 0 +buffers.type = root input.color_bg = default input.color_delim = cyan input.color_fg = default @@ -246,13 +260,27 @@ input.separator = off input.size = 1 input.size_max = 0 input.type = window +isetbar.color_bg = default +isetbar.color_delim = cyan +isetbar.color_fg = default +isetbar.conditions = "" +isetbar.filling_left_right = vertical +isetbar.filling_top_bottom = horizontal +isetbar.hidden = on +isetbar.items = "isetbar_help" +isetbar.position = top +isetbar.priority = 0 +isetbar.separator = on +isetbar.size = 3 +isetbar.size_max = 3 +isetbar.type = window nicklist.color_bg = default nicklist.color_delim = cyan nicklist.color_fg = default nicklist.conditions = "${nicklist}" nicklist.filling_left_right = vertical nicklist.filling_top_bottom = columns_vertical -nicklist.hidden = off +nicklist.hidden = on nicklist.items = "buffer_nicklist" nicklist.position = right nicklist.priority = 200 @@ -294,6 +322,7 @@ title.type = window [notify] [filter] +irc_smart = on;*;irc_smart_filter;* [key] ctrl-? = "/input delete_previous_char" @@ -567,11 +596,17 @@ meta2-D = "/cursor move left" @chat:q = "hsignal:chat_quote_prefix_message;/cursor stop" [key_mouse] +@bar(buffers):ctrl-wheeldown = "hsignal:buffers_mouse" +@bar(buffers):ctrl-wheelup = "hsignal:buffers_mouse" @bar(input):button2 = "/input grab_mouse_area" @bar(nicklist):button1-gesture-down = "/bar scroll nicklist ${_window_number} +100%" @bar(nicklist):button1-gesture-down-long = "/bar scroll nicklist ${_window_number} e" @bar(nicklist):button1-gesture-up = "/bar scroll nicklist ${_window_number} -100%" @bar(nicklist):button1-gesture-up-long = "/bar scroll nicklist ${_window_number} b" +@chat(perl.iset):button1 = "hsignal:iset_mouse" +@chat(perl.iset):button2* = "hsignal:iset_mouse" +@chat(perl.iset):wheeldown = "/repeat 5 /iset **down" +@chat(perl.iset):wheelup = "/repeat 5 /iset **up" @chat(script.scripts):button1 = "/window ${_window_number};/script go ${_chat_line_y}" @chat(script.scripts):button2 = "/window ${_window_number};/script go ${_chat_line_y};/script installremove -q ${script_name_with_extension}" @chat(script.scripts):wheeldown = "/script down 5" @@ -581,6 +616,8 @@ meta2-D = "/cursor move left" @item(buffer_nicklist):button1-gesture-left-long = "/window ${_window_number};/kickban ${nick}" @item(buffer_nicklist):button2 = "/window ${_window_number};/whois ${nick}" @item(buffer_nicklist):button2-gesture-left = "/window ${_window_number};/ban ${nick}" +@item(buffers):button1* = "hsignal:buffers_mouse" +@item(buffers):button2* = "hsignal:buffers_mouse" @bar:wheeldown = "/bar scroll ${_bar_name} ${_window_number} +20%" @bar:wheelup = "/bar scroll ${_bar_name} ${_window_number} -20%" @chat:button1 = "/window ${_window_number}"