looooots of weechat

This commit is contained in:
Ben Harris 2018-08-08 20:20:19 -04:00
parent cc9935fcac
commit d2fb2856fc
16 changed files with 2364 additions and 27 deletions

View File

@ -47,7 +47,7 @@ git:
vim:
@printf "$(YELLOW)--- vim ------------------------------------------------\n$(RESET)"
stow -t "$$HOME" vim
[[ -f ~/.spf13-vim/bootstrap.sh ]] ||
[[ -f ~/.spf13-vim/bootstrap.sh ]] || \
git submodule update --init
~/.spf13-vim/bootstrap.sh

View File

@ -0,0 +1,31 @@
# This file is automatically generated by the fish.
# Do NOT edit it directly, your changes will be overwritten.
SET __fish_init_2_39_8:\x1d
SET __fish_init_2_3_0:\x1d
SET fish_color_autosuggestion:555\x1ebrblack
SET fish_color_cancel:\x2dr
SET fish_color_command:\x2d\x2dbold
SET fish_color_comment:red
SET fish_color_cwd:green
SET fish_color_cwd_root:red
SET fish_color_end:brmagenta
SET fish_color_error:brred
SET fish_color_escape:bryellow\x1e\x2d\x2dbold
SET fish_color_history_current:\x2d\x2dbold
SET fish_color_host:normal
SET fish_color_match:\x2d\x2dbackground\x3dbrblue
SET fish_color_normal:normal
SET fish_color_operator:bryellow
SET fish_color_param:cyan
SET fish_color_quote:yellow
SET fish_color_redirection:brblue
SET fish_color_search_match:bryellow\x1e\x2d\x2dbackground\x3dbrblack
SET fish_color_selection:white\x1e\x2d\x2dbold\x1e\x2d\x2dbackground\x3dbrblack
SET fish_color_user:brgreen
SET fish_color_valid_path:\x2d\x2dunderline
SET fish_greeting:Welcome\x20to\x20fish\x2c\x20the\x20friendly\x20interactive\x20shell
SET fish_key_bindings:fish_default_key_bindings
SET fish_pager_color_completion:\x1d
SET fish_pager_color_description:B3A06D\x1eyellow
SET fish_pager_color_prefix:white\x1e\x2d\x2dbold\x1e\x2d\x2dunderline
SET fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan

View File

@ -3,4 +3,5 @@ logs/
sec.conf
script/plugins.xml.gz
weechat_fifo
*.pem

View File

@ -0,0 +1,22 @@
#
# 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
replacements = ""
rules = ""
signal_delay = 5
signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed"
sort_on_config_change = on
[v3]
helpers = "{"core_first": "${if:${buffer.full_name}!=core.weechat}", "irc_raw_last": "${if:${buffer.full_name}==irc.irc_raw}", "irc_last": "${if:${buffer.plugin.name}==irc}", "hashless_name": "${info:autosort_replace,#,,${buffer.name}}", "irc_first": "${if:${buffer.plugin.name}!=irc}", "irc_raw_first": "${if:${buffer.full_name}!=irc.irc_raw}"}"
rules = "["${core_first}", "${irc_last}", "${buffer.plugin.name}", "${irc_raw_first}", "${if:${plugin}==irc?${server}}", "${if:${plugin}==irc?${info:autosort_order,${type},server,*,channel,private}}", "${if:${plugin}==irc?${hashless_name}}", "${buffer.full_name}"]"

View File

@ -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

View File

@ -129,7 +129,7 @@ local_hostname = ""
msg_kick = ""
msg_part = "WeeChat ${info:version}"
msg_quit = "WeeChat ${info:version}"
nicks = "ben,ben1,ben2,ben3,ben4"
nicks = "ben,benharri,ben2,ben3,ben4"
nicks_alternate = on
notify = ""
password = ""
@ -164,8 +164,8 @@ tilde.ssl_verify
tilde.password
tilde.capabilities
tilde.sasl_mechanism
tilde.sasl_username
tilde.sasl_password
tilde.sasl_username = "ben"
tilde.sasl_password = "${sec.data.pass}"
tilde.sasl_key
tilde.sasl_timeout
tilde.sasl_fail
@ -175,12 +175,12 @@ tilde.autoreconnect_delay
tilde.nicks
tilde.nicks_alternate
tilde.username
tilde.realname
tilde.realname = "Ben Harris"
tilde.local_hostname
tilde.usermode
tilde.command
tilde.command = "/msg nickserv identify ${sec.data.pass}; /oper root ${sec.data.tildenetoper}; /msg operserv login ${sec.data.pass}"
tilde.command_delay
tilde.autojoin = "#meta,#team,#sudoers,#yourtilde,#chaos"
tilde.autojoin = "#meta,#team,#sudoers,#yourtilde,#chaos,#town,#bots,#music,#share,#stevenuniverse,#suwp,#projects,#zccount,#politics,#dnd,#journal,#shitposting,#quotes,#gopher,#tildeverse,#venting,#idlerpg"
tilde.autorejoin
tilde.autorejoin_delay
tilde.connection_timeout
@ -197,7 +197,7 @@ hashbang.addresses = "irc.hashbang.sh/6697"
hashbang.proxy
hashbang.ipv6
hashbang.ssl = on
hashbang.ssl_cert
hashbang.ssl_cert = "%h/ssl/benharri.pem"
hashbang.ssl_priorities
hashbang.ssl_dhkey_size
hashbang.ssl_fingerprint
@ -213,13 +213,13 @@ hashbang.sasl_fail
hashbang.autoconnect
hashbang.autoreconnect
hashbang.autoreconnect_delay
hashbang.nicks
hashbang.nicks = "benharri"
hashbang.nicks_alternate
hashbang.username
hashbang.realname
hashbang.realname = "Ben Harris"
hashbang.local_hostname
hashbang.usermode
hashbang.command
hashbang.command = "/oper benharri x"
hashbang.command_delay
hashbang.autojoin
hashbang.autorejoin
@ -234,3 +234,331 @@ hashbang.msg_part
hashbang.msg_quit
hashbang.notify
hashbang.split_msg_max_length
town.addresses = "localhost/2345"
town.proxy
town.ipv6
town.ssl
town.ssl_cert
town.ssl_priorities
town.ssl_dhkey_size
town.ssl_fingerprint
town.ssl_verify
town.password
town.capabilities
town.sasl_mechanism
town.sasl_username
town.sasl_password
town.sasl_key
town.sasl_timeout
town.sasl_fail
town.autoconnect
town.autoreconnect
town.autoreconnect_delay
town.nicks = "benharri"
town.nicks_alternate
town.username = "benharri"
town.realname
town.local_hostname
town.usermode
town.command
town.command_delay
town.autojoin
town.autorejoin
town.autorejoin_delay
town.connection_timeout
town.anti_flood_prio_high
town.anti_flood_prio_low
town.away_check
town.away_check_max_nicks
town.msg_kick
town.msg_part
town.msg_quit
town.notify
town.split_msg_max_length
esper.addresses = "irc.esper.net/6697"
esper.proxy
esper.ipv6
esper.ssl = on
esper.ssl_cert
esper.ssl_priorities
esper.ssl_dhkey_size
esper.ssl_fingerprint
esper.ssl_verify
esper.password
esper.capabilities
esper.sasl_mechanism
esper.sasl_username
esper.sasl_password = "${sec.data.pass}"
esper.sasl_key
esper.sasl_timeout
esper.sasl_fail
esper.autoconnect
esper.autoreconnect
esper.autoreconnect_delay
esper.nicks = "benharri"
esper.nicks_alternate
esper.username = "benharri"
esper.realname = "benharri"
esper.local_hostname
esper.usermode
esper.command = "/msg nickserv identify ${sec.data.pass}"
esper.command_delay
esper.autojoin = "#lobby,#coders"
esper.autorejoin
esper.autorejoin_delay
esper.connection_timeout
esper.anti_flood_prio_high
esper.anti_flood_prio_low
esper.away_check
esper.away_check_max_nicks
esper.msg_kick
esper.msg_part
esper.msg_quit
esper.notify
esper.split_msg_max_length
sdf.addresses = "irc.sdf.org"
sdf.proxy
sdf.ipv6
sdf.ssl
sdf.ssl_cert
sdf.ssl_priorities
sdf.ssl_dhkey_size
sdf.ssl_fingerprint
sdf.ssl_verify
sdf.password
sdf.capabilities
sdf.sasl_mechanism
sdf.sasl_username
sdf.sasl_password
sdf.sasl_key
sdf.sasl_timeout
sdf.sasl_fail
sdf.autoconnect
sdf.autoreconnect
sdf.autoreconnect_delay
sdf.nicks = "benharri"
sdf.nicks_alternate
sdf.username = "benharri"
sdf.realname = "Ben Harris"
sdf.local_hostname
sdf.usermode
sdf.command
sdf.command_delay
sdf.autojoin = "#sdf,#gopher"
sdf.autorejoin
sdf.autorejoin_delay
sdf.connection_timeout
sdf.anti_flood_prio_high
sdf.anti_flood_prio_low
sdf.away_check
sdf.away_check_max_nicks
sdf.msg_kick
sdf.msg_part
sdf.msg_quit
sdf.notify
sdf.split_msg_max_length
darwin.addresses = "irc.darwin.network/6697"
darwin.proxy
darwin.ipv6
darwin.ssl = on
darwin.ssl_cert
darwin.ssl_priorities
darwin.ssl_dhkey_size
darwin.ssl_fingerprint
darwin.ssl_verify
darwin.password = "${sec.data.darwin}"
darwin.capabilities
darwin.sasl_mechanism
darwin.sasl_username
darwin.sasl_password
darwin.sasl_key
darwin.sasl_timeout
darwin.sasl_fail
darwin.autoconnect
darwin.autoreconnect
darwin.autoreconnect_delay
darwin.nicks
darwin.nicks_alternate
darwin.username
darwin.realname
darwin.local_hostname
darwin.usermode
darwin.command
darwin.command_delay
darwin.autojoin = "#darwin"
darwin.autorejoin
darwin.autorejoin_delay
darwin.connection_timeout
darwin.anti_flood_prio_high
darwin.anti_flood_prio_low
darwin.away_check
darwin.away_check_max_nicks
darwin.msg_kick
darwin.msg_part
darwin.msg_quit
darwin.notify
darwin.split_msg_max_length
gitter.addresses = "irc.gitter.im/6697"
gitter.proxy
gitter.ipv6
gitter.ssl = on
gitter.ssl_cert
gitter.ssl_priorities
gitter.ssl_dhkey_size
gitter.ssl_fingerprint
gitter.ssl_verify
gitter.password = "323cf7b2994d646e80b261c0d5bc546b766fe0c6"
gitter.capabilities
gitter.sasl_mechanism
gitter.sasl_username
gitter.sasl_password
gitter.sasl_key
gitter.sasl_timeout
gitter.sasl_fail
gitter.autoconnect
gitter.autoreconnect
gitter.autoreconnect_delay
gitter.nicks = "benharri"
gitter.nicks_alternate
gitter.username = "benharri"
gitter.realname = "benharri"
gitter.local_hostname
gitter.usermode
gitter.command
gitter.command_delay
gitter.autojoin
gitter.autorejoin
gitter.autorejoin_delay
gitter.connection_timeout
gitter.anti_flood_prio_high
gitter.anti_flood_prio_low
gitter.away_check
gitter.away_check_max_nicks
gitter.msg_kick
gitter.msg_part
gitter.msg_quit
gitter.notify
gitter.split_msg_max_length
oftc.addresses = "irc.oftc.net/6697"
oftc.proxy
oftc.ipv6
oftc.ssl = on
oftc.ssl_cert
oftc.ssl_priorities
oftc.ssl_dhkey_size
oftc.ssl_fingerprint
oftc.ssl_verify
oftc.password
oftc.capabilities
oftc.sasl_mechanism
oftc.sasl_username = "bhh"
oftc.sasl_password = "${sec.data.pass}"
oftc.sasl_key
oftc.sasl_timeout
oftc.sasl_fail
oftc.autoconnect
oftc.autoreconnect
oftc.autoreconnect_delay
oftc.nicks = "bhh"
oftc.nicks_alternate
oftc.username
oftc.realname = "bhh"
oftc.local_hostname
oftc.usermode
oftc.command = "/msg nickserv identify ${sec.data.pass}"
oftc.command_delay
oftc.autojoin = "#debian,#debian-next,#debian-offtopic,#fish,#moocows,#msys2,#oftc,#suckless"
oftc.autorejoin
oftc.autorejoin_delay
oftc.connection_timeout
oftc.anti_flood_prio_high
oftc.anti_flood_prio_low
oftc.away_check
oftc.away_check_max_nicks
oftc.msg_kick
oftc.msg_part
oftc.msg_quit
oftc.notify
oftc.split_msg_max_length
freenode.addresses = "irc.freenode.net/6697"
freenode.proxy
freenode.ipv6
freenode.ssl = on
freenode.ssl_cert
freenode.ssl_priorities
freenode.ssl_dhkey_size
freenode.ssl_fingerprint
freenode.ssl_verify
freenode.password
freenode.capabilities
freenode.sasl_mechanism
freenode.sasl_username
freenode.sasl_password
freenode.sasl_key
freenode.sasl_timeout
freenode.sasl_fail
freenode.autoconnect
freenode.autoreconnect
freenode.autoreconnect_delay
freenode.nicks = "benharri,bhh"
freenode.nicks_alternate
freenode.username = "benharri"
freenode.realname = "benharri"
freenode.local_hostname
freenode.usermode
freenode.command = "/msg nickserv identify ${sec.data.pass}"
freenode.command_delay
freenode.autojoin = "##oodnet,##tildeverse,#alacritty,#disroot,#fediverse,#irc.net,#litepub,#lobsters,#lobsters-boil,#lxcontainers,#systemd,#thelounge,#gitea,#ipfs,#mailpile,#mastodon,#pleroma,#pleroma-offtopic,#pixelfed,#pixelfed-offtopic,#oragono,##csharp,#manjaro,#vim,#weechat-android"
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
blackhat.addresses = "breaking.technology/6697"
blackhat.proxy
blackhat.ipv6
blackhat.ssl = on
blackhat.ssl_cert
blackhat.ssl_priorities
blackhat.ssl_dhkey_size
blackhat.ssl_fingerprint
blackhat.ssl_verify
blackhat.password
blackhat.capabilities
blackhat.sasl_mechanism
blackhat.sasl_username
blackhat.sasl_password
blackhat.sasl_key
blackhat.sasl_timeout
blackhat.sasl_fail
blackhat.autoconnect
blackhat.autoreconnect
blackhat.autoreconnect_delay
blackhat.nicks = "no_u"
blackhat.nicks_alternate
blackhat.username = "no_u"
blackhat.realname = "no_u"
blackhat.local_hostname
blackhat.usermode
blackhat.command
blackhat.command_delay
blackhat.autojoin = "#blackhat"
blackhat.autorejoin
blackhat.autorejoin_delay
blackhat.connection_timeout
blackhat.anti_flood_prio_high
blackhat.anti_flood_prio_low
blackhat.away_check
blackhat.away_check_max_nicks
blackhat.msg_kick
blackhat.msg_part
blackhat.msg_quit
blackhat.notify
blackhat.split_msg_max_length

View File

@ -32,6 +32,19 @@ python.apply_corrections.print_format = "[nick]: [corrected]"
python.apply_corrections.print_limit = "1"
python.autojoin.autosave = "off"
python.check_license = "off"
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"
python.grep.clear_buffer = "off"
python.grep.default_tail_head = "10"
python.grep.go_to_buffer = "on"
@ -57,6 +70,19 @@ python.apply_corrections.data_timeout = "Time before a message is expired."
python.apply_corrections.message_limit = "Number of messages to store per nick."
python.apply_corrections.print_format = "Format string for the printed corrections."
python.apply_corrections.print_limit = "Maximum number of lines to correct."
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")"
python.screen_away.away_suffix = "What to append to your nick when you're away."
python.screen_away.command_on_attach = "Commands to execute on attach, separated by semicolon"
python.screen_away.command_on_detach = "Commands to execute on detach, separated by semicolon"

View File

@ -43,6 +43,9 @@
# 2014-05-22, Nathaniel Wesley Filardo <PADEBR2M2JIQN02N9OO5JM0CTN8K689P@cmx.ietfng.org>
# version 0.2.5: Fix keyed channel support
#
# 2016-01-13, The fox in the shell <KellerFuchs@hashbang.sh>
# version 0.2.6: Support keeping chan list as secured data
#
# @TODO: add options to ignore certain buffers
# @TODO: maybe add an option to enable autosaving on part/join messages
@ -51,7 +54,7 @@ import re
SCRIPT_NAME = "autojoin"
SCRIPT_AUTHOR = "xt <xt@bash.no>"
SCRIPT_VERSION = "0.2.5"
SCRIPT_VERSION = "0.2.6"
SCRIPT_LICENSE = "GPL3"
SCRIPT_DESC = "Configure autojoin for all servers according to currently joined channels"
SCRIPT_COMMAND = "autojoin"
@ -89,9 +92,7 @@ def autosave_channels_on_quit(signal, callback, callback_data):
# print/execute commands
for server, channels in items.iteritems():
channels = channels.rstrip(',')
command = "/set irc.server.%s.autojoin '%s'" % (server, channels)
w.command('', command)
process_server(server, channels)
return w.WEECHAT_RC_OK
@ -111,10 +112,7 @@ def autosave_channels_on_activity(signal, callback, callback_data):
match = re.match(pattern, callback_data)
if match: # check if nick is my nick. In that case: save
channel = match.group(2)
channels = channels.rstrip(',')
command = "/set irc.server.%s.autojoin '%s'" % (server, channels)
w.command('', command)
process_server(server, channels)
else: # someone else: ignore
continue
@ -126,19 +124,38 @@ def autojoin_cb(data, buffer, args):
"""But I can't believe somebody would want that behaviour"""
items = find_channels()
if args == '--run':
run = True
else:
run = False
# print/execute commands
for server, channels in items.iteritems():
process_server(server, channels, run)
return w.WEECHAT_RC_OK
def process_server(server, channels, run=True):
option = "irc.server.%s.autojoin" % server
channels = channels.rstrip(',')
oldchans = w.config_string(w.config_get(option))
if not channels: # empty channel list
continue
command = '/set irc.server.%s.autojoin %s' % (server, channels)
if args == '--run':
return
# Note: re already caches the result of regexp compilation
sec = re.match('^\${sec\.data\.(.*)}$', oldchans)
if sec:
secvar = sec.group(1)
command = "/secure set %s %s" % (secvar, channels)
else:
command = "/set irc.server.%s.autojoin '%s'" % (server, channels)
if run:
w.command('', command)
else:
w.prnt('', command)
return w.WEECHAT_RC_OK
def find_channels():
"""Return list of servers and channels"""
#@TODO: make it return a dict with more options like "nicks_count etc."

View File

@ -0,0 +1 @@
../autosort.py

View File

@ -0,0 +1 @@
../colorize_nicks.py

View File

@ -0,0 +1 @@
../go.py

View File

@ -0,0 +1,923 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-2017 Maarten de Vries <maarten@de-vri.es>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
# 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.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 <maarten@de-vri.es>'
SCRIPT_VERSION = '3.3'
SCRIPT_LICENSE = 'GPL3'
SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.'
config = None
hooks = []
timer = None
# 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 dictonary 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 seting manually.'.format(key, type(value)))
return {}
return parsed
class Config:
''' The autosort configuration. '''
default_rules = json.dumps([
'${core_first}',
'${irc_last}',
'${buffer.plugin.name}',
'${irc_raw_first}',
'${if:${plugin}==irc?${server}}',
'${if:${plugin}==irc?${info:autosort_order,${type},server,*,channel,private}}',
'${if:${plugin}==irc?${hashless_name}}',
'${buffer.full_name}',
])
default_helpers = json.dumps({
'core_first': '${if:${buffer.full_name}!=core.weechat}',
'irc_first': '${if:${buffer.plugin.name}!=irc}',
'irc_last': '${if:${buffer.plugin.name}==irc}',
'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,#,,${buffer.name}}',
})
default_signal_delay = 5
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_on_config = True
self.__case_sensitive = None
self.__rules = None
self.__helpers = None
self.__signals = None
self.__signal_delay = None
self.__sort_on_config = None
if not self.config_file:
log('Failed to initialize configuration file "{0}".'.format(self.filename))
return
self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '')
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_on_config = weechat.config_new_option(
self.config_file, self.sorting_section,
'sort_on_config_change', 'boolean',
'Decides if the buffer list should be sorted when autosort configuration changes.',
'', 0, 0, 'on', 'on', 0,
'', '', '', '', '', ''
)
if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK:
log('Failed to load configuration file.')
if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK:
log('Failed to write configuration file.')
self.reload()
def reload(self):
''' Load configuration variables. '''
self.case_sensitive = weechat.config_boolean(self.__case_sensitive)
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_on_config = weechat.config_boolean(self.__sort_on_config)
def save_rules(self, run_callback = True):
''' Save the current rules to the configuration. '''
weechat.config_option_set(self.__rules, 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 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():
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)
def command_sort(buffer, command, args):
''' Sort the buffers and print a confirmation. '''
start = perf_counter()
do_sort()
elapsed = perf_counter() - start
log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
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 evalutaion 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 subccommand from a dictionary. '''
subcommand, tail = pad(args.split(' ', 1), 2, '')
subcommand = subcommand.strip()
if (subcommand == ''):
child = subcommands.get(' ')
else:
command = command + [subcommand]
child = subcommands.get(subcommand)
if isinstance(child, dict):
return call_command(buffer, command, tail, child)
elif callable(child):
return child(buffer, command, tail)
log('{0}: command not found'.format(' '.join(command)))
return weechat.WEECHAT_RC_ERROR
def on_signal(*args, **kwargs):
global timer
''' Called whenever the buffer list changes. '''
if timer is not None:
weechat.unhook(timer)
timer = None
weechat.hook_timer(config.signal_delay, 0, 1, "on_timeout", "")
return weechat.WEECHAT_RC_OK
def on_timeout(pointer, remaining_calls):
global timer
timer = None
do_sort()
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:
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, 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:
arg, args = parse_arg(args)
if arg is None: break
result.append(arg)
i += 1
return result, args
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}<expression>{reset}
Add a new rule at the end of the list.
{*white}/autosort {brown}rules insert {cyan}<index> <expression>{reset}
Insert a new rule at the given index in the list.
{*white}/autosort {brown}rules update {cyan}<index> <expression>{reset}
Update a rule in the list with a new expression.
{*white}/autosort {brown}rules delete {cyan}<index>
Delete a rule from the list.
{*white}/autosort {brown}rules move {cyan}<index_from> <index_to>{reset}
Move a rule from one position in the list to another.
{*white}/autosort {brown}rules swap {cyan}<index_a> <index_b>{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}<name> <expression>
Add or update a helper variable with the given name.
{*white}/autosort {brown}helpers delete {cyan}<name>
Delete a helper variable.
{*white}/autosort {brown}helpers rename {cyan}<old_name> <new_name>
Rename a helper variable.
{*white}/autosort {brown}helpers swap {cyan}<name_a> <name_b>
Swap the expressions of two helper variables in the list.
{*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.
{*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}# Replacing substrings{reset}
There is no default method for replacing text inside eval expressions. However,
autosort adds a `replace` info hook that can be used inside eval expressions:
{cyan}${{info:autosort_replace,from,to,text}}{reset}
For example, to strip all hashes from a buffer name, you could write:
{cyan}${{info:autosort_replace,#,,${{buffer.name}}}}{reset}
You can escape commas and backslashes inside the arguments by prefixing them with
a backslash.
{*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}
{*white}/set {cyan}buffers.look.indenting{reset} {brown}on{reset}
The first setting allows server buffers to be sorted independently, which is
needed to create a hierarchical tree view of the server and channel buffers.
The second one indents channel and private buffers in the buffer list of the
`{*default}buffers.pl{reset}` script.
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 occurences of `from` with `to` in the string `text`.'
info_replace_arguments = 'from,to,text'
info_order_description = (
'Get a zero padded index of a value in a list of possible values.'
'If the value is not found, the index for `*` is returned.'
'If there is no `*` in the list, the highest index + 1 is returned.'
)
info_order_arguments = 'value,first,second,third,...'
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_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()

View File

@ -0,0 +1,400 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2010 by xt <xt@bash.no>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This script colors nicks in IRC channels in the actual message
# not just in the prefix section.
#
#
# History:
# 2018-04-06: Joey Pabalinas <joeypabalinas@gmail.com>
# 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 <louis.beziaud@ens-rennes.fr>
# version 24: colorize utf8 nicks
# 2017-03-01, arza <arza@arza.us>
# version 23: don't colorize nicklist group names
# 2016-05-01, Simmo Saan <simmo.saan@gmail.com>
# 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 <xt@bash.no>"
SCRIPT_VERSION = "26"
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
full_name = modifier_data.split(';')[1]
channel = '.'.join(full_name.split('.')[1:])
buffer = w.buffer_search('', full_name)
# Check if buffer has colorized nicks
if buffer not in colored_nicks:
return line
if channel and channel in ignore_channels:
return line
min_length = w.config_integer(colorize_config_option['min_nick_length'])
reset = w.color('reset')
# Don't colorize if the ignored tag is present in message
tags_line = modifier_data.rsplit(';')
if len(tags_line) >= 3:
tags_line = tags_line[2].split(',')
for i in w.config_string(colorize_config_option['ignore_tags']).split(','):
if i in tags_line:
return line
for words in valid_nick_re.findall(line):
nick = words[1]
# Check that nick is not ignored and longer than minimum length
if len(nick) < min_length or nick in ignore_nicks:
continue
# 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', '')

View File

@ -0,0 +1,561 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2014 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2010 m4v <lambdae2@gmail.com>
# Copyright (C) 2011 stfn <stfnmd@googlemail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
# History:
#
# 2017-04-01, Sébastien Helleu <flashcode@flashtux.org>:
# version 2.5: add option "buffer_number"
# 2017-03-02, Sébastien Helleu <flashcode@flashtux.org>:
# version 2.4: fix syntax and indentation error
# 2017-02-25, Simmo Saan <simmo.saan@gmail.com>
# version 2.3: fix fuzzy search breaking buffer number search display
# 2016-01-28, ylambda <ylambda@koalabeast.com>
# version 2.2: add option "fuzzy_search"
# 2015-11-12, nils_2 <weechatter@arcor.de>
# 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 <flashcode@flashtux.org>:
# 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 <anti.teamidiot.de>
# version 1.9: add auto_jump option to automatically go to buffer when it
# is uniquely selected
# 2012-09-17, Sébastien Helleu <flashcode@flashtux.org>:
# version 1.8: fix jump to non-active merged buffers (jump with buffer name
# instead of number)
# 2012-01-03 nils_2 <weechatter@arcor.de>
# version 1.7: add option "use_core_instead_weechat"
# 2012-01-03, Sébastien Helleu <flashcode@flashtux.org>:
# version 1.6: make script compatible with Python 3.x
# 2011-08-24, stfn <stfnmd@googlemail.com>:
# version 1.5: /go with name argument jumps directly to buffer
# Remember cursor position in buffer input
# 2011-05-31, Elián Hanisch <lambdae2@gmail.com>:
# version 1.4: Sort list of buffers by activity.
# 2011-04-25, Sébastien Helleu <flashcode@flashtux.org>:
# version 1.3: add info "go_running" (used by script input_lock.rb)
# 2010-11-01, Sébastien Helleu <flashcode@flashtux.org>:
# 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 <lambdae2@gmail.com>:
# version 1.1: use a space to match the end of a string
# 2009-11-16, Sébastien Helleu <flashcode@flashtux.org>:
# version 1.0: add new option to display short names
# 2009-06-15, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.9: fix typo in /help go with command /key
# 2009-05-16, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.8: search buffer by number, fix bug when window is split
# 2009-05-03, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.7: eat tab key (do not complete input, just move buffer
# pointer)
# 2009-05-02, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.6: sync with last API changes
# 2009-03-22, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.5: update modifier signal name for input text display,
# fix arguments for function string_remove_color
# 2009-02-18, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.4: do not hook command and init options if register failed
# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.3: case insensitive search for buffers names
# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
# version 0.2: add help about Tab key
# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
# 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 <flashcode@flashtux.org>'
SCRIPT_VERSION = '2.5'
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 == '/input search_text' or command.find('/input jump') == 0:
# 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()

View File

@ -32,7 +32,7 @@ clients_purge_delay = 0
compression_level = 6
ipv6 = on
max_clients = 5
password = ""
password = "${sec.data.pass}"
ssl_cert_key = "%h/ssl/relay.pem"
ssl_priorities = "NORMAL:-VERS-SSL3.0"
websocket_allowed_origins = ""
@ -46,3 +46,4 @@ backlog_tags = "irc_privmsg"
backlog_time_format = "[%H:%M] "
[port]
weechat = 9090

View File

@ -28,7 +28,7 @@ bar_more_up = "--"
bare_display_exit_on_input = on
bare_display_time_format = "%H:%M"
buffer_auto_renumber = on
buffer_notify_default = all
buffer_notify_default = highlight
buffer_position = end
buffer_search_case_sensitive = off
buffer_search_force_default = off
@ -85,7 +85,7 @@ jump_previous_buffer_when_closing = on
jump_smart_back_to_buffer = on
key_bind_safe = on
key_grab_delay = 800
mouse = on
mouse = off
mouse_timer_delay = 100
nick_color_force = ""
nick_color_hash = djb2
@ -354,6 +354,10 @@ default.window = "3;1;0;0;core;weechat"
default.current = on
[notify]
irc.darwin = message
irc.hashbang = message
irc.sdf = message
irc.tilde = message
[filter]