From b316d435921954112dd0fe9fe30731e43d69c2c0 Mon Sep 17 00:00:00 2001 From: Yigit Colakoglu Date: Sun, 4 Apr 2021 01:43:34 +0300 Subject: [PATCH] Added weechat --- config/.gitignore | 2 + config/weechat/.gitignore | 2 + config/weechat/alias.conf | 47 + config/weechat/autosort.conf | 24 + config/weechat/buflist.conf | 40 + config/weechat/charset.conf | 18 + config/weechat/colorize_nicks.conf | 20 + config/weechat/exec.conf | 19 + config/weechat/fifo.conf | 14 + config/weechat/fset.conf | 96 ++ config/weechat/guile.conf | 14 + config/weechat/irc.conf | 205 +++ config/weechat/logger.conf | 36 + config/weechat/lua.conf | 14 + config/weechat/perl.conf | 14 + config/weechat/perl/atcomplete.pl | 89 ++ config/weechat/perl/autoload/atcomplete.pl | 1 + config/weechat/perl/autoload/highmon.pl | 1 + config/weechat/perl/autoload/multiline.pl | 1 + config/weechat/perl/highmon.pl | 1154 +++++++++++++++++ config/weechat/perl/multiline.pl | 782 +++++++++++ config/weechat/plugins.conf | 79 ++ config/weechat/python.conf | 14 + config/weechat/python/aesthetic.py | 70 + config/weechat/python/anotify.py | 472 +++++++ config/weechat/python/autoload/aesthetic.py | 1 + config/weechat/python/autoload/anotify.py | 1 + config/weechat/python/autoload/autosort.py | 1 + .../weechat/python/autoload/colorize_nicks.py | 1 + config/weechat/python/autoload/go.py | 1 + config/weechat/python/autosort.py | 1075 +++++++++++++++ config/weechat/python/colorize_nicks.py | 409 ++++++ config/weechat/python/go.py | 563 ++++++++ config/weechat/relay.conf | 59 + config/weechat/ruby.conf | 14 + config/weechat/script.conf | 57 + config/weechat/script/plugins.xml.gz | Bin 0 -> 129202 bytes config/weechat/sec.conf | 20 + config/weechat/spell.conf | 33 + config/weechat/tcl.conf | 14 + config/weechat/trigger.conf | 67 + config/weechat/weechat.conf | 679 ++++++++++ config/weechat/weechat.log | 51 + config/weechat/xfer.conf | 49 + 44 files changed, 6323 insertions(+) create mode 100644 config/weechat/.gitignore create mode 100644 config/weechat/alias.conf create mode 100644 config/weechat/autosort.conf create mode 100644 config/weechat/buflist.conf create mode 100644 config/weechat/charset.conf create mode 100644 config/weechat/colorize_nicks.conf create mode 100644 config/weechat/exec.conf create mode 100644 config/weechat/fifo.conf create mode 100644 config/weechat/fset.conf create mode 100644 config/weechat/guile.conf create mode 100644 config/weechat/irc.conf create mode 100644 config/weechat/logger.conf create mode 100644 config/weechat/lua.conf create mode 100644 config/weechat/perl.conf create mode 100644 config/weechat/perl/atcomplete.pl create mode 120000 config/weechat/perl/autoload/atcomplete.pl create mode 120000 config/weechat/perl/autoload/highmon.pl create mode 120000 config/weechat/perl/autoload/multiline.pl create mode 100644 config/weechat/perl/highmon.pl create mode 100644 config/weechat/perl/multiline.pl create mode 100644 config/weechat/plugins.conf create mode 100644 config/weechat/python.conf create mode 100644 config/weechat/python/aesthetic.py create mode 100644 config/weechat/python/anotify.py create mode 120000 config/weechat/python/autoload/aesthetic.py create mode 120000 config/weechat/python/autoload/anotify.py create mode 120000 config/weechat/python/autoload/autosort.py create mode 120000 config/weechat/python/autoload/colorize_nicks.py create mode 120000 config/weechat/python/autoload/go.py create mode 100644 config/weechat/python/autosort.py create mode 100644 config/weechat/python/colorize_nicks.py create mode 100644 config/weechat/python/go.py create mode 100644 config/weechat/relay.conf create mode 100644 config/weechat/ruby.conf create mode 100644 config/weechat/script.conf create mode 100644 config/weechat/script/plugins.xml.gz create mode 100644 config/weechat/sec.conf create mode 100644 config/weechat/spell.conf create mode 100644 config/weechat/tcl.conf create mode 100644 config/weechat/trigger.conf create mode 100644 config/weechat/weechat.conf create mode 100644 config/weechat/weechat.log create mode 100644 config/weechat/xfer.conf diff --git a/config/.gitignore b/config/.gitignore index f2ee7775..6b484efc 100644 --- a/config/.gitignore +++ b/config/.gitignore @@ -70,3 +70,5 @@ !coc !npm !zsh +!weechat +!weechat/** diff --git a/config/weechat/.gitignore b/config/weechat/.gitignore new file mode 100644 index 00000000..a5e0c4cf --- /dev/null +++ b/config/weechat/.gitignore @@ -0,0 +1,2 @@ +logs +logs/** diff --git a/config/weechat/alias.conf b/config/weechat/alias.conf new file mode 100644 index 00000000..0a02da3b --- /dev/null +++ b/config/weechat/alias.conf @@ -0,0 +1,47 @@ +# +# weechat -- alias.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[cmd] +AAWAY = "allserv /away" +ANICK = "allserv /nick" +BEEP = "print -beep" +BYE = "quit" +C = "buffer clear" +CHAT = "dcc chat" +CL = "buffer clear" +CLOSE = "buffer close" +EXIT = "quit" +IG = "ignore" +J = "join" +K = "kick" +KB = "kickban" +LEAVE = "part" +M = "msg" +MSGBUF = "command -buffer $1 * /input send $2-" +MUB = "unban *" +N = "names" +Q = "query" +REDRAW = "window refresh" +SAY = "msg *" +SIGNOFF = "quit" +T = "topic" +UB = "unban" +UMODE = "mode $nick" +V = "command core version" +W = "who" +WC = "window close" +WI = "whois" +WII = "whois $1 $1" +WM = "window merge" +WW = "whowas" + +[completion] +MSGBUF = "%(buffers_plugins_names)" diff --git a/config/weechat/autosort.conf b/config/weechat/autosort.conf new file mode 100644 index 00000000..fca216a9 --- /dev/null +++ b/config/weechat/autosort.conf @@ -0,0 +1,24 @@ +# +# weechat -- autosort.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[sorting] +case_sensitive = off +debug_log = off +replacements = "" +rules = "" +signal_delay = 5 +signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed" +sort_limit = 100 +sort_on_config_change = on + +[v3] +helpers = "{"core_first": "${if:${buffer.full_name}!=core.weechat}", "irc_raw_first": "${if:${buffer.full_name}!=irc.irc_raw}", "irc_raw_last": "${if:${buffer.full_name}==irc.irc_raw}", "hashless_name": "${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}", "script_or_plugin": "${if:${script_name}?${script_name}:${plugin}}"}" +rules = "["${core_first}", "${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}", "${script_or_plugin}", "${irc_raw_first}", "${server}", "${info:autosort_order,${type},server,*,channel,private}", "${hashless_name}", "${buffer.full_name}"]" diff --git a/config/weechat/buflist.conf b/config/weechat/buflist.conf new file mode 100644 index 00000000..225b5e86 --- /dev/null +++ b/config/weechat/buflist.conf @@ -0,0 +1,40 @@ +# +# weechat -- buflist.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +add_newline = on +auto_scroll = 50 +display_conditions = "${buffer.hidden}==0" +enabled = on +mouse_jump_visited_buffer = off +mouse_move_buffer = on +mouse_wheel = on +nick_prefix = off +nick_prefix_empty = on +signals_refresh = "" +sort = "number,-active" +use_items = 1 + +[format] +buffer = "${format_number}${indent}${format_nick_prefix}${color_hotlist}${format_name}" +buffer_current = "${color:,blue}${format_buffer}" +hotlist = " ${color:green}(${hotlist}${color:green})" +hotlist_highlight = "${color:magenta}" +hotlist_low = "${color:white}" +hotlist_message = "${color:brown}" +hotlist_none = "${color:default}" +hotlist_private = "${color:green}" +hotlist_separator = "${color:default}," +indent = " " +lag = " ${color:green}[${color:brown}${lag}${color:green}]" +name = "${name}" +nick_prefix = "${color_nick_prefix}${nick_prefix}" +number = "${color:green}${number}${if:${number_displayed}?.: }" diff --git a/config/weechat/charset.conf b/config/weechat/charset.conf new file mode 100644 index 00000000..00d304cd --- /dev/null +++ b/config/weechat/charset.conf @@ -0,0 +1,18 @@ +# +# weechat -- charset.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[default] +decode = "iso-8859-1" +encode = "" + +[decode] + +[encode] diff --git a/config/weechat/colorize_nicks.conf b/config/weechat/colorize_nicks.conf new file mode 100644 index 00000000..a3cf7589 --- /dev/null +++ b/config/weechat/colorize_nicks.conf @@ -0,0 +1,20 @@ +# +# weechat -- colorize_nicks.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +blacklist_channels = "" +blacklist_nicks = "so,root" +colorize_input = off +greedy_matching = on +ignore_nicks_in_urls = off +ignore_tags = "" +match_limit = 20 +min_nick_length = 2 diff --git a/config/weechat/exec.conf b/config/weechat/exec.conf new file mode 100644 index 00000000..9e1b4f1e --- /dev/null +++ b/config/weechat/exec.conf @@ -0,0 +1,19 @@ +# +# weechat -- exec.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[command] +default_options = "" +purge_delay = 0 +shell = "${env:SHELL}" + +[color] +flag_finished = lightred +flag_running = lightgreen diff --git a/config/weechat/fifo.conf b/config/weechat/fifo.conf new file mode 100644 index 00000000..904985df --- /dev/null +++ b/config/weechat/fifo.conf @@ -0,0 +1,14 @@ +# +# weechat -- fifo.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[file] +enabled = on +path = "%h/weechat_fifo" diff --git a/config/weechat/fset.conf b/config/weechat/fset.conf new file mode 100644 index 00000000..53b0da7e --- /dev/null +++ b/config/weechat/fset.conf @@ -0,0 +1,96 @@ +# +# weechat -- fset.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +auto_refresh = "*" +auto_unmark = off +condition_catch_set = "${count} >= 1" +export_help_default = on +format_number = 1 +marked_string = "*" +scroll_horizontal = 10 +show_plugins_desc = off +sort = "~name" +unmarked_string = " " +use_color_value = off +use_keys = on +use_mute = off + +[format] +export_help = "# ${description2}" +export_option = "/set ${name} ${quoted_value}" +export_option_null = "/unset ${name}" +option1 = "" +option2 = "${marked} ${name} ${type} ${value2}${newline} ${empty_name} ${_default_value}${color:darkgray} -- ${min}..${max}${newline} ${empty_name} ${description}" + +[color] +default_value = default +default_value_selected = white +description = default +description_selected = white +file = default +file_changed = brown +file_changed_selected = yellow +file_selected = white +help_default_value = white +help_description = default +help_name = white +help_quotes = darkgray +help_values = default +index = cyan +index_selected = lightcyan +line_marked_bg1 = default +line_marked_bg2 = default +line_selected_bg1 = blue +line_selected_bg2 = red +marked = brown +marked_selected = yellow +max = default +max_selected = white +min = default +min_selected = white +name = default +name_changed = brown +name_changed_selected = yellow +name_selected = white +option = default +option_changed = brown +option_changed_selected = yellow +option_selected = white +parent_name = default +parent_name_selected = white +parent_value = cyan +parent_value_selected = lightcyan +quotes = darkgray +quotes_changed = default +quotes_changed_selected = white +quotes_selected = default +section = default +section_changed = brown +section_changed_selected = yellow +section_selected = white +string_values = default +string_values_selected = white +title_count_options = cyan +title_current_option = lightcyan +title_filter = yellow +title_marked_options = lightgreen +title_sort = white +type = green +type_selected = lightgreen +unmarked = default +unmarked_selected = white +value = cyan +value_changed = brown +value_changed_selected = yellow +value_selected = lightcyan +value_undef = magenta +value_undef_selected = lightmagenta diff --git a/config/weechat/guile.conf b/config/weechat/guile.conf new file mode 100644 index 00000000..07a4910e --- /dev/null +++ b/config/weechat/guile.conf @@ -0,0 +1,14 @@ +# +# weechat -- guile.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +check_license = off +eval_keep_context = on diff --git a/config/weechat/irc.conf b/config/weechat/irc.conf new file mode 100644 index 00000000..fc18feb2 --- /dev/null +++ b/config/weechat/irc.conf @@ -0,0 +1,205 @@ +# +# weechat -- irc.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +buffer_open_before_autojoin = on +buffer_open_before_join = off +buffer_switch_autojoin = on +buffer_switch_join = on +color_nicks_in_names = off +color_nicks_in_nicklist = off +color_nicks_in_server_messages = on +color_pv_nick_like_channel = on +ctcp_time_format = "%a, %d %b %Y %T %z" +display_away = local +display_ctcp_blocked = on +display_ctcp_reply = on +display_ctcp_unknown = on +display_host_join = on +display_host_join_local = on +display_host_quit = on +display_join_message = "329,332,333,366" +display_old_topic = on +display_pv_away_once = on +display_pv_back = on +display_pv_warning_address = off +highlight_channel = "$nick" +highlight_pv = "$nick" +highlight_server = "$nick" +highlight_tags_restrict = "irc_privmsg,irc_notice" +item_channel_modes_hide_args = "k" +item_display_server = buffer_plugin +item_nick_modes = on +item_nick_prefix = on +join_auto_add_chantype = off +msgbuffer_fallback = current +new_channel_position = none +new_pv_position = none +nick_completion_smart = speakers +nick_mode = prefix +nick_mode_empty = off +nicks_hide_password = "nickserv" +notice_as_pv = auto +notice_welcome_redirect = on +notice_welcome_tags = "" +notify_tags_ison = "notify_message" +notify_tags_whois = "notify_message" +part_closes_buffer = off +pv_buffer = independent +pv_tags = "notify_private" +raw_messages = 256 +server_buffer = merge_with_core +smart_filter = on +smart_filter_account = on +smart_filter_chghost = on +smart_filter_delay = 5 +smart_filter_join = on +smart_filter_join_unmask = 30 +smart_filter_mode = "+" +smart_filter_nick = on +smart_filter_quit = on +temporary_servers = off +topic_strip_colors = off + +[color] +input_nick = lightcyan +item_channel_modes = default +item_lag_counting = default +item_lag_finished = yellow +item_nick_modes = default +message_account = cyan +message_chghost = brown +message_join = green +message_kick = red +message_quit = red +mirc_remap = "1,-1:darkgray" +nick_prefixes = "y:lightred;q:lightred;a:lightcyan;o:lightgreen;h:lightmagenta;v:yellow;*:lightblue" +notice = green +reason_kick = default +reason_quit = default +topic_current = default +topic_new = white +topic_old = default + +[network] +autoreconnect_delay_growing = 2 +autoreconnect_delay_max = 600 +ban_mask_default = "*!$ident@$host" +colors_receive = on +colors_send = on +lag_check = 60 +lag_max = 1800 +lag_min_show = 500 +lag_reconnect = 300 +lag_refresh_interval = 1 +notify_check_ison = 1 +notify_check_whois = 5 +sasl_fail_unavailable = on +send_unknown_commands = off +whois_double_nick = off + +[msgbuffer] + +[ctcp] + +[ignore] + +[server_default] +addresses = "" +anti_flood_prio_high = 2 +anti_flood_prio_low = 2 +autoconnect = off +autojoin = "" +autoreconnect = on +autoreconnect_delay = 10 +autorejoin = off +autorejoin_delay = 30 +away_check = 0 +away_check_max_nicks = 25 +capabilities = "" +charset_message = message +command = "" +command_delay = 0 +connection_timeout = 60 +default_chantypes = "#&" +ipv6 = on +local_hostname = "" +msg_kick = "" +msg_part = "WeeChat ${info:version}" +msg_quit = "WeeChat ${info:version}" +nicks = "yigit,yigit1,yigit2,yigit3,yigit4" +nicks_alternate = on +notify = "" +password = "" +proxy = "" +realname = "" +sasl_fail = continue +sasl_key = "" +sasl_mechanism = plain +sasl_password = "" +sasl_timeout = 15 +sasl_username = "" +split_msg_max_length = 512 +ssl = off +ssl_cert = "" +ssl_dhkey_size = 2048 +ssl_fingerprint = "" +ssl_password = "" +ssl_priorities = "NORMAL:-VERS-SSL3.0" +ssl_verify = on +usermode = "" +username = "yigit" + +[server] +freenode.addresses = "chat.freenode.net/6697" +freenode.proxy +freenode.ipv6 +freenode.ssl = on +freenode.ssl_cert +freenode.ssl_password +freenode.ssl_priorities +freenode.ssl_dhkey_size +freenode.ssl_fingerprint +freenode.ssl_verify +freenode.password +freenode.capabilities +freenode.sasl_mechanism = plain +freenode.sasl_username = "Fr1nge" +freenode.sasl_password = "${sec.data.freenode_password}" +freenode.sasl_key +freenode.sasl_timeout +freenode.sasl_fail +freenode.autoconnect = on +freenode.autoreconnect +freenode.autoreconnect_delay +freenode.nicks = "Fr1nge" +freenode.nicks_alternate +freenode.username = "Fr1nge" +freenode.realname = "Yigit Colakoglu" +freenode.local_hostname +freenode.usermode +freenode.command +freenode.command_delay +freenode.autojoin = "#vim,#archlinux,#lf,##C,#python,##java" +freenode.autorejoin +freenode.autorejoin_delay +freenode.connection_timeout +freenode.anti_flood_prio_high +freenode.anti_flood_prio_low +freenode.away_check +freenode.away_check_max_nicks +freenode.msg_kick +freenode.msg_part +freenode.msg_quit +freenode.notify +freenode.split_msg_max_length +freenode.charset_message +freenode.default_chantypes diff --git a/config/weechat/logger.conf b/config/weechat/logger.conf new file mode 100644 index 00000000..d0d3218d --- /dev/null +++ b/config/weechat/logger.conf @@ -0,0 +1,36 @@ +# +# weechat -- logger.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +backlog = 20 +backlog_conditions = "" + +[color] +backlog_end = default +backlog_line = default + +[file] +auto_log = on +color_lines = off +flush_delay = 120 +fsync = off +info_lines = off +mask = "$plugin.$name.weechatlog" +name_lower_case = on +nick_prefix = "" +nick_suffix = "" +path = "%h/logs/" +replacement_char = "_" +time_format = "%Y-%m-%d %H:%M:%S" + +[level] + +[mask] diff --git a/config/weechat/lua.conf b/config/weechat/lua.conf new file mode 100644 index 00000000..fa4966b2 --- /dev/null +++ b/config/weechat/lua.conf @@ -0,0 +1,14 @@ +# +# weechat -- lua.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +check_license = off +eval_keep_context = on diff --git a/config/weechat/perl.conf b/config/weechat/perl.conf new file mode 100644 index 00000000..31924b91 --- /dev/null +++ b/config/weechat/perl.conf @@ -0,0 +1,14 @@ +# +# weechat -- perl.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +check_license = off +eval_keep_context = on diff --git a/config/weechat/perl/atcomplete.pl b/config/weechat/perl/atcomplete.pl new file mode 100644 index 00000000..e0b5d5c8 --- /dev/null +++ b/config/weechat/perl/atcomplete.pl @@ -0,0 +1,89 @@ +# Copyright 2015 by David A. Golden. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# ABOUT +# +# atcomplete.pl +# +# Adds nick completion when prefixed with '@' for use with IRC gateways +# for Slack, Flowdock, etc. as these require the '@' to highlight users +# +# CONFIG +# +# /set plugins.var.perl.atcomplete.enabled +# +# HISTORY +# +# 0.001 -- xdg, 2016-04-06 +# +# - initial release +# +# REPOSITORY +# +# https://github.com/xdg/weechat-atcomplete + +use strict; +use warnings; +my $SCRIPT_NAME = "atcomplete"; +my $VERSION = "0.001"; + +my %options_default = ( + 'enabled' => ['on', 'enable completion of nicks starting with @'], +); +my %options = (); + +weechat::register($SCRIPT_NAME, "David A. Golden", $VERSION, + "Apache2", "atcomplete - do nick completion following @", "", ""); +init_config(); + +weechat::hook_config("plugins.var.perl.$SCRIPT_NAME.*", "toggle_config_by_set", ""); +weechat::hook_completion("nicks", "Add @ prefix to nick completion", "complete_at_nicks", ""); + +sub complete_at_nicks { + my ($data, $completion_item, $buffer, $completion ) = @_; + return weechat::WEECHAT_RC_OK() unless $options{enabled} eq 'on'; + + my $nicklist = weechat::infolist_get("nicklist", weechat::current_buffer(), ""); + + if ($nicklist ne "") { + while (weechat::infolist_next($nicklist)) { + next unless weechat::infolist_string($nicklist, "type") eq "nick"; + my $nick = weechat::infolist_string($nicklist, "name"); + weechat::hook_completion_list_add($completion, "\@$nick", 1, weechat::WEECHAT_LIST_POS_SORT()); + } + } + + weechat::infolist_free($nicklist); + + return weechat::WEECHAT_RC_OK(); +} + +sub toggle_config_by_set { + my ($pointer, $name, $value) = @_; + $name = substr($name, length("plugins.var.perl.".$SCRIPT_NAME."."), length($name)); + $options{$name} = $value; + return weechat::WEECHAT_RC_OK(); +} + +sub init_config { + my $version = weechat::info_get("version_number", "") || 0; + foreach my $option (keys %options_default) + { + if (!weechat::config_is_set_plugin($option)) + { + weechat::config_set_plugin($option, $options_default{$option}[0]); + $options{$option} = $options_default{$option}[0]; + } + else + { + $options{$option} = weechat::config_get_plugin($option); + } + if ($version >= 0x00030500) + { + weechat::config_set_desc_plugin($option, $options_default{$option}[1]." (default: \"".$options_default{$option}[0]."\")"); + } + } +} diff --git a/config/weechat/perl/autoload/atcomplete.pl b/config/weechat/perl/autoload/atcomplete.pl new file mode 120000 index 00000000..bd3e9ea3 --- /dev/null +++ b/config/weechat/perl/autoload/atcomplete.pl @@ -0,0 +1 @@ +../atcomplete.pl \ No newline at end of file diff --git a/config/weechat/perl/autoload/highmon.pl b/config/weechat/perl/autoload/highmon.pl new file mode 120000 index 00000000..2eb5e1e5 --- /dev/null +++ b/config/weechat/perl/autoload/highmon.pl @@ -0,0 +1 @@ +../highmon.pl \ No newline at end of file diff --git a/config/weechat/perl/autoload/multiline.pl b/config/weechat/perl/autoload/multiline.pl new file mode 120000 index 00000000..cf9fe28f --- /dev/null +++ b/config/weechat/perl/autoload/multiline.pl @@ -0,0 +1 @@ +../multiline.pl \ No newline at end of file diff --git a/config/weechat/perl/highmon.pl b/config/weechat/perl/highmon.pl new file mode 100644 index 00000000..f843cade --- /dev/null +++ b/config/weechat/perl/highmon.pl @@ -0,0 +1,1154 @@ +# +# highmon.pl - Highlight Monitoring for weechat 0.3.0 +# +# 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: +# 2020-06-21, Sebastien Helleu : +# v2.7: make call to bar_new compatible with WeeChat >= 2.9 +# 2019-05-13, HubbeKing +# v2.6: -add: send "logger_backlog" signal on buffer open if logging is enabled +# 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", ""); + + if (weechat::info_get("version_number", "") >= 0x02090000) + { + $highmon_bar = weechat::bar_new ("highmon", "off", 100, "root", "", "bottom", "vertical", "vertical", 0, 0, "default", "cyan", "default", "default", "on", "highmon"); + } + else + { + $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"); + } + # send "logger_backlog" signal if logging is enabled to display backlog + if (weechat::config_get_plugin("logging") eq "on") + { + weechat::hook_signal_send("logger_backlog", weechat::WEECHAT_HOOK_SIGNAL_POINTER, $highmon_buffer) + } + } + 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", "") <= 0x00030200) + { + $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.7", "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/config/weechat/perl/multiline.pl b/config/weechat/perl/multiline.pl new file mode 100644 index 00000000..54474d41 --- /dev/null +++ b/config/weechat/perl/multiline.pl @@ -0,0 +1,782 @@ +use strict; use warnings; +$INC{'Encode/ConfigLocal.pm'}=1; +require Encode; +use utf8; + +# multiline.pl is written by Nei +# and licensed under the under GNU General Public License v3 +# or any later version + +# to read the following docs, you can use "perldoc multiline.pl" + +=head1 NAME + +multiline - Multi-line edit box for WeeChat (weechat edition) + +=head1 DESCRIPTION + +multiline will draw a multi-line edit box to your WeeChat window so +that when you hit the return key, you can first compose a complete +multi-line message before sending it all at once. + +Furthermore, if you have multi-line pastes then you can edit them +before sending out all the lines. + +=head1 USAGE + +make a key binding to send the finished message: + + /key bind meta-s /input return + +then you can send the multi-line message with Alt+S + +=head1 SETTINGS + +the settings are usually found in the + + plugins.var.perl.multiline + +namespace, that is, type + + /set plugins.var.perl.multiline.* + +to see them and + + /set plugins.var.perl.multiline.SETTINGNAME VALUE + +to change a setting C to a new value C. Finally, + + /unset plugins.var.perl.multiline.SETTINGNAME + +will reset a setting to its default value. + +the following settings are available: + +=head2 char + +character(s) which should be displayed to indicate end of line + +=head2 tab + +character(s) which should be displayed instead of Tab key character + +=head2 lead_linebreak + +if turned on, multi-line messages always start on a new line + +=head2 modify_keys + +if turned on, cursor keys are modified so that they respect line +boundaries instead of treating the whole multi-line message as a +single line + +=head2 magic + +indicator displayed when message will be sent soon + +=head2 magic_enter_time + +delay after pressing enter before sending automatically (in ms), or 0 +to disable + +=head2 magic_paste_only + +only use multi-line messages for multi-line pastes (multi-line on +enter is disabled by this) + +=head2 paste_lock + +time-out to detect pastes (disable the weechat built-in paste +detection if you want to use this) + +=head2 send_empty + +set to on to automatically disregard enter key on empty line + +=head2 hide_magic_nl + +whether the new line inserted by magic enter key will be hidden + +=head2 weechat_paste_fix + +disable ctrl-J binding when paste is detected to stop silly weechat +sending out pastes without allowing to edit them + +=head2 ipl + +this setting controls override of ctrl-M (enter key) by script. Turn +it off if you don't want multiline.pl to set and re-set the key binding. + +=head1 FUNCTION DESCRIPTION + +for full pod documentation, filter this script with + + perl -pE' + (s/^## (.*?) -- (.*)/=head2 $1\n\n$2\n\n=over\n/ and $o=1) or + s/^## (.*?) - (.*)/=item I<$1>\n\n$2\n/ or + (s/^## (.*)/=back\n\n$1\n\n=cut\n/ and $o=0,1) or + ($o and $o=0,1 and s/^sub /=back\n\n=cut\n\nsub /)' + +=cut + +use constant SCRIPT_NAME => 'multiline'; +our $VERSION = '0.6.3'; # af2e0a17b659a16 +weechat::register(SCRIPT_NAME, + 'Nei ', # Author + $VERSION, + 'GPL3', # License + 'Multi-line edit box', # Description + 'stop_multiline', '') || return; +sub SCRIPT_FILE() { + my $infolistptr = weechat::infolist_get('perl_script', '', SCRIPT_NAME); + my $filename = weechat::infolist_string($infolistptr, 'filename') if weechat::infolist_next($infolistptr); + weechat::infolist_free($infolistptr); + return $filename unless @_; +} + +{ +package Nlib; +# this is a weechat perl library +use strict; use warnings; no warnings 'redefine'; + +## i2h -- copy weechat infolist content into perl hash +## $infolist - name of the infolist in weechat +## $ptr - pointer argument (infolist dependend) +## @args - arguments to the infolist (list dependend) +## $fields - string of ref type "fields" if only certain keys are needed (optional) +## returns perl list with perl hashes for each infolist entry +sub i2h { + my %i2htm = (i => 'integer', s => 'string', p => 'pointer', b => 'buffer', t => 'time'); + local *weechat::infolist_buffer = sub { '(not implemented)' }; + my ($infolist, $ptr, @args) = @_; + $ptr ||= ""; + my $fields = ref $args[-1] eq 'fields' ? ${ pop @args } : undef; + my $infptr = weechat::infolist_get($infolist, $ptr, do { local $" = ','; "@args" }); + my @infolist; + while (weechat::infolist_next($infptr)) { + my @fields = map { + my ($t, $v) = split ':', $_, 2; + bless \$v, $i2htm{$t}; + } + split ',', + ($fields || weechat::infolist_fields($infptr)); + push @infolist, +{ do { + my (%list, %local, @local); + map { + my $fn = 'weechat::infolist_'.ref $_; + my $r = do { no strict 'refs'; &$fn($infptr, $$_) }; + if ($$_ =~ /^localvar_name_(\d+)$/) { + $local[$1] = $r; + () + } + elsif ($$_ =~ /^(localvar)_value_(\d+)$/) { + $local{$local[$2]} = $r; + $1 => \%local + } + elsif ($$_ =~ /(.*?)((?:_\d+)+)$/) { + my ($key, $idx) = ($1, $2); + my @idx = split '_', $idx; shift @idx; + my $target = \$list{$key}; + for my $x (@idx) { + my $o = 1; + if ($key eq 'key' or $key eq 'key_command') { + $o = 0; + } + if ($x-$o < 0) { + local $" = '|'; + weechat::print('',"list error: $target/$$_/$key/$x/$idx/@idx(@_)"); + $o = 0; + } + $target = \$$target->[$x-$o] + } + $$target = $r; + + $key => $list{$key} + } + else { + $$_ => $r + } + } @fields + } }; + } + weechat::infolist_free($infptr); + !wantarray && @infolist ? \@infolist : @infolist +} + +## hdh -- hdata helper +## $_[0] - arg pointer or hdata list name +## $_[1] - hdata name +## $_[2..$#_] - hdata variable name +## $_[-1] - hashref with key/value to update (optional) +## returns value of hdata, and hdata name in list ctx, or number of variables updated +sub hdh { + if (@_ > 1 && $_[0] !~ /^0x/ && $_[0] !~ /^\d+$/) { + my $arg = shift; + unshift @_, weechat::hdata_get_list(weechat::hdata_get($_[0]), $arg); + } + while (@_ > 2) { + my ($arg, $name, $var) = splice @_, 0, 3; + my $hdata = weechat::hdata_get($name); + unless (ref $var eq 'HASH') { + $var =~ s/!(.*)/weechat::hdata_get_string($hdata, $1)/e; + (my $plain_var = $var) =~ s/^\d+\|//; + my $type = weechat::hdata_get_var_type_string($hdata, $plain_var); + if ($type eq 'pointer') { + my $name = weechat::hdata_get_var_hdata($hdata, $var); + unshift @_, $name if $name; + } + + my $fn = "weechat::hdata_$type"; + unshift @_, do { no strict 'refs'; + &$fn($hdata, $arg, $var) }; + } + else { + return weechat::hdata_update($hdata, $arg, $var); + } + } + wantarray ? @_ : $_[0] +} + +use Pod::Select qw(); +use Pod::Simple::TextContent; + +## get_desc_from_pod -- return setting description from pod documentation +## $file - filename with pod +## $setting - name of setting +## returns description as text +sub get_desc_from_pod { + my $file = shift; + return unless -s $file; + my $setting = shift; + + open my $pod_sel, '>', \my $ss; + Pod::Select::podselect({ + -output => $pod_sel, + -sections => ["SETTINGS/$setting"]}, $file); + + my $pt = new Pod::Simple::TextContent; + $pt->output_string(\my $ss_f); + $pt->parse_string_document($ss); + + my ($res) = $ss_f =~ /^\s*\Q$setting\E\s+(.*)\s*/; + $res +} + +## get_settings_from_pod -- retrieve all settings in settings section of pod +## $file - file with pod +## returns list of all settings +sub get_settings_from_pod { + my $file = shift; + return unless -s $file; + + open my $pod_sel, '>', \my $ss; + Pod::Select::podselect({ + -output => $pod_sel, + -sections => ["SETTINGS//!.+"]}, $file); + + $ss =~ /^=head2\s+(.*)\s*$/mg +} + +## mangle_man_for_wee -- turn man output into weechat codes +## @_ - list of grotty lines that should be turned into weechat attributes +## returns modified lines and modifies lines in-place +sub mangle_man_for_wee { + for (@_) { + s/_\x08(.)/weechat::color('underline').$1.weechat::color('-underline')/ge; + s/(.)\x08\1/weechat::color('bold').$1.weechat::color('-bold')/ge; + } + wantarray ? @_ : $_[0] +} + +## read_manpage -- read a man page in weechat window +## $file - file with pod +## $name - buffer name +sub read_manpage { + my $caller_package = (caller)[0]; + my $file = shift; + my $name = shift; + + if (my $obuf = weechat::buffer_search('perl', "man $name")) { + eval qq{ + package $caller_package; + weechat::buffer_close(\$obuf); + }; + } + + my @wee_keys = Nlib::i2h('key'); + my @keys; + + my $winptr = weechat::current_window(); + my ($wininfo) = Nlib::i2h('window', $winptr); + my $buf = weechat::buffer_new("man $name", '', '', '', ''); + return weechat::WEECHAT_RC_OK unless $buf; + + my $width = $wininfo->{chat_width}; + --$width if $wininfo->{chat_width} < $wininfo->{width} || ($wininfo->{width_pct} < 100 && (grep { $_->{y} == $wininfo->{y} } Nlib::i2h('window'))[-1]{x} > $wininfo->{x}); + $width -= 2; # when prefix is shown + + weechat::buffer_set($buf, 'time_for_each_line', 0); + eval qq{ + package $caller_package; + weechat::buffer_set(\$buf, 'display', 'auto'); + }; + die $@ if $@; + + @keys = map { $_->{key} } + grep { $_->{command} eq '/input history_previous' || + $_->{command} eq '/input history_global_previous' } @wee_keys; + @keys = 'meta2-A' unless @keys; + weechat::buffer_set($buf, "key_bind_$_", '/window scroll -1') for @keys; + + @keys = map { $_->{key} } + grep { $_->{command} eq '/input history_next' || + $_->{command} eq '/input history_global_next' } @wee_keys; + @keys = 'meta2-B' unless @keys; + weechat::buffer_set($buf, "key_bind_$_", '/window scroll +1') for @keys; + + weechat::buffer_set($buf, 'key_bind_ ', '/window page_down'); + + @keys = map { $_->{key} } + grep { $_->{command} eq '/input delete_previous_char' } @wee_keys; + @keys = ('ctrl-?', 'ctrl-H') unless @keys; + weechat::buffer_set($buf, "key_bind_$_", '/window page_up') for @keys; + + weechat::buffer_set($buf, 'key_bind_g', '/window scroll_top'); + weechat::buffer_set($buf, 'key_bind_G', '/window scroll_bottom'); + + weechat::buffer_set($buf, 'key_bind_q', '/buffer close'); + + weechat::print($buf, " \t".mangle_man_for_wee($_)) # weird bug with \t\t showing nothing? + for `pod2man \Q$file\E 2>/dev/null | GROFF_NO_SGR=1 nroff -mandoc -rLL=${width}n -rLT=${width}n -Tutf8 2>/dev/null`; + weechat::command($buf, '/window scroll_top'); + + unless (hdh($buf, 'buffer', 'lines', 'lines_count') > 0) { + weechat::print($buf, weechat::prefix('error').$_) + for "Unfortunately, your @{[weechat::color('underline')]}nroff". + "@{[weechat::color('-underline')]} command did not produce". + " any output.", + "Working pod2man and nroff commands are required for the ". + "help viewer to work.", + "In the meantime, please use the command ", '', + "\tperldoc $file", '', + "on your shell instead in order to read the manual.", + "Thank you and sorry for the inconvenience." + } +} + +1 +} + +our $MAGIC_ENTER_TIMER; +our $MAGIC_LOCK; +our $MAGIC_LOCK_TIMER; +our $WEECHAT_PASTE_FIX_CTRLJ_CMD; +our $INPUT_CHANGED_EATER_FLAG; +our $IGNORE_INPUT_CHANGED; +our $IGNORE_INPUT_CHANGED2; + +use constant KEY_RET => 'ctrl-M'; +use constant INPUT_NL => '/input insert \x0a'; +use constant INPUT_MAGIC => '/input magic_enter'; +our $NL = "\x0a"; + +init_multiline(); + +my $magic_enter_cancel_dynamic = 1; +my $paste_undo_start_ignore_dynamic = 0; +my $input_changed_eater_dynamic = 0; +my $multiline_complete_fix_dynamic = 1; + +sub magic_enter_cancel_dynamic { $magic_enter_cancel_dynamic ? &magic_enter_cancel : weechat::WEECHAT_RC_OK } +sub paste_undo_start_ignore_dynamic { $paste_undo_start_ignore_dynamic ? &paste_undo_start_ignore : weechat::WEECHAT_RC_OK } +sub input_changed_eater_dynamic { $input_changed_eater_dynamic ? &input_changed_eater : weechat::WEECHAT_RC_OK } +sub multiline_complete_fix_dynamic { $multiline_complete_fix_dynamic ? &multiline_complete_fix : weechat::WEECHAT_RC_OK } + +weechat::hook_config('plugins.var.perl.'.SCRIPT_NAME.'.*', 'default_options', ''); +weechat::hook_modifier('input_text_display_with_cursor', 'multiline_display', ''); +weechat::hook_command_run('/help '.SCRIPT_NAME, 'help_cmd', ''); +weechat::hook_command_run(INPUT_MAGIC, 'magic_enter', ''); +weechat::hook_signal('input_text_*', 'magic_enter_cancel_dynamic', ''); +weechat::hook_command_run('/input *', 'paste_undo_start_ignore_dynamic', ''); +weechat::hook_signal('2000|input_text_changed', 'input_changed_eater_dynamic', ''); +weechat::hook_signal('key_pressed', 'magic_lock_hatch', ''); +# we need lower than default priority here or the first character is separated +weechat::hook_signal('500|input_text_changed', 'paste_undo_hack', '') + # can only do this on weechat 0.4.0 + if (weechat::info_get('version_number', '') || 0) >= 0x00040000; +weechat::hook_command_run("1500|/input complete*", 'multiline_complete_fix_dynamic', 'complete*'); +weechat::hook_command_run("1500|/input delete_*", 'multiline_complete_fix_dynamic', 'delete_*'); +weechat::hook_command_run("1500|/input move_*", 'multiline_complete_fix_dynamic', 'move_*'); + +sub _stack_depth { + my $depth = -1; + 1 while caller(++$depth); + $depth; +} + +## multiline_display -- show multi-lines on display of input string +## () - modifier handler +## $_[2] - buffer pointer +## $_[3] - input string +## returns modified input string +sub multiline_display { + Encode::_utf8_on($_[3]); + Encode::_utf8_on(my $nl = weechat::config_get_plugin('char') || ' '); + Encode::_utf8_on(my $tab = weechat::config_get_plugin('tab')); + my $cb = weechat::current_buffer() eq $_[2] && $MAGIC_ENTER_TIMER; + if ($cb) { + $_[3] =~ s/$NL\x19b#/\x19b#/ if weechat::config_string_to_boolean(weechat::config_get_plugin('hide_magic_nl')); + } + if ($_[3] =~ s/$NL/$nl\x0d/g) { + $_[3] =~ s/\A/ \x0d/ if weechat::config_string_to_boolean(weechat::config_get_plugin('lead_linebreak')); + } + $_[3] =~ s/\x09/$tab/g if $tab; + if ($cb) { + Encode::_utf8_on(my $magic = weechat::config_get_plugin('magic')); + $_[3] =~ s/\Z/$magic/ if $magic; + } + $_[3] +} + +## lock_timer_exp -- expire the magic lock timer +sub lock_timer_exp { + if ($MAGIC_LOCK_TIMER) { + weechat::unhook($MAGIC_LOCK_TIMER); + $MAGIC_LOCK_TIMER = undef; + } + weechat::WEECHAT_RC_OK +} + +## paste_undo_stop_ignore -- unset ignore2 flag +sub paste_undo_stop_ignore { + $IGNORE_INPUT_CHANGED2 = undef; + weechat::WEECHAT_RC_OK +} + +## paste_undo_start_ignore -- set ignore2 flag when /input is received so to allow /input undo/redo +## () - command_run handler +## $_[2] - command that was called +sub paste_undo_start_ignore { + return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED; + return weechat::WEECHAT_RC_OK if $_[2] =~ /insert/; + $IGNORE_INPUT_CHANGED2 = 1; + weechat::WEECHAT_RC_OK +} + +## paste_undo_hack -- fix up undo stack when paste is detected by calling /input undo +## () - signal handler +## $_[2] - buffer pointer +sub paste_undo_hack { + return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED; + return paste_undo_stop_ignore() if $IGNORE_INPUT_CHANGED2; + if ($MAGIC_LOCK > 0 && get_lock_enabled()) { + signall_ignore_input_changed(1); + $paste_undo_start_ignore_dynamic = 1; + + Encode::_utf8_on(my $input = weechat::buffer_get_string($_[2], 'input')); + my $pos = weechat::buffer_get_integer($_[2], 'input_pos'); + + weechat::command($_[2], '/input undo') for 1..2; + + weechat::buffer_set($_[2], 'input', $input); + weechat::buffer_set($_[2], 'input_pos', $pos); + + $paste_undo_start_ignore_dynamic = 0; + signall_ignore_input_changed(0); + } + weechat::WEECHAT_RC_OK +} + +## input_changed_eater -- suppress input_text_changed signal on new weechats +## () - signal handler +sub input_changed_eater { + $INPUT_CHANGED_EATER_FLAG = undef; + weechat::WEECHAT_RC_OK_EAT +} + +## signall_ignore_input_changed -- use various methods to "ignore" input_text_changed signal +## $_[0] - start ignore or stop ignore +sub signall_ignore_input_changed { + if ($_[0]) { + weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 1); + $input_changed_eater_dynamic = 1; + $IGNORE_INPUT_CHANGED = 1; + weechat::buffer_set('', 'completion_freeze', '1'); + } + else { + weechat::buffer_set('', 'completion_freeze', '0'); + $IGNORE_INPUT_CHANGED = undef; + $input_changed_eater_dynamic = 0; + weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 0); + } +} + +## multiline_complete_fix -- add per line /input handling for completion, movement and deletion +## () - command_run handler +## $_[0] - original bound data +## $_[1] - buffer pointer +## $_[2] - original command +sub multiline_complete_fix { + $magic_enter_cancel_dynamic = 0; + $multiline_complete_fix_dynamic = 0; + if ($_[2] =~ s/_message$/_line/ || !weechat::config_string_to_boolean(weechat::config_get_plugin('modify_keys'))) { + weechat::command($_[1], $_[2]); + } + else { + signall_ignore_input_changed(1); + Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input')); + my $pos = weechat::buffer_get_integer($_[1], 'input_pos'); + if ($pos && $_[2] =~ /(?:previous|beginning_of)_/ && (substr $input, $pos-1, 1) eq $NL) { + substr $input, $pos-1, 1, "\0" + } + elsif ($pos < length $input && $_[2] =~ /(?:next|end_of)_/ && (substr $input, $pos, 1) eq $NL) { + substr $input, $pos, 1, "\0" + } + my @lines = $pos ? (split /$NL/, (substr $input, 0, $pos), -1) : ''; + my @after = $pos < length $input ? (split /$NL/, (substr $input, $pos), -1) : ''; + $lines[-1] =~ s/\0$/$NL/; + $after[0] =~ s/^\0/$NL/; + my ($p1, $p2) = (pop @lines, shift @after); + weechat::buffer_set($_[1], 'input', $p1.$p2); + weechat::buffer_set($_[1], 'input_pos', length $p1); + + $magic_enter_cancel_dynamic = 1; + $INPUT_CHANGED_EATER_FLAG = 1; + weechat::command($_[1], $_[2]); + my $changed_later = !$INPUT_CHANGED_EATER_FLAG; + magic_enter_cancel() if $changed_later; + $magic_enter_cancel_dynamic = 0; + + Encode::_utf8_on(my $p = weechat::buffer_get_string($_[1], 'input')); + $pos = weechat::buffer_get_integer($_[1], 'input_pos'); + weechat::command($_[1], '/input undo') if @lines || @after; + weechat::command($_[1], '/input undo'); + weechat::buffer_set($_[1], 'input', join $NL, @lines, $p, @after); + weechat::buffer_set($_[1], 'input_pos', $pos+length join $NL, @lines, ''); + + signall_ignore_input_changed(0); + weechat::hook_signal_send('input_text_changed', weechat::WEECHAT_HOOK_SIGNAL_POINTER, $_[1]) if $changed_later; + } + $multiline_complete_fix_dynamic = 1; + $magic_enter_cancel_dynamic = 1; + weechat::WEECHAT_RC_OK_EAT +} + +## help_cmd -- show multi-line script documentation +## () - command_run handler +sub help_cmd { + Nlib::read_manpage(SCRIPT_FILE, SCRIPT_NAME); + weechat::WEECHAT_RC_OK_EAT +} + +## get_lock_time -- gets timeout for paste detection according to setting +## returns timeout (at least 1) +sub get_lock_time { + my $lock_time = weechat::config_get_plugin('paste_lock'); + $lock_time = 1 unless $lock_time =~ /^\d+$/ && $lock_time; + $lock_time +} + +## get_lock_enabled -- checks whether the paste detection lock is enabled +## returns bool +sub get_lock_enabled { + my $lock = weechat::config_get_plugin('paste_lock'); + $lock = weechat::config_string_to_boolean($lock) + unless $lock =~ /^\d+$/; + $lock +} + +## magic_lock_hatch -- set a timer for paste detection +## () - signal handler +sub magic_lock_hatch { + lock_timer_exp(); + $MAGIC_LOCK_TIMER = weechat::hook_timer(get_lock_time(), 0, 1, 'lock_timer_exp', ''); + weechat::WEECHAT_RC_OK +} + +## magic_unlock -- reduce the lock added by paste detection +## () - timer handler +sub magic_unlock { + if ($MAGIC_LOCK_TIMER) { + weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', ''); + } + else { + --$MAGIC_LOCK; + if (!$MAGIC_LOCK && $WEECHAT_PASTE_FIX_CTRLJ_CMD) { + do_key_bind('ctrl-J', $WEECHAT_PASTE_FIX_CTRLJ_CMD); + $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef; + } + } + weechat::WEECHAT_RC_OK +} + +## get_magic_enter_time -- get timeout for auto-sending messages according to config +## returns timeout +sub get_magic_enter_time { + my $magic_enter = weechat::config_get_plugin('magic_enter_time'); + $magic_enter = 1000 * weechat::config_string_to_boolean($magic_enter) + unless $magic_enter =~ /^\d+$/; + $magic_enter +} + +## magic_enter -- receive enter key and do magic things: set up a timer for sending the message, add newline +## () - command_run handler +## $_[1] - buffer pointer +sub magic_enter { + Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input')); + if (!length $input && weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty'))) { + weechat::command($_[1], '/input return'); + } + else { + magic_enter_cancel(); + weechat::command($_[1], INPUT_NL); + + unless (get_lock_enabled() && $MAGIC_LOCK) { + if (weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only')) && + $input !~ /$NL/) { + magic_enter_send($_[1]); + } + elsif (my $magic_enter = get_magic_enter_time()) { + $MAGIC_ENTER_TIMER = weechat::hook_timer($magic_enter, 0, 1, 'magic_enter_send', $_[1]); + } + } + } + weechat::WEECHAT_RC_OK_EAT +} + +## magic_enter_send -- actually send enter key when triggered by magic_enter, remove preceding newline +## $_[0] - buffer pointer +## sending is delayed by 1ms to circumvent crash bug in api +sub magic_enter_send { + magic_enter_cancel(); + weechat::command($_[0], '/input delete_previous_char'); + weechat::command($_[0], '/wait 1ms /input return'); + weechat::WEECHAT_RC_OK +} + +## magic_enter_cancel -- cancel the timer for automatic sending of message, for example when more text was added, increase the paste lock for paste detection when used as signal handler +## () - signal handler when @_ is set +sub magic_enter_cancel { + if ($MAGIC_ENTER_TIMER) { + weechat::unhook($MAGIC_ENTER_TIMER); + $MAGIC_ENTER_TIMER = undef; + } + if ($MAGIC_LOCK_TIMER && @_) { + if (!$MAGIC_LOCK && !$WEECHAT_PASTE_FIX_CTRLJ_CMD && + weechat::config_string_to_boolean(weechat::config_get_plugin('weechat_paste_fix'))) { + ($WEECHAT_PASTE_FIX_CTRLJ_CMD) = get_key_command('ctrl-J'); + $WEECHAT_PASTE_FIX_CTRLJ_CMD = '-' unless defined $WEECHAT_PASTE_FIX_CTRLJ_CMD; + do_key_bind('ctrl-J', '-'); + } + if ($MAGIC_LOCK < 1) { + my $lock_time = get_lock_time(); + ++$MAGIC_LOCK; + weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', ''); + } + } + weechat::WEECHAT_RC_OK +} + +## need_magic_enter -- check if magic enter keybinding is needed according to config settings +## returns bool +sub need_magic_enter { + weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty')) || get_magic_enter_time() || + weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only')) +} + +## do_key_bind -- mute execute a key binding, or unbind if $_[-1] is '-' +## @_ - arguments to /key bind +sub do_key_bind { + if ($_[-1] eq '-') { + pop; + weechat::command('', "/mute /key unbind @_"); + } + elsif ($_[-1] eq '!') { + pop; + weechat::command('', "/mute /key reset @_"); + } + else { + weechat::command('', "/mute /key bind @_"); + } +} + +{ my %keys; +## get_key_command -- get the command bound to a key +## $_[0] - key in weechat syntax +## returns the command + sub get_key_command { + unless (exists $keys{$_[0]}) { + ($keys{$_[0]}) = + map { $_->{command} } grep { $_->{key} eq $_[0] } + Nlib::i2h('key') + } + $keys{$_[0]} + } +} + +## default_options -- set up default option values on start and when unset +## () - config handler if @_ is set +sub default_options { + my %defaults = ( + char => '↩', + tab => '──▶▏', + magic => '‼', + ipl => 'on', + lead_linebreak => 'on', + modify_keys => 'on', + send_empty => 'on', + magic_enter_time => '1000', + paste_lock => '1', + magic_paste_only => 'off', + hide_magic_nl => 'on', + weechat_paste_fix => 'on', + ); + unless (weechat::config_is_set_plugin('ipl')) { + if (my $bar = weechat::bar_search('input')) { + weechat::bar_set($bar, $_, '0') for 'size', 'size_max'; + } + } + for (keys %defaults) { + weechat::config_set_plugin($_, $defaults{$_}) + unless weechat::config_is_set_plugin($_); + } + do_key_bind(KEY_RET, INPUT_NL) + if weechat::config_string_to_boolean(weechat::config_get_plugin('ipl')); + my ($enter_key) = get_key_command(KEY_RET); + if (need_magic_enter()) { + do_key_bind(KEY_RET, INPUT_MAGIC) + if $enter_key eq INPUT_NL; + } + else { + do_key_bind(KEY_RET, INPUT_NL) + if $enter_key eq INPUT_MAGIC; + } + weechat::WEECHAT_RC_OK +} + +sub init_multiline { + $MAGIC_LOCK = -1; + default_options(); + my $sf = SCRIPT_FILE; + for (Nlib::get_settings_from_pod($sf)) { + weechat::config_set_desc_plugin($_, Nlib::get_desc_from_pod($sf, $_)); + } + weechat::WEECHAT_RC_OK +} + +sub stop_multiline { + magic_enter_cancel(); + if (need_magic_enter()) { + my ($enter_key) = get_key_command(KEY_RET); + do_key_bind(KEY_RET, INPUT_NL) + if $enter_key eq INPUT_MAGIC; + } + if ($WEECHAT_PASTE_FIX_CTRLJ_CMD) { + do_key_bind('ctrl-J', $WEECHAT_PASTE_FIX_CTRLJ_CMD); + $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef; + } + if (weechat::config_string_to_boolean(weechat::config_get_plugin('ipl'))) { + do_key_bind(KEY_RET, '!'); + } + weechat::WEECHAT_RC_OK +} diff --git a/config/weechat/plugins.conf b/config/weechat/plugins.conf new file mode 100644 index 00000000..a0a11cd0 --- /dev/null +++ b/config/weechat/plugins.conf @@ -0,0 +1,79 @@ +# +# weechat -- plugins.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[var] +perl.atcomplete.enabled = "on" +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" +perl.multiline.char = "↩" +perl.multiline.hide_magic_nl = "on" +perl.multiline.ipl = "on" +perl.multiline.lead_linebreak = "on" +perl.multiline.magic = "‼" +perl.multiline.magic_enter_time = "1000" +perl.multiline.magic_paste_only = "off" +perl.multiline.modify_keys = "on" +perl.multiline.paste_lock = "1" +perl.multiline.send_empty = "on" +perl.multiline.tab = "──▶▏" +perl.multiline.weechat_paste_fix = "on" +python.chanotify.filters = "*:*" +python.chanotify.status = "on" +python.go.auto_jump = "off" +python.go.buffer_number = "on" +python.go.color_name = "black,cyan" +python.go.color_name_highlight = "red,cyan" +python.go.color_name_highlight_selected = "red,brown" +python.go.color_name_selected = "black,brown" +python.go.color_number = "yellow,magenta" +python.go.color_number_selected = "yellow,red" +python.go.fuzzy_search = "off" +python.go.message = "Go to: " +python.go.short_name = "off" +python.go.sort = "number,beginning" +python.go.use_core_instead_weechat = "off" + +[desc] +perl.atcomplete.enabled = "enable completion of nicks starting with @ (default: "on")" +perl.multiline.char = "character(s) which should be displayed to indicate end of line" +perl.multiline.hide_magic_nl = "whether the new line inserted by magic enter key will be hidden" +perl.multiline.ipl = "this setting controls override of ctrl-M (enter key) by script. Turn it off if you don't want multiline.pl to set and re-set the key binding." +perl.multiline.lead_linebreak = "if turned on, multi-line messages always start on a new line" +perl.multiline.magic = "indicator displayed when message will be sent soon" +perl.multiline.magic_enter_time = "delay after pressing enter before sending automatically (in ms), or 0 to disable" +perl.multiline.magic_paste_only = "only use multi-line messages for multi-line pastes (multi-line on enter is disabled by this)" +perl.multiline.modify_keys = "if turned on, cursor keys are modified so that they respect line boundaries instead of treating the whole multi-line message as a single line" +perl.multiline.paste_lock = "time-out to detect pastes (disable the weechat built-in paste detection if you want to use this)" +perl.multiline.send_empty = "set to on to automatically disregard enter key on empty line" +perl.multiline.tab = "character(s) which should be displayed instead of Tab key character" +perl.multiline.weechat_paste_fix = "disable ctrl-J binding when paste is detected to stop silly weechat sending out pastes without allowing to edit them" +python.go.auto_jump = "automatically jump to buffer when it is uniquely selected (default: "off")" +python.go.buffer_number = "display buffer number (default: "on")" +python.go.color_name = "color for buffer name (not selected) (default: "black,cyan")" +python.go.color_name_highlight = "color for highlight in buffer name (not selected) (default: "red,cyan")" +python.go.color_name_highlight_selected = "color for highlight in a selected buffer name (default: "red,brown")" +python.go.color_name_selected = "color for a selected buffer name (default: "black,brown")" +python.go.color_number = "color for buffer number (not selected) (default: "yellow,magenta")" +python.go.color_number_selected = "color for selected buffer number (default: "yellow,red")" +python.go.fuzzy_search = "search buffer matches using approximation (default: "off")" +python.go.message = "message to display before list of buffers (default: "Go to: ")" +python.go.short_name = "display and search in short names instead of buffer name (default: "off")" +python.go.sort = "comma-separated list of keys to sort buffers (the order is important, sorts are performed in the given order): name = sort by name (or short name), (default: "number,beginning")" +python.go.use_core_instead_weechat = "use name "core" instead of "weechat" for core buffer (default: "off")" diff --git a/config/weechat/python.conf b/config/weechat/python.conf new file mode 100644 index 00000000..187b778d --- /dev/null +++ b/config/weechat/python.conf @@ -0,0 +1,14 @@ +# +# weechat -- python.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +check_license = off +eval_keep_context = on diff --git a/config/weechat/python/aesthetic.py b/config/weechat/python/aesthetic.py new file mode 100644 index 00000000..988071d6 --- /dev/null +++ b/config/weechat/python/aesthetic.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# +# Script Name: aesthetic.py +# Script Author: Wojciech Siewierski +# Script License: GPL3 +# Contact: vifon @ irc.freenode.net + +SCRIPT_NAME = 'aesthetic' +SCRIPT_AUTHOR = 'Wojciech Siewierski' +SCRIPT_VERSION = '1.0.6' +SCRIPT_LICENSE = 'GPL3' +SCRIPT_DESC = 'Make messages more A E S T H E T I C A L L Y pleasing.' + +import_ok = True + +try: + import weechat +except ImportError: + print('This script must be run under WeeChat') + print('You can obtain a copy of WeeChat, for free, at https://weechat.org') + import_ok = False + +weechat_version = 0 + +import shlex +import sys + +def aesthetic_(args): + for arg in args: + try: + arg = arg.decode('utf8') + except AttributeError: + pass + yield " ".join(arg.upper()) + for n, char in enumerate(arg[1:]): + yield " ".join(" "*(n+1)).join(char.upper()*2) + +def aesthetic(args): + if sys.version_info < (3,): + return (x.encode('utf8') for x in aesthetic_(args)) + else: + return aesthetic_(args) + +def aesthetic_cb(data, buffer, args): + for x in aesthetic(shlex.split(args)): + weechat.command(buffer, x) + return weechat.WEECHAT_RC_OK + +if __name__ == "__main__" and import_ok: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + weechat_version = weechat.info_get("version_number", "") or 0 + weechat.hook_command( + "aesthetic", + """Format a message like this: + +E X A M P L E +X X +A A +M M +P P +L L +E E + +Each argument is formatted separately, use sh-like quotes for grouping. For example '/aesthetic foo bar' will send two such blocks while '/aesthetic "foo bar"' would send one larger one. + +Use with care to not cause undesirable message spam.""", + "message", "", + "", + "aesthetic_cb", "" + ) diff --git a/config/weechat/python/anotify.py b/config/weechat/python/anotify.py new file mode 100644 index 00000000..88e020e5 --- /dev/null +++ b/config/weechat/python/anotify.py @@ -0,0 +1,472 @@ +# -*- coding: utf-8 -*- +# +# anotify.py +# Copyright (c) 2012 magnific0 +# +# based on: +# growl.py +# Copyright (c) 2011 Sorin Ionescu +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +SCRIPT_NAME = 'anotify' +SCRIPT_AUTHOR = 'magnific0' +SCRIPT_VERSION = '1.0.2' +SCRIPT_LICENSE = 'MIT' +SCRIPT_DESC = 'Sends libnotify notifications upon events.' + + +# Changelog +# 2014-05-10: v1.0.1 Change hook_print callback argument type of +# displayed/highlight (WeeChat >= 1.0) +# 2012-09-20: v1.0.0 Forked from original and adapted for libnotify. + +# ----------------------------------------------------------------------------- +# Settings +# ----------------------------------------------------------------------------- +SETTINGS = { + 'show_public_message': 'off', + 'show_private_message': 'on', + 'show_public_action_message': 'off', + 'show_private_action_message': 'on', + 'show_notice_message': 'off', + 'show_invite_message': 'on', + 'show_highlighted_message': 'on', + 'show_server': 'on', + 'show_channel_topic': 'on', + 'show_dcc': 'on', + 'show_upgrade_ended': 'on', + 'sticky': 'off', + 'sticky_away': 'on', + 'icon': '/usr/share/pixmaps/weechat.xpm', +} + + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +try: + import re + import os + import weechat + import notify2 + IMPORT_OK = True +except ImportError as error: + IMPORT_OK = False + if str(error).find('weechat') != -1: + print('This script must be run under WeeChat.') + print('Get WeeChat at http://www.weechat.org.') + else: + weechat.prnt('', 'anotify: {0}'.format(error)) + +# ----------------------------------------------------------------------------- +# Globals +# ----------------------------------------------------------------------------- +TAGGED_MESSAGES = { + 'public message or action': set(['irc_privmsg', 'notify_message']), + 'private message or action': set(['irc_privmsg', 'notify_private']), + 'notice message': set(['irc_notice', 'notify_private']), + 'invite message': set(['irc_invite', 'notify_highlight']), + 'channel topic': set(['irc_topic', ]), + #'away status': set(['away_info', ]), +} + + +UNTAGGED_MESSAGES = { + 'away status': + re.compile(r'^You ((\w+).){2,3}marked as being away', re.UNICODE), + 'dcc chat request': + re.compile(r'^xfer: incoming chat request from (\w+)', re.UNICODE), + 'dcc chat closed': + re.compile(r'^xfer: chat closed with (\w+)', re.UNICODE), + 'dcc get request': + re.compile( + r'^xfer: incoming file from (\w+) [^:]+: ((?:,\w|[^,])+),', + re.UNICODE), + 'dcc get completed': + re.compile(r'^xfer: file ([^\s]+) received from \w+: OK', re.UNICODE), + 'dcc get failed': + re.compile( + r'^xfer: file ([^\s]+) received from \w+: FAILED', + re.UNICODE), + 'dcc send completed': + re.compile(r'^xfer: file ([^\s]+) sent to \w+: OK', re.UNICODE), + 'dcc send failed': + re.compile(r'^xfer: file ([^\s]+) sent to \w+: FAILED', re.UNICODE), +} + + +DISPATCH_TABLE = { + 'away status': 'set_away_status', + 'public message or action': 'notify_public_message_or_action', + 'private message or action': 'notify_private_message_or_action', + 'notice message': 'notify_notice_message', + 'invite message': 'notify_invite_message', + 'channel topic': 'notify_channel_topic', + 'dcc chat request': 'notify_dcc_chat_request', + 'dcc chat closed': 'notify_dcc_chat_closed', + 'dcc get request': 'notify_dcc_get_request', + 'dcc get completed': 'notify_dcc_get_completed', + 'dcc get failed': 'notify_dcc_get_failed', + 'dcc send completed': 'notify_dcc_send_completed', + 'dcc send failed': 'notify_dcc_send_failed', +} + + +STATE = { + 'icon': None, + 'is_away': False +} + + +# ----------------------------------------------------------------------------- +# Notifiers +# ----------------------------------------------------------------------------- +def cb_irc_server_connected(data, signal, signal_data): + '''Notify when connected to IRC server.''' + if weechat.config_get_plugin('show_server') == 'on': + a_notify( + 'Server', + 'Server Connected', + 'Connected to network {0}.'.format(signal_data)) + return weechat.WEECHAT_RC_OK + + +def cb_irc_server_disconnected(data, signal, signal_data): + '''Notify when disconnected to IRC server.''' + if weechat.config_get_plugin('show_server') == 'on': + a_notify( + 'Server', + 'Server Disconnected', + 'Disconnected from network {0}.'.format(signal_data)) + return weechat.WEECHAT_RC_OK + + +def cb_notify_upgrade_ended(data, signal, signal_data): + '''Notify on end of WeeChat upgrade.''' + if weechat.config_get_plugin('show_upgrade_ended') == 'on': + a_notify( + 'WeeChat', + 'WeeChat Upgraded', + 'WeeChat has been upgraded.') + return weechat.WEECHAT_RC_OK + + +def notify_highlighted_message(prefix, message): + '''Notify on highlighted message.''' + if weechat.config_get_plugin("show_highlighted_message") == "on": + a_notify( + 'Highlight', + 'Highlighted Message', + "{0}: {1}".format(prefix, message), + priority=notify2.URGENCY_CRITICAL) + + +def notify_public_message_or_action(prefix, message, highlighted): + '''Notify on public message or action.''' + if prefix == ' *': + regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) + match = regex.match(message) + if match: + prefix = match.group(1) + message = match.group(2) + notify_public_action_message(prefix, message, highlighted) + else: + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_public_message") == "on": + a_notify( + 'Public', + 'Public Message', + '{0}: {1}'.format(prefix, message)) + + +def notify_private_message_or_action(prefix, message, highlighted): + '''Notify on private message or action.''' + regex = re.compile(r'^CTCP_MESSAGE.+?ACTION (.+)$', re.UNICODE) + match = regex.match(message) + if match: + notify_private_action_message(prefix, match.group(1), highlighted) + else: + if prefix == ' *': + regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) + match = regex.match(message) + if match: + prefix = match.group(1) + message = match.group(2) + notify_private_action_message(prefix, message, highlighted) + else: + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_private_message") == "on": + a_notify( + 'Private', + 'Private Message', + '{0}: {1}'.format(prefix, message)) + + +def notify_public_action_message(prefix, message, highlighted): + '''Notify on public action message.''' + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_public_action_message") == "on": + a_notify( + 'Action', + 'Public Action Message', + '{0}: {1}'.format(prefix, message), + priority=notify2.URGENCY_NORMAL) + + +def notify_private_action_message(prefix, message, highlighted): + '''Notify on private action message.''' + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_private_action_message") == "on": + a_notify( + 'Action', + 'Private Action Message', + '{0}: {1}'.format(prefix, message), + priority=notify2.URGENCY_NORMAL) + + +def notify_notice_message(prefix, message, highlighted): + '''Notify on notice message.''' + regex = re.compile(r'^([^\s]*) [^:]*: (.+)$', re.UNICODE) + match = regex.match(message) + if match: + prefix = match.group(1) + message = match.group(2) + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_notice_message") == "on": + a_notify( + 'Notice', + 'Notice Message', + '{0}: {1}'.format(prefix, message)) + + +def notify_invite_message(prefix, message, highlighted): + '''Notify on channel invitation message.''' + if weechat.config_get_plugin("show_invite_message") == "on": + regex = re.compile( + r'^You have been invited to ([^\s]+) by ([^\s]+)$', re.UNICODE) + match = regex.match(message) + if match: + channel = match.group(1) + nick = match.group(2) + a_notify( + 'Invite', + 'Channel Invitation', + '{0} has invited you to join {1}.'.format(nick, channel)) + + +def notify_channel_topic(prefix, message, highlighted): + '''Notify on channel topic change.''' + if weechat.config_get_plugin("show_channel_topic") == "on": + regex = re.compile( + r'^\w+ has (?:changed|unset) topic for ([^\s]+)' + + '(?:(?: from "(?:(?:"\w|[^"])+)")? to "((?:"\w|[^"])+)")?', + re.UNICODE) + match = regex.match(message) + if match: + channel = match.group(1) + topic = match.group(2) or '' + a_notify( + 'Channel', + 'Channel Topic', + "{0}: {1}".format(channel, topic)) + + +def notify_dcc_chat_request(match): + '''Notify on DCC chat request.''' + if weechat.config_get_plugin("show_dcc") == "on": + nick = match.group(1) + a_notify( + 'DCC', + 'Direct Chat Request', + '{0} wants to chat directly.'.format(nick)) + + +def notify_dcc_chat_closed(match): + '''Notify on DCC chat termination.''' + if weechat.config_get_plugin("show_dcc") == "on": + nick = match.group(1) + a_notify( + 'DCC', + 'Direct Chat Ended', + 'Direct chat with {0} has ended.'.format(nick)) + + +def notify_dcc_get_request(match): + 'Notify on DCC get request.' + if weechat.config_get_plugin("show_dcc") == "on": + nick = match.group(1) + file_name = match.group(2) + a_notify( + 'DCC', + 'File Transfer Request', + '{0} wants to send you {1}.'.format(nick, file_name)) + + +def notify_dcc_get_completed(match): + 'Notify on DCC get completion.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Download Complete', file_name) + + +def notify_dcc_get_failed(match): + 'Notify on DCC get failure.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Download Failed', file_name) + + +def notify_dcc_send_completed(match): + 'Notify on DCC send completion.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Upload Complete', file_name) + + +def notify_dcc_send_failed(match): + 'Notify on DCC send failure.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Upload Failed', file_name) + + +# ----------------------------------------------------------------------------- +# Utility +# ----------------------------------------------------------------------------- +def set_away_status(match): + status = match.group(1) + if status == 'been ': + STATE['is_away'] = True + if status == 'longer ': + STATE['is_away'] = False + + +def cb_process_message( + data, + wbuffer, + date, + tags, + displayed, + highlight, + prefix, + message +): + '''Delegates incoming messages to appropriate handlers.''' + tags = set(tags.split(',')) + functions = globals() + is_public_message = tags.issuperset( + TAGGED_MESSAGES['public message or action']) + buffer_name = weechat.buffer_get_string(wbuffer, 'name') + dcc_buffer_regex = re.compile(r'^irc_dcc\.', re.UNICODE) + dcc_buffer_match = dcc_buffer_regex.match(buffer_name) + highlighted = False + if int(highlight): + highlighted = True + # Private DCC message identifies itself as public. + if is_public_message and dcc_buffer_match: + notify_private_message_or_action(prefix, message, highlighted) + return weechat.WEECHAT_RC_OK + # Pass identified, untagged message to its designated function. + for key, value in UNTAGGED_MESSAGES.items(): + match = value.match(message) + if match: + functions[DISPATCH_TABLE[key]](match) + return weechat.WEECHAT_RC_OK + # Pass identified, tagged message to its designated function. + for key, value in TAGGED_MESSAGES.items(): + if tags.issuperset(value): + functions[DISPATCH_TABLE[key]](prefix, message, highlighted) + return weechat.WEECHAT_RC_OK + return weechat.WEECHAT_RC_OK + + +def a_notify(notification, title, description, priority=notify2.URGENCY_LOW): + '''Returns whether notifications should be sticky.''' + is_away = STATE['is_away'] + icon = STATE['icon'] + time_out = 5000 + if weechat.config_get_plugin('sticky') == 'on': + time_out = 0 + if weechat.config_get_plugin('sticky_away') == 'on' and is_away: + time_out = 0 + try: + notify2.init("wee-notifier") + wn = notify2.Notification(title, description, icon) + wn.set_urgency(priority) + wn.set_timeout(time_out) + wn.show() + except Exception as error: + weechat.prnt('', 'anotify: {0}'.format(error)) + + +# ----------------------------------------------------------------------------- +# Main +# ----------------------------------------------------------------------------- +def main(): + '''Sets up WeeChat notifications.''' + # Initialize options. + for option, value in SETTINGS.items(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value) + # Initialize. + name = "WeeChat" + icon = "/usr/share/pixmaps/weechat.xpm" + notifications = [ + 'Public', + 'Private', + 'Action', + 'Notice', + 'Invite', + 'Highlight', + 'Server', + 'Channel', + 'DCC', + 'WeeChat' + ] + STATE['icon'] = icon + # Register hooks. + weechat.hook_signal( + 'irc_server_connected', + 'cb_irc_server_connected', + '') + weechat.hook_signal( + 'irc_server_disconnected', + 'cb_irc_server_disconnected', + '') + weechat.hook_signal('upgrade_ended', 'cb_upgrade_ended', '') + weechat.hook_print('', '', '', 1, 'cb_process_message', '') + + +if __name__ == '__main__' and IMPORT_OK and weechat.register( + SCRIPT_NAME, + SCRIPT_AUTHOR, + SCRIPT_VERSION, + SCRIPT_LICENSE, + SCRIPT_DESC, + '', + '' +): + main() diff --git a/config/weechat/python/autoload/aesthetic.py b/config/weechat/python/autoload/aesthetic.py new file mode 120000 index 00000000..9c60e1b2 --- /dev/null +++ b/config/weechat/python/autoload/aesthetic.py @@ -0,0 +1 @@ +../aesthetic.py \ No newline at end of file diff --git a/config/weechat/python/autoload/anotify.py b/config/weechat/python/autoload/anotify.py new file mode 120000 index 00000000..1517fefe --- /dev/null +++ b/config/weechat/python/autoload/anotify.py @@ -0,0 +1 @@ +../anotify.py \ No newline at end of file diff --git a/config/weechat/python/autoload/autosort.py b/config/weechat/python/autoload/autosort.py new file mode 120000 index 00000000..1850897f --- /dev/null +++ b/config/weechat/python/autoload/autosort.py @@ -0,0 +1 @@ +../autosort.py \ No newline at end of file diff --git a/config/weechat/python/autoload/colorize_nicks.py b/config/weechat/python/autoload/colorize_nicks.py new file mode 120000 index 00000000..3ee34e96 --- /dev/null +++ b/config/weechat/python/autoload/colorize_nicks.py @@ -0,0 +1 @@ +../colorize_nicks.py \ No newline at end of file diff --git a/config/weechat/python/autoload/go.py b/config/weechat/python/autoload/go.py new file mode 120000 index 00000000..bdfb7ddf --- /dev/null +++ b/config/weechat/python/autoload/go.py @@ -0,0 +1 @@ +../go.py \ No newline at end of file diff --git a/config/weechat/python/autosort.py b/config/weechat/python/autosort.py new file mode 100644 index 00000000..4312cda7 --- /dev/null +++ b/config/weechat/python/autosort.py @@ -0,0 +1,1075 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2017 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. +# +# https://github.com/de-vri-es/weechat-autosort +# + +# +# Changelog: +# 3.9: +# * Remove `buffers.pl` from recommended settings. +# 3,8: +# * Fix relative sorting on script name in default rules. +# * Document a useful property of stable sort algorithms. +# 3.7: +# * Make default rules work with bitlbee, matrix and slack. +# 3.6: +# * Add more documentation on provided info hooks. +# 3.5: +# * Add ${info:autosort_escape,...} to escape arguments for other info hooks. +# 3.4: +# * Fix rate-limit of sorting to prevent high CPU load and lock-ups. +# * Fix bug in parsing empty arguments for info hooks. +# * Add debug_log option to aid with debugging. +# * Correct a few typos. +# 3.3: +# * Fix the /autosort debug command for unicode. +# * Update the default rules to work better with Slack. +# 3.2: +# * Fix python3 compatiblity. +# 3.1: +# * Use colors to format the help text. +# 3.0: +# * Switch to evaluated expressions for sorting. +# * Add `/autosort debug` command. +# * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules. +# * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules. +# * Make tab completion context aware. +# 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 json +import math +import re +import sys +import time +import weechat + +SCRIPT_NAME = 'autosort' +SCRIPT_AUTHOR = 'Maarten de Vries ' +SCRIPT_VERSION = '3.9' +SCRIPT_LICENSE = 'GPL3' +SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.' + + +config = None +hooks = [] +signal_delay_timer = None +sort_limit_timer = None +sort_queued = False + + +# Make sure that unicode, bytes and str are always available in python2 and 3. +# For python 2, str == bytes +# For python 3, str == unicode +if sys.version_info[0] >= 3: + unicode = str + +def ensure_str(input): + ''' + Make sure the given type if the correct string type for the current python version. + That means bytes for python2 and unicode for python3. + ''' + if not isinstance(input, str): + if isinstance(input, bytes): + return input.encode('utf-8') + if isinstance(input, unicode): + return input.decode('utf-8') + return input + + +if hasattr(time, 'perf_counter'): + perf_counter = time.perf_counter +else: + perf_counter = time.clock + +def casefold(string): + if hasattr(string, 'casefold'): return string.casefold() + # Fall back to lowercasing for python2. + return string.lower() + +def list_swap(values, a, b): + values[a], values[b] = values[b], values[a] + +def list_move(values, old_index, new_index): + values.insert(new_index, values.pop(old_index)) + +def list_find(collection, value): + for i, elem in enumerate(collection): + if elem == value: return i + return None + +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)) + +def decode_rules(blob): + parsed = json.loads(blob) + if not isinstance(parsed, list): + log('Malformed rules, expected a JSON encoded list of strings, but got a {0}. No rules have been loaded. Please fix the setting manually.'.format(type(parsed))) + return [] + + for i, entry in enumerate(parsed): + if not isinstance(entry, (str, unicode)): + log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry))) + return [] + + return parsed + +def decode_helpers(blob): + parsed = json.loads(blob) + if not isinstance(parsed, dict): + log('Malformed helpers, expected a JSON encoded dictionary but got a {0}. No helpers have been loaded. Please fix the setting manually.'.format(type(parsed))) + return {} + + for key, value in parsed.items(): + if not isinstance(value, (str, unicode)): + log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix setting manually.'.format(key, type(value))) + return {} + return parsed + +class Config: + ''' The autosort configuration. ''' + + default_rules = json.dumps([ + '${core_first}', + '${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}', + '${script_or_plugin}', + '${irc_raw_first}', + '${server}', + '${info:autosort_order,${type},server,*,channel,private}', + '${hashless_name}', + '${buffer.full_name}', + ]) + + default_helpers = json.dumps({ + 'core_first': '${if:${buffer.full_name}!=core.weechat}', + 'irc_raw_first': '${if:${buffer.full_name}!=irc.irc_raw}', + 'irc_raw_last': '${if:${buffer.full_name}==irc.irc_raw}', + 'hashless_name': '${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}', + 'script_or_plugin': '${if:${script_name}?${script_name}:${plugin}}', + }) + + default_signal_delay = 5 + default_sort_limit = 100 + + 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.v3_section = None + + self.case_sensitive = False + self.rules = [] + self.helpers = {} + self.signals = [] + self.signal_delay = Config.default_signal_delay, + self.sort_limit = Config.default_sort_limit, + self.sort_on_config = True + self.debug_log = False + + self.__case_sensitive = None + self.__rules = None + self.__helpers = None + self.__signals = None + self.__signal_delay = None + self.__sort_limit = None + self.__sort_on_config = None + self.__debug_log = 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, '', '', '', '', '', '', '', '', '', '') + self.v3_section = weechat.config_new_section(self.config_file, 'v3', 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, + '', '', '', '', '', '' + ) + + weechat.config_new_option( + self.config_file, self.sorting_section, + 'rules', 'string', + 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.', + '', 0, 0, '', '', 0, + '', '', '', '', '', '' + ) + + weechat.config_new_option( + self.config_file, self.sorting_section, + 'replacements', 'string', + 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.', + '', 0, 0, '', '', 0, + '', '', '', '', '', '' + ) + + self.__rules = weechat.config_new_option( + self.config_file, self.v3_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.__helpers = weechat.config_new_option( + self.config_file, self.v3_section, + 'helpers', 'string', + 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.', + '', 0, 0, Config.default_helpers, Config.default_helpers, 0, + '', '', '', '', '', '' + ) + + self.__signals = weechat.config_new_option( + self.config_file, self.sorting_section, + 'signals', 'string', + 'A space separated list of signals that will cause autosort to resort your buffer list.', + '', 0, 0, Config.default_signals, Config.default_signals, 0, + '', '', '', '', '', '' + ) + + self.__signal_delay = weechat.config_new_option( + self.config_file, self.sorting_section, + 'signal_delay', 'integer', + 'Delay in milliseconds to wait after a signal before sorting the buffer list. This prevents triggering many times if multiple signals arrive in a short time. It can also be needed to wait for buffer localvars to be available.', + '', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0, + '', '', '', '', '', '' + ) + + self.__sort_limit = weechat.config_new_option( + self.config_file, self.sorting_section, + 'sort_limit', 'integer', + 'Minimum delay in milliseconds to wait after sorting before signals can trigger a sort again. This is effectively a rate limit on sorting. Keeping signal_delay low while setting this higher can reduce excessive sorting without a long initial delay.', + '', 0, 1000, str(Config.default_sort_limit), str(Config.default_sort_limit), 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, + '', '', '', '', '', '' + ) + + self.__debug_log = weechat.config_new_option( + self.config_file, self.sorting_section, + 'debug_log', 'boolean', + 'If enabled, print more debug messages. Not recommended for normal usage.', + '', 0, 0, 'off', 'off', 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) + + rules_blob = weechat.config_string(self.__rules) + helpers_blob = weechat.config_string(self.__helpers) + signals_blob = weechat.config_string(self.__signals) + + self.rules = decode_rules(rules_blob) + self.helpers = decode_helpers(helpers_blob) + self.signals = signals_blob.split() + self.signal_delay = weechat.config_integer(self.__signal_delay) + self.sort_limit = weechat.config_integer(self.__sort_limit) + self.sort_on_config = weechat.config_boolean(self.__sort_on_config) + self.debug_log = weechat.config_boolean(self.__debug_log) + + def save_rules(self, run_callback = True): + ''' Save the current rules to the configuration. ''' + weechat.config_option_set(self.__rules, json.dumps(self.rules), run_callback) + + def save_helpers(self, run_callback = True): + ''' Save the current helpers to the configuration. ''' + weechat.config_option_set(self.__helpers, json.dumps(self.helpers), 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 debug(message, buffer = 'NULL'): + if config.debug_log: + weechat.prnt(buffer, 'autosort: debug: {0}'.format(message)) + +def get_buffers(): + ''' Get a list of all the buffers in weechat. ''' + hdata = weechat.hdata_get('buffer') + buffer = weechat.hdata_get_list(hdata, "gui_buffers"); + + result = [] + while buffer: + number = weechat.hdata_integer(hdata, buffer, 'number') + result.append((number, buffer)) + buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer') + return hdata, result + +class MergedBuffers(list): + """ A list of merged buffers, possibly of size 1. """ + def __init__(self, number): + super(MergedBuffers, self).__init__() + self.number = number + +def merge_buffer_list(buffers): + ''' + Group merged buffers together. + The output is a list of MergedBuffers. + ''' + if not buffers: return [] + result = {} + for number, buffer in buffers: + if number not in result: result[number] = MergedBuffers(number) + result[number].append(buffer) + return result.values() + +def sort_buffers(hdata, buffers, rules, helpers, case_sensitive): + for merged in buffers: + for buffer in merged: + name = weechat.hdata_string(hdata, buffer, 'name') + + return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive)) + +def buffer_sort_key(rules, helpers, case_sensitive): + ''' Create a sort key function for a list of lists of merged buffers. ''' + def key(buffer): + extra_vars = {} + for helper_name, helper in sorted(helpers.items()): + expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {}) + extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded) + result = [] + for rule in rules: + expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {}) + result.append(expanded if case_sensitive else casefold(expanded)) + return result + + return key + +def merged_sort_key(rules, helpers, case_sensitive): + buffer_key = buffer_sort_key(rules, helpers, case_sensitive) + def key(merged): + best = None + for buffer in merged: + this = buffer_key(buffer) + if best is None or this < best: best = this + return best + return key + +def apply_buffer_order(buffers): + ''' Sort the buffers in weechat according to the given order. ''' + for i, buffer in enumerate(buffers): + weechat.buffer_set(buffer[0], "number", str(i + 1)) + +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 do_sort(verbose = False): + start = perf_counter() + + hdata, buffers = get_buffers() + buffers = merge_buffer_list(buffers) + buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive) + apply_buffer_order(buffers) + + elapsed = perf_counter() - start + if verbose: + log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed)) + else: + debug("Finished sorting buffers in {0:.4f} seconds.".format(elapsed)) + +def command_sort(buffer, command, args): + ''' Sort the buffers and print a confirmation. ''' + do_sort(True) + return weechat.WEECHAT_RC_OK + +def command_debug(buffer, command, args): + hdata, buffers = get_buffers() + buffers = merge_buffer_list(buffers) + + # Show evaluation results. + log('Individual evaluation results:') + start = perf_counter() + key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive) + results = [] + for merged in buffers: + for buffer in merged: + fullname = weechat.hdata_string(hdata, buffer, 'full_name') + results.append((fullname, key(buffer))) + elapsed = perf_counter() - start + + for fullname, result in results: + fullname = ensure_str(fullname) + result = [ensure_str(x) for x in result] + log('{0}: {1}'.format(fullname, result)) + log('Computing evaluation results took {0:.4f} seconds.'.format(elapsed)) + + 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}\n'.format(i, rule) + if not len(config.rules): + output += ' No sorting rules configured.\n' + log(output ) + + return weechat.WEECHAT_RC_OK + + +def command_rule_add(buffer, command, args): + ''' Add a rule to the rule list. ''' + config.rules.append(args) + 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') + + 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') + + 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') + + list_move(config.rules, 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') + + list_swap(config.rules, index_a, index_b) + config.save_rules() + command_rule_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_helper_list(buffer, command, args): + ''' Show the list of helpers. ''' + output = 'Helper variables:\n' + + width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys())) + + for name, expression in sorted(config.helpers.items()): + output += ' {0:>{width}}: {1}\n'.format(name, expression, width=width) + if not len(config.helpers): + output += ' No helper variables configured.' + log(output) + + return weechat.WEECHAT_RC_OK + + +def command_helper_set(buffer, command, args): + ''' Add/update a helper to the helper list. ''' + name, expression = split_args(args, 2) + + config.helpers[name] = expression + config.save_helpers() + command_helper_list(buffer, command, '') + + return weechat.WEECHAT_RC_OK + +def command_helper_delete(buffer, command, args): + ''' Delete a helper from the helper list. ''' + name = args.strip() + + del config.helpers[name] + config.save_helpers() + command_helper_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_helper_rename(buffer, command, args): + ''' Rename a helper to a new position. ''' + old_name, new_name = split_args(args, 2) + + try: + config.helpers[new_name] = config.helpers[old_name] + del config.helpers[old_name] + except KeyError: + raise HumanReadableError('No such helper: {0}'.format(old_name)) + config.save_helpers() + command_helper_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + + +def command_helper_swap(buffer, command, args): + ''' Swap two helpers. ''' + a, b = split_args(args, 2) + try: + config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b] + except KeyError as e: + raise HumanReadableError('No such helper: {0}'.format(e.args[0])) + + config.helpers.swap(index_a, index_b) + config.save_helpers() + command_helper_list(buffer, command, '') + return weechat.WEECHAT_RC_OK + +def call_command(buffer, command, args, subcommands): + ''' Call a subcommand 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_signal(data, signal, signal_data): + global signal_delay_timer + global sort_queued + + # If the sort limit timeout is started, we're in the hold-off time after sorting, just queue a sort. + if sort_limit_timer is not None: + if sort_queued: + debug('Signal {0} ignored, sort limit timeout is active and sort is already queued.'.format(signal)) + else: + debug('Signal {0} received but sort limit timeout is active, sort is now queued.'.format(signal)) + sort_queued = True + return weechat.WEECHAT_RC_OK + + # If the signal delay timeout is started, a signal was recently received, so ignore this signal. + if signal_delay_timer is not None: + debug('Signal {0} ignored, signal delay timeout active.'.format(signal)) + return weechat.WEECHAT_RC_OK + + # Otherwise, start the signal delay timeout. + debug('Signal {0} received, starting signal delay timeout of {1} ms.'.format(signal, config.signal_delay)) + weechat.hook_timer(config.signal_delay, 0, 1, "on_signal_delay_timeout", "") + return weechat.WEECHAT_RC_OK + +def on_signal_delay_timeout(pointer, remaining_calls): + """ Called when the signal_delay_timer triggers. """ + global signal_delay_timer + global sort_limit_timer + global sort_queued + + signal_delay_timer = None + + # If the sort limit timeout was started, we're still in the no-sort period, so just queue a sort. + if sort_limit_timer is not None: + debug('Signal delay timeout expired, but sort limit timeout is active, sort is now queued.') + sort_queued = True + return weechat.WEECHAT_RC_OK + + # Time to sort! + debug('Signal delay timeout expired, starting sort.') + do_sort() + + # Start the sort limit timeout if not disabled. + if config.sort_limit > 0: + debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit)) + sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "") + + return weechat.WEECHAT_RC_OK + +def on_sort_limit_timeout(pointer, remainin_calls): + """ Called when de sort_limit_timer triggers. """ + global sort_limit_timer + global sort_queued + + # If no signal was received during the timeout, we're done. + if not sort_queued: + debug('Sort limit timeout expired without receiving a signal.') + sort_limit_timer = None + return weechat.WEECHAT_RC_OK + + # Otherwise it's time to sort. + debug('Signal received during sort limit timeout, starting queued sort.') + do_sort() + sort_queued = False + + # Start the sort limit timeout again if not disabled. + if config.sort_limit > 0: + debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit)) + sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "") + + return weechat.WEECHAT_RC_OK + + +def apply_config(): + # 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_signal', '')) + + if config.sort_on_config: + debug('Sorting because configuration changed.') + do_sort() + +def on_config_changed(*args, **kwargs): + ''' Called whenever the configuration changes. ''' + config.reload() + apply_config() + + return weechat.WEECHAT_RC_OK + +def parse_arg(args): + if not args: return '', None + + result = '' + escaped = False + for i, c in enumerate(args): + if not escaped: + if c == '\\': + escaped = True + continue + elif c == ',': + return result, args[i+1:] + result += c + escaped = False + return result, None + +def parse_args(args, max = None): + result = [] + i = 0 + while max is None or i < max: + i += 1 + arg, args = parse_arg(args) + if arg is None: break + result.append(arg) + if args is None: break + return result, args + +def on_info_escape(pointer, name, arguments): + result = '' + for c in arguments: + if c == '\\': + result += '\\\\' + elif c == ',': + result += '\\,' + else: + result +=c + return result + +def on_info_replace(pointer, name, arguments): + arguments, rest = parse_args(arguments, 3) + if rest or len(arguments) < 3: + log('usage: ${{info:{0},old,new,text}}'.format(name)) + return '' + old, new, text = arguments + + return text.replace(old, new) + +def on_info_order(pointer, name, arguments): + arguments, rest = parse_args(arguments) + if len(arguments) < 1: + log('usage: ${{info:{0},value,first,second,third,...}}'.format(name)) + return '' + + value = arguments[0] + keys = arguments[1:] + if not keys: return '0' + + # Find the value in the keys (or '*' if we can't find it) + result = list_find(keys, value) + if result is None: result = list_find(keys, '*') + if result is None: result = len(keys) + + # Pad result with leading zero to make sure string sorting works. + width = int(math.log10(len(keys))) + 1 + return '{0:0{1}}'.format(result, width) + + +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, + 'debug': command_debug, + + '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, + }, + 'helpers': { + ' ': command_helper_list, + 'list': command_helper_list, + 'set': command_helper_set, + 'delete': command_helper_delete, + 'rename': command_helper_rename, + 'swap': command_helper_swap, + }, + }) + except HumanReadableError as e: + log(e) + return weechat.WEECHAT_RC_ERROR + +def add_completions(completion, words): + for word in words: + weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END) + +def autosort_complete_rules(words, completion): + if len(words) == 0: + add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update']) + if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'): + add_completions(completion, map(str, range(len(config.rules)))) + if len(words) == 2 and words[0] in ('move', 'swap'): + add_completions(completion, map(str, range(len(config.rules)))) + if len(words) == 2 and words[0] in ('update'): + try: + add_completions(completion, [config.rules[int(words[1])]]) + except KeyError: pass + except ValueError: pass + else: + add_completions(completion, ['']) + return weechat.WEECHAT_RC_OK + +def autosort_complete_helpers(words, completion): + if len(words) == 0: + add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap']) + elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'): + add_completions(completion, sorted(config.helpers.keys())) + elif len(words) == 2 and words[0] == 'swap': + add_completions(completion, sorted(config.helpers.keys())) + elif len(words) == 2 and words[0] == 'rename': + add_completions(completion, sorted(config.helpers.keys())) + elif len(words) == 2 and words[0] == 'set': + try: + add_completions(completion, [config.helpers[words[1]]]) + except KeyError: pass + return weechat.WEECHAT_RC_OK + +def on_autosort_complete(data, name, buffer, completion): + cmdline = weechat.buffer_get_string(buffer, "input") + cursor = weechat.buffer_get_integer(buffer, "input_pos") + prefix = cmdline[:cursor] + words = prefix.split()[1:] + + # If the current word isn't finished yet, + # ignore it for coming up with completion suggestions. + if prefix[-1] != ' ': words = words[:-1] + + if len(words) == 0: + add_completions(completion, ['debug', 'helpers', 'rules', 'sort']) + elif words[0] == 'rules': + return autosort_complete_rules(words[1:], completion) + elif words[0] == 'helpers': + return autosort_complete_helpers(words[1:], completion) + return weechat.WEECHAT_RC_OK + +command_description = r'''{*white}# General commands{reset} + +{*white}/autosort {brown}sort{reset} +Manually trigger the buffer sorting. + +{*white}/autosort {brown}debug{reset} +Show the evaluation results of the sort rules for each buffer. + + +{*white}# Sorting rule commands{reset} + +{*white}/autosort{brown} rules list{reset} +Print the list of sort rules. + +{*white}/autosort {brown}rules add {cyan}{reset} +Add a new rule at the end of the list. + +{*white}/autosort {brown}rules insert {cyan} {reset} +Insert a new rule at the given index in the list. + +{*white}/autosort {brown}rules update {cyan} {reset} +Update a rule in the list with a new expression. + +{*white}/autosort {brown}rules delete {cyan} +Delete a rule from the list. + +{*white}/autosort {brown}rules move {cyan} {reset} +Move a rule from one position in the list to another. + +{*white}/autosort {brown}rules swap {cyan} {reset} +Swap two rules in the list + + +{*white}# Helper variable commands{reset} + +{*white}/autosort {brown}helpers list +Print the list of helper variables. + +{*white}/autosort {brown}helpers set {cyan} +Add or update a helper variable with the given name. + +{*white}/autosort {brown}helpers delete {cyan} +Delete a helper variable. + +{*white}/autosort {brown}helpers rename {cyan} +Rename a helper variable. + +{*white}/autosort {brown}helpers swap {cyan} +Swap the expressions of two helper variables in the list. + + +{*white}# Info hooks{reset} +Autosort comes with a number of info hooks to add some extra functionality to regular weechat eval strings. +Info hooks can be used in eval strings in the form of {cyan}${{info:some_hook,arguments}}{reset}. + +Commas and backslashes in arguments to autosort info hooks (except for {cyan}${{info:autosort_escape}}{reset}) must be escaped with a backslash. + +{*white}${{info:{brown}autosort_replace{white},{cyan}pattern{white},{cyan}replacement{white},{cyan}source{white}}}{reset} +Replace all occurrences of {cyan}pattern{reset} with {cyan}replacement{reset} in the string {cyan}source{reset}. +Can be used to ignore certain strings when sorting by replacing them with an empty string. + +For example: {cyan}${{info:autosort_replace,cat,dog,the dog is meowing}}{reset} expands to "the cat is meowing". + +{*white}${{info:{brown}autosort_order{white},{cyan}value{white},{cyan}option0{white},{cyan}option1{white},{cyan}option2{white},{cyan}...{white}}} +Generate a zero-padded number that corresponds to the index of {cyan}value{reset} in the list of options. +If one of the options is the special value {brown}*{reset}, then any value not explicitly mentioned will be sorted at that position. +Otherwise, any value that does not match an option is assigned the highest number available. +Can be used to easily sort buffers based on a manual sequence. + +For example: {cyan}${{info:autosort_order,${{server}},freenode,oftc,efnet}}{reset} will sort freenode before oftc, followed by efnet and then any remaining servers. +Alternatively, {cyan}${{info:autosort_order,${{server}},freenode,oftc,*,efnet}}{reset} will sort any unlisted servers after freenode and oftc, but before efnet. + +{*white}${{info:{brown}autosort_escape{white},{cyan}text{white}}}{reset} +Escape commas and backslashes in {cyan}text{reset} by prepending them with a backslash. +This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. +Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments. + +For example, it can be used to safely pass buffer names to {cyan}${{info:autosort_replace}}{reset} like so: +{cyan}${{info:autosort_replace,##,#,${{info:autosort_escape,${{buffer.name}}}}}}{reset}. + + +{*white}# Description +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 uses a stable sorting algorithm, meaning that you can manually move buffers +to change their relative order, if they sort equal with your rule set. + +{*white}# Sort rules{reset} +Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the +buffers based on evaluated result. Earlier rules will be considered first. Only +if earlier rules produced identical results is the result of the next rule +considered for sorting purposes. + +You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will +print the evaluation results of each rule for each buffer. + +{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice +versa. You will have to manually port your old rules to version 3 if you have any. + +{*white}# Helper variables{reset} +You may define helper variables for the main sort rules to keep your rules +readable. They can be used in the main sort rules as variables. For example, +a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the +string `{cyan}${{foo}}{reset}`. + +{*white}# Automatic or manual sorting{reset} +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 `{cyan}autosort.sorting.signals{reset}` +option to add or remove any signal you like. + +If you remove all signals you can still sort your buffers manually with the +`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option +`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled. + +{*white}# Recommended settings +For the best visual effect, consider setting the following options: + {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset} + +This setting allows server buffers to be sorted independently, which is +needed to create a hierarchical tree view of the server and channel buffers. + +If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree +structure with the following setting (modify to suit your need): + {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset} +''' + +command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)' + +info_replace_description = ( + 'Replace all occurrences of `pattern` with `replacement` in the string `source`. ' + 'Can be used to ignore certain strings when sorting by replacing them with an empty string. ' + 'See /help autosort for examples.' +) +info_replace_arguments = 'pattern,replacement,source' + +info_order_description = ( + 'Generate a zero-padded number that corresponds to the index of `value` in the list of options. ' + 'If one of the options is the special value `*`, then any value not explicitly mentioned will be sorted at that position. ' + 'Otherwise, any value that does not match an option is assigned the highest number available. ' + 'Can be used to easily sort buffers based on a manual sequence. ' + 'See /help autosort for examples.' +) +info_order_arguments = 'value,first,second,third,...' + +info_escape_description = ( + 'Escape commas and backslashes in `text` by prepending them with a backslash. ' + 'This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. ' + 'Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.' + 'See /help autosort for examples.' +) +info_escape_arguments = 'text' + + +if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): + config = Config('autosort') + + colors = { + 'default': weechat.color('default'), + 'reset': weechat.color('reset'), + 'black': weechat.color('black'), + 'red': weechat.color('red'), + 'green': weechat.color('green'), + 'brown': weechat.color('brown'), + 'yellow': weechat.color('yellow'), + 'blue': weechat.color('blue'), + 'magenta': weechat.color('magenta'), + 'cyan': weechat.color('cyan'), + 'white': weechat.color('white'), + '*default': weechat.color('*default'), + '*black': weechat.color('*black'), + '*red': weechat.color('*red'), + '*green': weechat.color('*green'), + '*brown': weechat.color('*brown'), + '*yellow': weechat.color('*yellow'), + '*blue': weechat.color('*blue'), + '*magenta': weechat.color('*magenta'), + '*cyan': weechat.color('*cyan'), + '*white': weechat.color('*white'), + } + + weechat.hook_config('autosort.*', 'on_config_changed', '') + weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '') + weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '') + weechat.hook_info('autosort_escape', info_escape_description, info_escape_arguments, 'on_info_escape', '') + weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '') + weechat.hook_info('autosort_order', info_order_description, info_order_arguments, 'on_info_order', '') + + apply_config() diff --git a/config/weechat/python/colorize_nicks.py b/config/weechat/python/colorize_nicks.py new file mode 100644 index 00000000..cb95a0d6 --- /dev/null +++ b/config/weechat/python/colorize_nicks.py @@ -0,0 +1,409 @@ +# -*- 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: +# 2020-11-29: jess +# version 28: fix ignore_tags having been broken by weechat 2.9 changes +# 2020-05-09: Sébastien Helleu +# version 27: add compatibility with new weechat_print modifier data +# (WeeChat >= 2.9) +# 2018-04-06: Joey Pabalinas +# version 26: fix freezes with too many nicks in one line +# 2018-03-18: nils_2 +# version 25: fix unable to run function colorize_config_reload_cb() +# 2017-06-20: lbeziaud +# version 24: colorize utf8 nicks +# 2017-03-01, arza +# version 23: don't colorize nicklist group names +# 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 = "28" +SCRIPT_LICENSE = "GPL" +SCRIPT_DESC = "Use the weechat nick colors in the chat area" + +# Based on the recommendations in RFC 7613. A valid nick is composed +# of anything but " ,*?.!@". +VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@]+)' +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, + "", "") + 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["match_limit"] = weechat.config_new_option( + colorize_config_file, section_look, "match_limit", + "integer", "Fall back to lazy matching if greedy matches exceeds this number", "", + 20, 1000, "", "", 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 + + if modifier_data.startswith('0x'): + # WeeChat >= 2.9 + buffer, tags = modifier_data.split(';', 1) + else: + # WeeChat <= 2.8 + plugin, buffer_name, tags = modifier_data.split(';', 2) + buffer = w.buffer_search(plugin, buffer_name) + + channel = w.buffer_get_string(buffer, 'localvar_channel') + tags = tags.split(',') + + # 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 + tag_ignores = w.config_string(colorize_config_option['ignore_tags']).split(',') + for tag in tags: + if tag in tag_ignores: + 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 + + # If the matched word is not a known nick, we try to match the + # word without its first or last character (if not a letter). + # This is necessary as "foo:" is a valid nick, which could be + # adressed as "foo::". + if nick not in colored_nicks[buffer]: + if not nick[-1].isalpha() and not nick[0].isalpha(): + if nick[1:-1] in colored_nicks[buffer]: + nick = nick[1:-1] + elif not nick[0].isalpha(): + if nick[1:] in colored_nicks[buffer]: + nick = nick[1:] + elif not nick[-1].isalpha(): + if nick[:-1] in colored_nicks[buffer]: + nick = nick[:-1] + + # Check that nick is in the dictionary colored_nicks + if nick in colored_nicks[buffer]: + nick_color = colored_nicks[buffer][nick] + + try: + # Let's use greedy matching. Will check against every word in a line. + if w.config_boolean(colorize_config_option['greedy_matching']): + cnt = 0 + limit = w.config_integer(colorize_config_option['match_limit']) + + for word in line.split(): + cnt += 1 + assert cnt < limit + # if cnt > limit: + # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.'); + + 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]: + cnt += 1 + assert cnt < limit + + 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) + + # Switch to lazy matching + else: + raise AssertionError + + except AssertionError: + # Let's use lazy matching for nick + 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] = {} + + if w.infolist_string(nicklist, 'type') != 'nick': + continue + + 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/config/weechat/python/go.py b/config/weechat/python/go.py new file mode 100644 index 00000000..2ab47ed4 --- /dev/null +++ b/config/weechat/python/go.py @@ -0,0 +1,563 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2014 Sébastien Helleu +# Copyright (C) 2010 m4v +# Copyright (C) 2011 stfn +# +# 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 . +# + +# +# History: +# +# 2019-07-11, Simmo Saan +# version 2.6: fix detection of "/input search_text_here" +# 2017-04-01, Sébastien Helleu : +# version 2.5: add option "buffer_number" +# 2017-03-02, Sébastien Helleu : +# version 2.4: fix syntax and indentation error +# 2017-02-25, Simmo Saan +# version 2.3: fix fuzzy search breaking buffer number search display +# 2016-01-28, ylambda +# version 2.2: add option "fuzzy_search" +# 2015-11-12, nils_2 +# version 2.1: fix problem with buffer short_name "weechat", using option +# "use_core_instead_weechat", see: +# https://github.com/weechat/weechat/issues/574 +# 2014-05-12, Sébastien Helleu : +# version 2.0: add help on options, replace option "sort_by_activity" by +# "sort" (add sort by name and first match at beginning of +# name and by number), PEP8 compliance +# 2012-11-26, Nei +# version 1.9: add auto_jump option to automatically go to buffer when it +# is uniquely selected +# 2012-09-17, Sébastien Helleu : +# version 1.8: fix jump to non-active merged buffers (jump with buffer name +# instead of number) +# 2012-01-03 nils_2 +# version 1.7: add option "use_core_instead_weechat" +# 2012-01-03, Sébastien Helleu : +# version 1.6: make script compatible with Python 3.x +# 2011-08-24, stfn : +# version 1.5: /go with name argument jumps directly to buffer +# Remember cursor position in buffer input +# 2011-05-31, Elián Hanisch : +# version 1.4: Sort list of buffers by activity. +# 2011-04-25, Sébastien Helleu : +# version 1.3: add info "go_running" (used by script input_lock.rb) +# 2010-11-01, Sébastien Helleu : +# version 1.2: use high priority for hooks to prevent conflict with other +# plugins/scripts (WeeChat >= 0.3.4 only) +# 2010-03-25, Elián Hanisch : +# version 1.1: use a space to match the end of a string +# 2009-11-16, Sébastien Helleu : +# version 1.0: add new option to display short names +# 2009-06-15, Sébastien Helleu : +# version 0.9: fix typo in /help go with command /key +# 2009-05-16, Sébastien Helleu : +# version 0.8: search buffer by number, fix bug when window is split +# 2009-05-03, Sébastien Helleu : +# version 0.7: eat tab key (do not complete input, just move buffer +# pointer) +# 2009-05-02, Sébastien Helleu : +# version 0.6: sync with last API changes +# 2009-03-22, Sébastien Helleu : +# version 0.5: update modifier signal name for input text display, +# fix arguments for function string_remove_color +# 2009-02-18, Sébastien Helleu : +# version 0.4: do not hook command and init options if register failed +# 2009-02-08, Sébastien Helleu : +# version 0.3: case insensitive search for buffers names +# 2009-02-08, Sébastien Helleu : +# version 0.2: add help about Tab key +# 2009-02-08, Sébastien Helleu : +# version 0.1: initial release +# + +""" +Quick jump to buffers. +(this script requires WeeChat 0.3.0 or newer) +""" + +from __future__ import print_function + +SCRIPT_NAME = 'go' +SCRIPT_AUTHOR = 'Sébastien Helleu ' +SCRIPT_VERSION = '2.6' +SCRIPT_LICENSE = 'GPL3' +SCRIPT_DESC = 'Quick jump to buffers' + +SCRIPT_COMMAND = 'go' + +IMPORT_OK = True + +try: + import weechat +except ImportError: + print('This script must be run under WeeChat.') + print('Get WeeChat now at: http://www.weechat.org/') + IMPORT_OK = False + +import re + +# script options +SETTINGS = { + 'color_number': ( + 'yellow,magenta', + 'color for buffer number (not selected)'), + 'color_number_selected': ( + 'yellow,red', + 'color for selected buffer number'), + 'color_name': ( + 'black,cyan', + 'color for buffer name (not selected)'), + 'color_name_selected': ( + 'black,brown', + 'color for a selected buffer name'), + 'color_name_highlight': ( + 'red,cyan', + 'color for highlight in buffer name (not selected)'), + 'color_name_highlight_selected': ( + 'red,brown', + 'color for highlight in a selected buffer name'), + 'message': ( + 'Go to: ', + 'message to display before list of buffers'), + 'short_name': ( + 'off', + 'display and search in short names instead of buffer name'), + 'sort': ( + 'number,beginning', + 'comma-separated list of keys to sort buffers ' + '(the order is important, sorts are performed in the given order): ' + 'name = sort by name (or short name), ', + 'hotlist = sort by hotlist order, ' + 'number = first match a buffer number before digits in name, ' + 'beginning = first match at beginning of names (or short names); ' + 'the default sort of buffers is by numbers'), + 'use_core_instead_weechat': ( + 'off', + 'use name "core" instead of "weechat" for core buffer'), + 'auto_jump': ( + 'off', + 'automatically jump to buffer when it is uniquely selected'), + 'fuzzy_search': ( + 'off', + 'search buffer matches using approximation'), + 'buffer_number': ( + 'on', + 'display buffer number'), +} + +# hooks management +HOOK_COMMAND_RUN = { + 'input': ('/input *', 'go_command_run_input'), + 'buffer': ('/buffer *', 'go_command_run_buffer'), + 'window': ('/window *', 'go_command_run_window'), +} +hooks = {} + +# input before command /go (we'll restore it later) +saved_input = '' +saved_input_pos = 0 + +# last user input (if changed, we'll update list of matching buffers) +old_input = None + +# matching buffers +buffers = [] +buffers_pos = 0 + + +def go_option_enabled(option): + """Checks if a boolean script option is enabled or not.""" + return weechat.config_string_to_boolean(weechat.config_get_plugin(option)) + + +def go_info_running(data, info_name, arguments): + """Returns "1" if go is running, otherwise "0".""" + return '1' if 'modifier' in hooks else '0' + + +def go_unhook_one(hook): + """Unhook something hooked by this script.""" + global hooks + if hook in hooks: + weechat.unhook(hooks[hook]) + del hooks[hook] + + +def go_unhook_all(): + """Unhook all.""" + go_unhook_one('modifier') + for hook in HOOK_COMMAND_RUN: + go_unhook_one(hook) + + +def go_hook_all(): + """Hook command_run and modifier.""" + global hooks + priority = '' + version = weechat.info_get('version_number', '') or 0 + # use high priority for hook to prevent conflict with other plugins/scripts + # (WeeChat >= 0.3.4 only) + if int(version) >= 0x00030400: + priority = '2000|' + for hook, value in HOOK_COMMAND_RUN.items(): + if hook not in hooks: + hooks[hook] = weechat.hook_command_run( + '%s%s' % (priority, value[0]), + value[1], '') + if 'modifier' not in hooks: + hooks['modifier'] = weechat.hook_modifier( + 'input_text_display_with_cursor', 'go_input_modifier', '') + + +def go_start(buf): + """Start go on buffer.""" + global saved_input, saved_input_pos, old_input, buffers_pos + go_hook_all() + saved_input = weechat.buffer_get_string(buf, 'input') + saved_input_pos = weechat.buffer_get_integer(buf, 'input_pos') + weechat.buffer_set(buf, 'input', '') + old_input = None + buffers_pos = 0 + + +def go_end(buf): + """End go on buffer.""" + global saved_input, saved_input_pos, old_input + go_unhook_all() + weechat.buffer_set(buf, 'input', saved_input) + weechat.buffer_set(buf, 'input_pos', str(saved_input_pos)) + old_input = None + + +def go_match_beginning(buf, string): + """Check if a string matches the beginning of buffer name/short name.""" + if not string: + return False + esc_str = re.escape(string) + if re.search(r'^#?' + esc_str, buf['name']) \ + or re.search(r'^#?' + esc_str, buf['short_name']): + return True + return False + + +def go_match_fuzzy(name, string): + """Check if string matches name using approximation.""" + if not string: + return False + + name_len = len(name) + string_len = len(string) + + if string_len > name_len: + return False + if name_len == string_len: + return name == string + + # Attempt to match all chars somewhere in name + prev_index = -1 + for i, char in enumerate(string): + index = name.find(char, prev_index+1) + if index == -1: + return False + prev_index = index + return True + + +def go_now(buf, args): + """Go to buffer specified by args.""" + listbuf = go_matching_buffers(args) + if not listbuf: + return + + # prefer buffer that matches at beginning (if option is enabled) + if 'beginning' in weechat.config_get_plugin('sort').split(','): + for index in range(len(listbuf)): + if go_match_beginning(listbuf[index], args): + weechat.command(buf, + '/buffer ' + str(listbuf[index]['full_name'])) + return + + # jump to first buffer in matching buffers by default + weechat.command(buf, '/buffer ' + str(listbuf[0]['full_name'])) + + +def go_cmd(data, buf, args): + """Command "/go": just hook what we need.""" + global hooks + if args: + go_now(buf, args) + elif 'modifier' in hooks: + go_end(buf) + else: + go_start(buf) + return weechat.WEECHAT_RC_OK + + +def go_matching_buffers(strinput): + """Return a list with buffers matching user input.""" + global buffers_pos + listbuf = [] + if len(strinput) == 0: + buffers_pos = 0 + strinput = strinput.lower() + infolist = weechat.infolist_get('buffer', '', '') + while weechat.infolist_next(infolist): + short_name = weechat.infolist_string(infolist, 'short_name') + if go_option_enabled('short_name'): + name = weechat.infolist_string(infolist, 'short_name') + else: + name = weechat.infolist_string(infolist, 'name') + if name == 'weechat' \ + and go_option_enabled('use_core_instead_weechat') \ + and weechat.infolist_string(infolist, 'plugin_name') == 'core': + name = 'core' + number = weechat.infolist_integer(infolist, 'number') + full_name = weechat.infolist_string(infolist, 'full_name') + if not full_name: + full_name = '%s.%s' % ( + weechat.infolist_string(infolist, 'plugin_name'), + weechat.infolist_string(infolist, 'name')) + pointer = weechat.infolist_pointer(infolist, 'pointer') + matching = name.lower().find(strinput) >= 0 + if not matching and strinput[-1] == ' ': + matching = name.lower().endswith(strinput.strip()) + if not matching and go_option_enabled('fuzzy_search'): + matching = go_match_fuzzy(name.lower(), strinput) + if not matching and strinput.isdigit(): + matching = str(number).startswith(strinput) + if len(strinput) == 0 or matching: + listbuf.append({ + 'number': number, + 'short_name': short_name, + 'name': name, + 'full_name': full_name, + 'pointer': pointer, + }) + weechat.infolist_free(infolist) + + # sort buffers + hotlist = [] + infolist = weechat.infolist_get('hotlist', '', '') + while weechat.infolist_next(infolist): + hotlist.append( + weechat.infolist_pointer(infolist, 'buffer_pointer')) + weechat.infolist_free(infolist) + last_index_hotlist = len(hotlist) + + def _sort_name(buf): + """Sort buffers by name (or short name).""" + return buf['name'] + + def _sort_hotlist(buf): + """Sort buffers by hotlist order.""" + try: + return hotlist.index(buf['pointer']) + except ValueError: + # not in hotlist, always last. + return last_index_hotlist + + def _sort_match_number(buf): + """Sort buffers by match on number.""" + return 0 if str(buf['number']) == strinput else 1 + + def _sort_match_beginning(buf): + """Sort buffers by match at beginning.""" + return 0 if go_match_beginning(buf, strinput) else 1 + + funcs = { + 'name': _sort_name, + 'hotlist': _sort_hotlist, + 'number': _sort_match_number, + 'beginning': _sort_match_beginning, + } + + for key in weechat.config_get_plugin('sort').split(','): + if key in funcs: + listbuf = sorted(listbuf, key=funcs[key]) + + if not strinput: + index = [i for i, buf in enumerate(listbuf) + if buf['pointer'] == weechat.current_buffer()] + if index: + buffers_pos = index[0] + + return listbuf + + +def go_buffers_to_string(listbuf, pos, strinput): + """Return string built with list of buffers found (matching user input).""" + string = '' + strinput = strinput.lower() + for i in range(len(listbuf)): + selected = '_selected' if i == pos else '' + buffer_name = listbuf[i]['name'] + index = buffer_name.lower().find(strinput) + if index >= 0: + index2 = index + len(strinput) + name = '%s%s%s%s%s' % ( + buffer_name[:index], + weechat.color(weechat.config_get_plugin( + 'color_name_highlight' + selected)), + buffer_name[index:index2], + weechat.color(weechat.config_get_plugin( + 'color_name' + selected)), + buffer_name[index2:]) + elif go_option_enabled("fuzzy_search") and \ + go_match_fuzzy(buffer_name.lower(), strinput): + name = "" + prev_index = -1 + for char in strinput.lower(): + index = buffer_name.lower().find(char, prev_index+1) + if prev_index < 0: + name += buffer_name[:index] + name += weechat.color(weechat.config_get_plugin( + 'color_name_highlight' + selected)) + if prev_index >= 0 and index > prev_index+1: + name += weechat.color(weechat.config_get_plugin( + 'color_name' + selected)) + name += buffer_name[prev_index+1:index] + name += weechat.color(weechat.config_get_plugin( + 'color_name_highlight' + selected)) + name += buffer_name[index] + prev_index = index + + name += weechat.color(weechat.config_get_plugin( + 'color_name' + selected)) + name += buffer_name[prev_index+1:] + else: + name = buffer_name + string += ' ' + if go_option_enabled('buffer_number'): + string += '%s%s' % ( + weechat.color(weechat.config_get_plugin( + 'color_number' + selected)), + str(listbuf[i]['number'])) + string += '%s%s%s' % ( + weechat.color(weechat.config_get_plugin( + 'color_name' + selected)), + name, + weechat.color('reset')) + return ' ' + string if string else '' + + +def go_input_modifier(data, modifier, modifier_data, string): + """This modifier is called when input text item is built by WeeChat. + + This is commonly called after changes in input or cursor move: it builds + a new input with prefix ("Go to:"), and suffix (list of buffers found). + """ + global old_input, buffers, buffers_pos + if modifier_data != weechat.current_buffer(): + return '' + names = '' + new_input = weechat.string_remove_color(string, '') + new_input = new_input.lstrip() + if old_input is None or new_input != old_input: + old_buffers = buffers + buffers = go_matching_buffers(new_input) + if buffers != old_buffers and len(new_input) > 0: + if len(buffers) == 1 and go_option_enabled('auto_jump'): + weechat.command(modifier_data, '/wait 1ms /input return') + buffers_pos = 0 + old_input = new_input + names = go_buffers_to_string(buffers, buffers_pos, new_input.strip()) + return weechat.config_get_plugin('message') + string + names + + +def go_command_run_input(data, buf, command): + """Function called when a command "/input xxx" is run.""" + global buffers, buffers_pos + if command.startswith('/input search_text') or command.startswith('/input jump'): + # search text or jump to another buffer is forbidden now + return weechat.WEECHAT_RC_OK_EAT + elif command == '/input complete_next': + # choose next buffer in list + buffers_pos += 1 + if buffers_pos >= len(buffers): + buffers_pos = 0 + weechat.hook_signal_send('input_text_changed', + weechat.WEECHAT_HOOK_SIGNAL_STRING, '') + return weechat.WEECHAT_RC_OK_EAT + elif command == '/input complete_previous': + # choose previous buffer in list + buffers_pos -= 1 + if buffers_pos < 0: + buffers_pos = len(buffers) - 1 + weechat.hook_signal_send('input_text_changed', + weechat.WEECHAT_HOOK_SIGNAL_STRING, '') + return weechat.WEECHAT_RC_OK_EAT + elif command == '/input return': + # switch to selected buffer (if any) + go_end(buf) + if len(buffers) > 0: + weechat.command( + buf, '/buffer ' + str(buffers[buffers_pos]['full_name'])) + return weechat.WEECHAT_RC_OK_EAT + return weechat.WEECHAT_RC_OK + + +def go_command_run_buffer(data, buf, command): + """Function called when a command "/buffer xxx" is run.""" + return weechat.WEECHAT_RC_OK_EAT + + +def go_command_run_window(data, buf, command): + """Function called when a command "/window xxx" is run.""" + return weechat.WEECHAT_RC_OK_EAT + + +def go_unload_script(): + """Function called when script is unloaded.""" + go_unhook_all() + return weechat.WEECHAT_RC_OK + + +def go_main(): + """Entry point.""" + if not weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, + 'go_unload_script', ''): + return + weechat.hook_command( + SCRIPT_COMMAND, + 'Quick jump to buffers', '[name]', + 'name: directly jump to buffer by name (without argument, list is ' + 'displayed)\n\n' + 'You can bind command to a key, for example:\n' + ' /key bind meta-g /go\n\n' + 'You can use completion key (commonly Tab and shift-Tab) to select ' + 'next/previous buffer in list.', + '%(buffers_names)', + 'go_cmd', '') + + # set default settings + version = weechat.info_get('version_number', '') or 0 + for option, value in SETTINGS.items(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value[0]) + if int(version) >= 0x00030500: + weechat.config_set_desc_plugin( + option, '%s (default: "%s")' % (value[1], value[0])) + weechat.hook_info('go_running', + 'Return "1" if go is running, otherwise "0"', + '', + 'go_info_running', '') + + +if __name__ == "__main__" and IMPORT_OK: + go_main() diff --git a/config/weechat/relay.conf b/config/weechat/relay.conf new file mode 100644 index 00000000..15346c33 --- /dev/null +++ b/config/weechat/relay.conf @@ -0,0 +1,59 @@ +# +# weechat -- relay.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +auto_open_buffer = on +raw_messages = 256 + +[color] +client = cyan +status_active = green +status_auth_failed = lightmagenta +status_connecting = white +status_disconnected = lightred +status_waiting_auth = yellow +text = default +text_bg = default +text_selected = white + +[network] +allow_empty_password = off +allowed_ips = "" +auth_timeout = 60 +bind_address = "" +clients_purge_delay = 0 +compression_level = 6 +ipv6 = on +max_clients = 5 +nonce_size = 16 +password = "" +password_hash_algo = "*" +password_hash_iterations = 100000 +ssl_cert_key = "%h/ssl/relay.pem" +ssl_priorities = "NORMAL:-VERS-SSL3.0" +totp_secret = "" +totp_window = 0 +websocket_allowed_origins = "" + +[irc] +backlog_max_minutes = 0 +backlog_max_number = 1024 +backlog_since_last_disconnect = on +backlog_since_last_message = off +backlog_tags = "irc_privmsg" +backlog_time_format = "[%H:%M] " + +[weechat] +commands = "" + +[port] + +[path] diff --git a/config/weechat/ruby.conf b/config/weechat/ruby.conf new file mode 100644 index 00000000..f6de7fbf --- /dev/null +++ b/config/weechat/ruby.conf @@ -0,0 +1,14 @@ +# +# weechat -- ruby.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +check_license = off +eval_keep_context = on diff --git a/config/weechat/script.conf b/config/weechat/script.conf new file mode 100644 index 00000000..519bb1d1 --- /dev/null +++ b/config/weechat/script.conf @@ -0,0 +1,57 @@ +# +# weechat -- script.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +columns = "%s %n %V %v %u | %d | %t" +diff_color = on +diff_command = "auto" +display_source = on +quiet_actions = on +sort = "i,p,n" +translate_description = on +use_keys = on + +[color] +status_autoloaded = cyan +status_held = white +status_installed = lightcyan +status_obsolete = lightmagenta +status_popular = yellow +status_running = lightgreen +status_unknown = lightred +text = default +text_bg = default +text_bg_selected = red +text_date = default +text_date_selected = white +text_delimiters = default +text_description = default +text_description_selected = white +text_extension = default +text_extension_selected = white +text_name = cyan +text_name_selected = lightcyan +text_selected = white +text_tags = brown +text_tags_selected = yellow +text_version = magenta +text_version_loaded = default +text_version_loaded_selected = white +text_version_selected = lightmagenta + +[scripts] +autoload = on +cache_expire = 1440 +download_enabled = on +download_timeout = 30 +hold = "" +path = "%h/script" +url = "https://weechat.org/files/plugins.xml.gz" diff --git a/config/weechat/script/plugins.xml.gz b/config/weechat/script/plugins.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..a271e7e3cf5a6b80d965cd3ad07f10987c5cff4e GIT binary patch literal 129202 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

fEG8y0AE0$zp(s#IzklZ6g^%X`{J=a8N~=; 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%0DFRLX>Zv1!Jv~q`jn^cbEeO~Jk&8QjLayal8nXr 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 = "/input jump_next_visited_buffer" +meta-B = "/buflist toggle" +meta-N = "/bar toggle nicklist" +meta-OA = "/input history_global_previous" +meta-OB = "/input history_global_next" +meta-OC = "/input move_next_word" +meta-OD = "/input move_previous_word" +meta-OF = "/input move_end_of_line" +meta-OH = "/input move_beginning_of_line" +meta-OP = "/bar scroll buflist * -100%" +meta-OQ = "/bar scroll buflist * +100%" +meta-Oa = "/input history_global_previous" +meta-Ob = "/input history_global_next" +meta-Oc = "/input move_next_word" +meta-Od = "/input move_previous_word" +meta2-11^ = "/bar scroll buflist * -100%" +meta2-11~ = "/bar scroll buflist * -100%" +meta2-12^ = "/bar scroll buflist * +100%" +meta2-12~ = "/bar scroll buflist * +100%" +meta2-15~ = "/buffer -1" +meta2-17~ = "/buffer +1" +meta2-18~ = "/window -1" +meta2-19~ = "/window +1" +meta2-1;3A = "/buffer -1" +meta2-1;3B = "/buffer +1" +meta2-1;3C = "/buffer +1" +meta2-1;3D = "/buffer -1" +meta2-1;3F = "/window scroll_bottom" +meta2-1;3H = "/window scroll_top" +meta2-1;3P = "/bar scroll buflist * b" +meta2-1;3Q = "/bar scroll buflist * e" +meta2-1;5A = "/input history_global_previous" +meta2-1;5B = "/input history_global_next" +meta2-1;5C = "/input move_next_word" +meta2-1;5D = "/input move_previous_word" +meta2-1;5P = "/bar scroll buflist * -100%" +meta2-1;5Q = "/bar scroll buflist * +100%" +meta2-1~ = "/input move_beginning_of_line" +meta2-200~ = "/input paste_start" +meta2-201~ = "/input paste_stop" +meta2-20~ = "/bar scroll title * -30%" +meta2-21~ = "/bar scroll title * +30%" +meta2-23;3~ = "/bar scroll nicklist * b" +meta2-23;5~ = "/bar scroll nicklist * -100%" +meta2-23^ = "/bar scroll nicklist * -100%" +meta2-23~ = "/bar scroll nicklist * -100%" +meta2-24;3~ = "/bar scroll nicklist * e" +meta2-24;5~ = "/bar scroll nicklist * +100%" +meta2-24^ = "/bar scroll nicklist * +100%" +meta2-24~ = "/bar scroll nicklist * +100%" +meta2-3~ = "/input delete_next_char" +meta2-4~ = "/input move_end_of_line" +meta2-5;3~ = "/window scroll_up" +meta2-5~ = "/window page_up" +meta2-6;3~ = "/window scroll_down" +meta2-6~ = "/window page_down" +meta2-7~ = "/input move_beginning_of_line" +meta2-8~ = "/input move_end_of_line" +meta2-A = "/input history_previous" +meta2-B = "/input history_next" +meta2-C = "/input move_next_char" +meta2-D = "/input move_previous_char" +meta2-F = "/input move_end_of_line" +meta2-G = "/window page_down" +meta2-H = "/input move_beginning_of_line" +meta2-I = "/window page_up" +meta2-Z = "/input complete_previous" +meta2-[E = "/buffer -1" +meta-_ = "/input redo" +meta-a = "/input jump_smart" +meta-b = "/input move_previous_word" +meta-d = "/input delete_next_word" +meta-f = "/input move_next_word" +meta-h = "/input hotlist_clear" +meta-jmeta-f = "/buffer -" +meta-jmeta-l = "/buffer +" +meta-jmeta-r = "/server raw" +meta-jmeta-s = "/server jump" +meta-j01 = "/buffer *1" +meta-j02 = "/buffer *2" +meta-j03 = "/buffer *3" +meta-j04 = "/buffer *4" +meta-j05 = "/buffer *5" +meta-j06 = "/buffer *6" +meta-j07 = "/buffer *7" +meta-j08 = "/buffer *8" +meta-j09 = "/buffer *9" +meta-j10 = "/buffer *10" +meta-j11 = "/buffer *11" +meta-j12 = "/buffer *12" +meta-j13 = "/buffer *13" +meta-j14 = "/buffer *14" +meta-j15 = "/buffer *15" +meta-j16 = "/buffer *16" +meta-j17 = "/buffer *17" +meta-j18 = "/buffer *18" +meta-j19 = "/buffer *19" +meta-j20 = "/buffer *20" +meta-j21 = "/buffer *21" +meta-j22 = "/buffer *22" +meta-j23 = "/buffer *23" +meta-j24 = "/buffer *24" +meta-j25 = "/buffer *25" +meta-j26 = "/buffer *26" +meta-j27 = "/buffer *27" +meta-j28 = "/buffer *28" +meta-j29 = "/buffer *29" +meta-j30 = "/buffer *30" +meta-j31 = "/buffer *31" +meta-j32 = "/buffer *32" +meta-j33 = "/buffer *33" +meta-j34 = "/buffer *34" +meta-j35 = "/buffer *35" +meta-j36 = "/buffer *36" +meta-j37 = "/buffer *37" +meta-j38 = "/buffer *38" +meta-j39 = "/buffer *39" +meta-j40 = "/buffer *40" +meta-j41 = "/buffer *41" +meta-j42 = "/buffer *42" +meta-j43 = "/buffer *43" +meta-j44 = "/buffer *44" +meta-j45 = "/buffer *45" +meta-j46 = "/buffer *46" +meta-j47 = "/buffer *47" +meta-j48 = "/buffer *48" +meta-j49 = "/buffer *49" +meta-j50 = "/buffer *50" +meta-j51 = "/buffer *51" +meta-j52 = "/buffer *52" +meta-j53 = "/buffer *53" +meta-j54 = "/buffer *54" +meta-j55 = "/buffer *55" +meta-j56 = "/buffer *56" +meta-j57 = "/buffer *57" +meta-j58 = "/buffer *58" +meta-j59 = "/buffer *59" +meta-j60 = "/buffer *60" +meta-j61 = "/buffer *61" +meta-j62 = "/buffer *62" +meta-j63 = "/buffer *63" +meta-j64 = "/buffer *64" +meta-j65 = "/buffer *65" +meta-j66 = "/buffer *66" +meta-j67 = "/buffer *67" +meta-j68 = "/buffer *68" +meta-j69 = "/buffer *69" +meta-j70 = "/buffer *70" +meta-j71 = "/buffer *71" +meta-j72 = "/buffer *72" +meta-j73 = "/buffer *73" +meta-j74 = "/buffer *74" +meta-j75 = "/buffer *75" +meta-j76 = "/buffer *76" +meta-j77 = "/buffer *77" +meta-j78 = "/buffer *78" +meta-j79 = "/buffer *79" +meta-j80 = "/buffer *80" +meta-j81 = "/buffer *81" +meta-j82 = "/buffer *82" +meta-j83 = "/buffer *83" +meta-j84 = "/buffer *84" +meta-j85 = "/buffer *85" +meta-j86 = "/buffer *86" +meta-j87 = "/buffer *87" +meta-j88 = "/buffer *88" +meta-j89 = "/buffer *89" +meta-j90 = "/buffer *90" +meta-j91 = "/buffer *91" +meta-j92 = "/buffer *92" +meta-j93 = "/buffer *93" +meta-j94 = "/buffer *94" +meta-j95 = "/buffer *95" +meta-j96 = "/buffer *96" +meta-j97 = "/buffer *97" +meta-j98 = "/buffer *98" +meta-j99 = "/buffer *99" +meta-k = "/input grab_key_command" +meta-l = "/window bare" +meta-m = "/mute mouse toggle" +meta-n = "/window scroll_next_highlight" +meta-p = "/window scroll_previous_highlight" +meta-r = "/input delete_line" +meta-s = "/mute spell toggle" +meta-u = "/window scroll_unread" +meta-wmeta-meta2-A = "/window up" +meta-wmeta-meta2-B = "/window down" +meta-wmeta-meta2-C = "/window right" +meta-wmeta-meta2-D = "/window left" +meta-wmeta2-1;3A = "/window up" +meta-wmeta2-1;3B = "/window down" +meta-wmeta2-1;3C = "/window right" +meta-wmeta2-1;3D = "/window left" +meta-wmeta-b = "/window balance" +meta-wmeta-s = "/window swap" +meta-x = "/input zoom_merged_buffer" +meta-z = "/window zoom" +ctrl-_ = "/input undo" + +[key_search] +ctrl-I = "/input search_switch_where" +ctrl-J = "/input search_stop_here" +ctrl-M = "/input search_stop_here" +ctrl-Q = "/input search_stop" +ctrl-R = "/input search_switch_regex" +meta2-A = "/input search_previous" +meta2-B = "/input search_next" +meta-c = "/input search_switch_case" + +[key_cursor] +ctrl-J = "/cursor stop" +ctrl-M = "/cursor stop" +meta-meta2-A = "/cursor move area_up" +meta-meta2-B = "/cursor move area_down" +meta-meta2-C = "/cursor move area_right" +meta-meta2-D = "/cursor move area_left" +meta2-1;3A = "/cursor move area_up" +meta2-1;3B = "/cursor move area_down" +meta2-1;3C = "/cursor move area_right" +meta2-1;3D = "/cursor move area_left" +meta2-A = "/cursor move up" +meta2-B = "/cursor move down" +meta2-C = "/cursor move right" +meta2-D = "/cursor move left" +@item(buffer_nicklist):K = "/window ${_window_number};/kickban ${nick}" +@item(buffer_nicklist):b = "/window ${_window_number};/ban ${nick}" +@item(buffer_nicklist):k = "/window ${_window_number};/kick ${nick}" +@item(buffer_nicklist):q = "/window ${_window_number};/query ${nick};/cursor stop" +@item(buffer_nicklist):w = "/window ${_window_number};/whois ${nick}" +@chat:Q = "hsignal:chat_quote_time_prefix_message;/cursor stop" +@chat:m = "hsignal:chat_quote_message;/cursor stop" +@chat:q = "hsignal:chat_quote_prefix_message;/cursor stop" + +[key_mouse] +@bar(buflist):ctrl-wheeldown = "hsignal:buflist_mouse" +@bar(buflist):ctrl-wheelup = "hsignal:buflist_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(fset.fset):button1 = "/window ${_window_number};/fset -go ${_chat_line_y}" +@chat(fset.fset):button2* = "hsignal:fset_mouse" +@chat(fset.fset):wheeldown = "/fset -down 5" +@chat(fset.fset):wheelup = "/fset -up 5" +@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" +@chat(script.scripts):wheelup = "/script up 5" +@item(buffer_nicklist):button1 = "/window ${_window_number};/query ${nick}" +@item(buffer_nicklist):button1-gesture-left = "/window ${_window_number};/kick ${nick}" +@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(buflist):button1* = "hsignal:buflist_mouse" +@item(buflist):button2* = "hsignal:buflist_mouse" +@item(buflist2):button1* = "hsignal:buflist_mouse" +@item(buflist2):button2* = "hsignal:buflist_mouse" +@item(buflist3):button1* = "hsignal:buflist_mouse" +@item(buflist3):button2* = "hsignal:buflist_mouse" +@bar:wheeldown = "/bar scroll ${_bar_name} ${_window_number} +20%" +@bar:wheelup = "/bar scroll ${_bar_name} ${_window_number} -20%" +@chat:button1 = "/window ${_window_number}" +@chat:button1-gesture-left = "/window ${_window_number};/buffer -1" +@chat:button1-gesture-left-long = "/window ${_window_number};/buffer 1" +@chat:button1-gesture-right = "/window ${_window_number};/buffer +1" +@chat:button1-gesture-right-long = "/window ${_window_number};/input jump_last_buffer" +@chat:ctrl-wheeldown = "/window scroll_horiz -window ${_window_number} +10%" +@chat:ctrl-wheelup = "/window scroll_horiz -window ${_window_number} -10%" +@chat:wheeldown = "/window scroll_down -window ${_window_number}" +@chat:wheelup = "/window scroll_up -window ${_window_number}" +@*:button3 = "/cursor go ${_x},${_y}" diff --git a/config/weechat/weechat.log b/config/weechat/weechat.log new file mode 100644 index 00000000..2eb7d2be --- /dev/null +++ b/config/weechat/weechat.log @@ -0,0 +1,51 @@ +[2021-04-04 01:40:21] WeeChat 3.1 (compiled on Mar 11 2021 21:41:58) +[2021-04-04 01:40:21] Reading configuration file sec.conf +[2021-04-04 01:40:27] Reading configuration file weechat.conf +[2021-04-04 01:40:27] Reading configuration file plugins.conf +[2021-04-04 01:40:27] Reading configuration file charset.conf +[2021-04-04 01:40:27] Reading configuration file logger.conf +[2021-04-04 01:40:27] Reading configuration file exec.conf +[2021-04-04 01:40:27] Reading configuration file trigger.conf +[2021-04-04 01:40:27] Reading configuration file spell.conf +[2021-04-04 01:40:27] Reading configuration file alias.conf +[2021-04-04 01:40:27] Reading configuration file buflist.conf +[2021-04-04 01:40:27] Reading configuration file fifo.conf +[2021-04-04 01:40:27] Reading configuration file xfer.conf +[2021-04-04 01:40:27] Reading configuration file irc.conf +[2021-04-04 01:40:27] Reading configuration file relay.conf +[2021-04-04 01:40:27] Reading configuration file tcl.conf +[2021-04-04 01:40:27] Reading configuration file python.conf +[2021-04-04 01:40:27] Reading configuration file colorize_nicks.conf +[2021-04-04 01:40:27] Reading configuration file autosort.conf +[2021-04-04 01:40:27] Writing configuration file autosort.conf +[2021-04-04 01:40:27] Reading configuration file guile.conf +[2021-04-04 01:40:27] Reading configuration file perl.conf +[2021-04-04 01:40:27] Reading configuration file ruby.conf +[2021-04-04 01:40:27] Reading configuration file lua.conf +[2021-04-04 01:40:27] Reading configuration file script.conf +[2021-04-04 01:40:27] Reading configuration file fset.conf +[2021-04-04 01:40:27] irc: connecting to server chat.freenode.net/6697 (SSL)... +[2021-04-04 01:41:47] Writing configuration file plugins.conf +[2021-04-04 01:41:47] Writing configuration file trigger.conf +[2021-04-04 01:41:47] Writing configuration file spell.conf +[2021-04-04 01:41:47] Writing configuration file relay.conf +[2021-04-04 01:41:47] Writing configuration file lua.conf +[2021-04-04 01:41:47] Writing configuration file ruby.conf +[2021-04-04 01:41:47] Writing configuration file xfer.conf +[2021-04-04 01:41:47] Writing configuration file fifo.conf +[2021-04-04 01:41:47] Writing configuration file exec.conf +[2021-04-04 01:41:47] Writing configuration file buflist.conf +[2021-04-04 01:41:47] Writing configuration file logger.conf +[2021-04-04 01:41:47] Writing configuration file perl.conf +[2021-04-04 01:41:47] Writing configuration file charset.conf +[2021-04-04 01:41:47] Writing configuration file alias.conf +[2021-04-04 01:41:47] Writing configuration file fset.conf +[2021-04-04 01:41:47] Writing configuration file guile.conf +[2021-04-04 01:41:47] Writing configuration file autosort.conf +[2021-04-04 01:41:47] Writing configuration file colorize_nicks.conf +[2021-04-04 01:41:47] Writing configuration file python.conf +[2021-04-04 01:41:47] Writing configuration file irc.conf +[2021-04-04 01:41:47] Writing configuration file tcl.conf +[2021-04-04 01:41:47] Writing configuration file script.conf +[2021-04-04 01:41:47] Writing configuration file weechat.conf +[2021-04-04 01:41:47] Writing configuration file sec.conf diff --git a/config/weechat/xfer.conf b/config/weechat/xfer.conf new file mode 100644 index 00000000..551316ff --- /dev/null +++ b/config/weechat/xfer.conf @@ -0,0 +1,49 @@ +# +# weechat -- xfer.conf +# +# WARNING: It is NOT recommended to edit this file by hand, +# especially if WeeChat is running. +# +# Use /set or similar command to change settings in WeeChat. +# +# For more info, see: https://weechat.org/doc/quickstart +# + +[look] +auto_open_buffer = on +progress_bar_size = 20 +pv_tags = "notify_private" + +[color] +status_aborted = lightred +status_active = lightblue +status_connecting = yellow +status_done = lightgreen +status_failed = lightred +status_waiting = lightcyan +text = default +text_bg = default +text_selected = white + +[network] +blocksize = 65536 +fast_send = on +own_ip = "" +port_range = "" +send_ack = on +speed_limit_recv = 0 +speed_limit_send = 0 +timeout = 300 + +[file] +auto_accept_chats = off +auto_accept_files = off +auto_accept_nicks = "" +auto_check_crc32 = off +auto_rename = on +auto_resume = on +convert_spaces = on +download_path = "%h/xfer" +download_temporary_suffix = ".part" +upload_path = "~" +use_nick_in_filename = on