954e95288732

Merge.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Thu, 14 May 2015 11:06:43 -0400
parents d077c6911044 (diff) 603bb1ae9da2 (current diff)
children d0382bc711a0 c39a8470f5fc
branches/tags (none)
files vim/vimrc

Changes

--- a/agignore	Thu Dec 18 21:59:14 2014 -0500
+++ b/agignore	Thu May 14 11:06:43 2015 -0400
@@ -4,3 +4,4 @@
 target
 *.public.key
 *.private.key
+*.pgp
--- a/hgrc	Thu Dec 18 21:59:14 2014 -0500
+++ b/hgrc	Thu May 14 11:06:43 2015 -0400
@@ -28,7 +28,8 @@
 delay = 1.0
 
 [web]
-cacerts = /etc/hg-dummy-cert.pem
+# cacerts = /etc/hg-dummy-cert.pem
+cacerts = 
 
 [schemes]
 webf = ssh://sjl@sjl.webfactional.com/repos/
--- a/mutt/muttrc	Thu Dec 18 21:59:14 2014 -0500
+++ b/mutt/muttrc	Thu May 14 11:06:43 2015 -0400
@@ -43,6 +43,29 @@
 set thorough_search      # strip headers and eval mimes before searching
 
 # }}}
+# PGP {{{
+
+set pgp_decode_command="gpg %?p?--passphrase-fd 0? --no-verbose --batch --output - %f"
+set pgp_verify_command="gpg --no-verbose --batch --output - --verify %s %f"
+set pgp_decrypt_command="gpg --passphrase-fd 0 --no-verbose --batch --output - %f"
+set pgp_sign_command="gpg --no-verbose --batch --output - --passphrase-fd 0 --armor --detach-sign --textmode %?a?-u %a? %f"
+set pgp_clearsign_command="gpg --no-verbose --batch --output - --passphrase-fd 0 --armor --textmode --clearsign %?a?-u %a? %f"
+set pgp_verify_key_command="gpg --no-verbose --batch --fingerprint --check-sigs %r"
+
+set pgp_import_command="gpg --no-verbose --import -v %f"
+set pgp_export_command="gpg --no-verbose --export --armor %r"
+set pgp_list_pubring_command="gpg --no-verbose --batch --with-colons --list-keys %r" 
+set pgp_list_secring_command="gpg --no-verbose --batch --with-colons --list-secret-keys %r"
+
+set crypt_verify_sig=yes
+
+message-hook '!(~g|~G) ~b"^-----BEGIN\ PGP\ (SIGNED\ )?MESSAGE"' "exec check-traditional-pgp"
+
+# set these in .mutt-local with the appropriate encrypt-to key id
+# set pgp_encrypt_only_command="pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust --encrypt-to 0x7438228A -- -r %r -- %f"
+# set pgp_encrypt_sign_command="pgpewrap gpg --passphrase-fd 0 --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust --encrypt-to 0x7438228A -- -r %r -- %f"
+
+# }}}
 # Sidebar Patch {{{
 
 # set sidebar_delim   = '  │'
@@ -210,6 +233,7 @@
 # Compose {{{
 
 bind compose p postpone-message
+bind compose P pgp-menu
 
 # }}}
 # Attachment {{{
--- a/vim/vimrc	Thu Dec 18 21:59:14 2014 -0500
+++ b/vim/vimrc	Thu May 14 11:06:43 2015 -0400
@@ -2446,7 +2446,13 @@
     return MS2UTC(expand("<cword>"))
 endfunction
 
-nnoremap <leader>U :echo MS2UTCWord()<cr>
+function! ReplaceMS2UTCWord()
+    let res = MS2UTC(expand("<cword>"))
+    execute 'normal! ciw"' . res . '"'
+endfunction
+
+" nnoremap <leader>U :echo MS2UTCWord()<cr>
+nnoremap <leader>U :call ReplaceMS2UTCWord()<cr>
 
 " }}}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat/.agignore	Thu May 14 11:06:43 2015 -0400
@@ -0,0 +1,2 @@
+logs
+urls.log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat/autosort.conf	Thu May 14 11:06:43 2015 -0400
@@ -0,0 +1,11 @@
+#
+# autosort.conf -- weechat v0.4.3
+#
+
+[sorting]
+case_sensitive = off
+group_irc = on
+replacements = "[]"
+rules = "[["core", 0], ["irc", 2], ["*", 1], ["irc.irc_raw", 0], ["irc.server", 1]]"
+signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed"
+sort_on_config_change = on
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat/python/autoload/autosort.py	Thu May 14 11:06:43 2015 -0400
@@ -0,0 +1,860 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013-2014 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.
+#
+# http://github.com/de-vri.es/weechat-autosort
+#
+
+#
+# Changelog:
+# 2.5:
+#   * Fix handling unicode buffer names.
+#   * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on.
+# 2.4:
+#   * Make script python3 compatible.
+# 2.3:
+#   * Fix sorting items without score last (regressed in 2.2).
+# 2.2:
+#   * Add configuration option for signals that trigger a sort.
+#   * Add command to manually trigger a sort (/autosort sort).
+#   * Add replacement patterns to apply before sorting.
+# 2.1:
+#   * Fix some minor style issues.
+# 2.0:
+#   * Allow for custom sort rules.
+#
+
+
+import weechat
+import re
+import json
+
+SCRIPT_NAME     = 'autosort'
+SCRIPT_AUTHOR   = 'Maarten de Vries <maarten@de-vri.es>'
+SCRIPT_VERSION  = '2.5'
+SCRIPT_LICENSE  = 'GPL3'
+SCRIPT_DESC     = 'Automatically or manually keep your buffers sorted and grouped by server.'
+
+
+config = None
+hooks  = []
+
+class HumanReadableError(Exception):
+	pass
+
+
+def parse_int(arg, arg_name = 'argument'):
+	''' Parse an integer and provide a more human readable error. '''
+	arg = arg.strip()
+	try:
+		return int(arg)
+	except ValueError:
+		raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg))
+
+
+class Pattern:
+	''' A simple glob-like pattern for matching buffer names. '''
+
+	def __init__(self, pattern):
+		''' Construct a pattern from a string. '''
+		escaped    = False
+		char_class = 0
+		chars      = ''
+		regex      = ''
+		for c in pattern:
+			if escaped and char_class:
+				escaped = False
+				chars += re.escape(c)
+			elif escaped:
+				escaped = False
+				regex += re.escape(c)
+			elif c == '\\':
+				escaped = True
+			elif c == '*' and not char_class:
+				regex += '[^.]*'
+			elif c == '?' and not char_class:
+				regex += '[^.]'
+			elif c == '[' and not char_class:
+				char_class = 1
+				chars    = ''
+			elif c == '^' and char_class and not chars:
+				chars += '^'
+			elif c == ']' and char_class and chars not in ('', '^'):
+				char_class = False
+				regex += '[' + chars + ']'
+			elif c == '-' and char_class:
+				chars += '-'
+			elif char_class:
+				chars += re.escape(c)
+			else:
+				regex += re.escape(c)
+
+		if char_class:
+			raise ValueError("unmatched opening '['")
+		if escaped:
+			raise ValueError("unexpected trailing '\\'")
+
+		self.regex   = re.compile('^' + regex + '$')
+		self.pattern = pattern
+
+	def match(self, input):
+		''' Match the pattern against a string. '''
+		return self.regex.match(input)
+
+
+class FriendlyList(object):
+	''' A list with human readable errors. '''
+
+	def __init__(self):
+		self.__data = []
+
+	def raw(self):
+		return self.__data
+
+	def append(self, value):
+		''' Add a rule to the list. '''
+		self.__data.append(value)
+
+	def insert(self, index, value):
+		''' Add a rule to the list. '''
+		if not 0 <= index <= len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}], got {1}.'.format(len(self), index))
+		self.__data.insert(index, value)
+
+	def pop(self, index):
+		''' Remove a rule from the list and return it. '''
+		if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index))
+		return self.__data.pop(index)
+
+	def move(self, index_a, index_b):
+		''' Move a rule to a new position in the list. '''
+		self.insert(index_b, self.pop(index_a))
+
+	def swap(self, index_a, index_b):
+		''' Swap two elements in the list. '''
+		self[index_a], self[index_b] = self[index_b], self[index_a]
+
+	def __len__(self):
+		return len(self.__data)
+
+	def __getitem__(self, index):
+		if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index))
+		return self.__data[index]
+
+	def __setitem__(self, index, value):
+		if not 0 <= index < len(self): raise HumanReadableError('Index out of range: expected an integer in the range [0, {0}), got {1}.'.format(len(self), index))
+		self.__data[index] = value
+
+	def __iter__(self):
+		return iter(self.__data)
+
+
+class RuleList(FriendlyList):
+	''' A list of rules to test buffer names against. '''
+	rule_regex = re.compile(r'^(.*)=\s*([+-]?[^=]*)$')
+
+	def __init__(self, rules):
+		''' Construct a RuleList from a list of rules. '''
+		super(RuleList, self).__init__()
+		for rule in rules: self.append(rule)
+
+	def get_score(self, name, rules):
+		''' Get the sort score of a partial name according to a rule list. '''
+		for rule in self:
+			if rule[0].match(name): return rule[1]
+		return 999999999
+
+	def encode(self):
+		''' Encode the rules for storage. '''
+		return json.dumps(list(map(lambda x: (x[0].pattern, x[1]), self)))
+
+	@staticmethod
+	def decode(blob):
+		''' Parse rules from a string blob. '''
+		result = []
+
+		try:
+			decoded = json.loads(blob)
+		except ValueError:
+			log('Invalid rules: expected JSON encoded list of pairs, got "{0}".'.format(blob))
+			return [], 0
+
+		for rule in decoded:
+			# Rules must be a pattern,score pair.
+			if len(rule) != 2:
+				log('Invalid rule: expected (pattern, score), got "{0}". Rule ignored.'.format(rule))
+				continue
+
+			# Rules must have a valid pattern.
+			try:
+				pattern = Pattern(rule[0])
+			except ValueError as e:
+				log('Invalid pattern: {0} in "{1}". Rule ignored.'.format(e, rule[0]))
+				continue
+
+			# Rules must have a valid score.
+			try:
+				score = int(rule[1])
+			except ValueError as e:
+				log('Invalid score: expected an integer, got "{0}". Rule ignored.'.format(score))
+				continue
+
+			result.append((pattern, score))
+
+		return RuleList(result)
+
+	@staticmethod
+	def parse_rule(arg):
+		''' Parse a rule argument. '''
+		arg = arg.strip()
+		match = RuleList.rule_regex.match(arg)
+		if not match:
+			raise HumanReadableError('Invalid rule: expected "<pattern> = <score>", got "{0}".'.format(arg))
+
+		pattern = match.group(1).strip()
+		try:
+			pattern = Pattern(pattern)
+		except ValueError as e:
+			raise HumanReadableError('Invalid pattern: {0} in "{1}".'.format(e, pattern))
+
+		score   = parse_int(match.group(2), 'score')
+		return (pattern, score)
+
+
+def decode_replacements(blob):
+	''' Decode a replacement list encoded as JSON. '''
+	result = FriendlyList()
+	try:
+		decoded = json.loads(blob)
+	except ValueError:
+		log('Invalid replacement list: expected JSON encoded list of pairs, got "{0}".'.format(blob))
+		return [], 0
+
+	for replacement in decoded:
+		# Replacements must be a (string, string) pair.
+		if len(replacement) != 2:
+			log('Invalid replacement pattern: expected (pattern, replacement), got "{0}". Replacement ignored.'.format(rule))
+			continue
+		result.append(replacement)
+
+	return result
+
+
+def encode_replacements(replacements):
+	''' Encode a list of replacement patterns as JSON. '''
+	return json.dumps(replacements.raw())
+
+
+class Config:
+	''' The autosort configuration. '''
+
+	default_rules = json.dumps([
+		('core', 0),
+		('irc',  2),
+		('*',    1),
+
+		('irc.irc_raw', 0),
+		('irc.server',  1),
+	])
+
+	default_replacements = '[]'
+	default_signals      = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed'
+
+	def __init__(self, filename):
+		''' Initialize the configuration. '''
+
+		self.filename         = filename
+		self.config_file      = weechat.config_new(self.filename, '', '')
+		self.sorting_section  = None
+
+		self.case_sensitive   = False
+		self.group_irc        = True
+		self.rules            = []
+		self.replacements     = []
+		self.signals          = []
+		self.sort_on_config   = True
+
+		self.__case_sensitive = None
+		self.__group_irc      = None
+		self.__rules          = None
+		self.__replacements   = None
+		self.__signals        = None
+		self.__sort_on_config = None
+
+		if not self.config_file:
+			log('Failed to initialize configuration file "{0}".'.format(self.filename))
+			return
+
+		self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '')
+
+		if not self.sorting_section:
+			log('Failed to initialize section "sorting" of configuration file.')
+			weechat.config_free(self.config_file)
+			return
+
+		self.__case_sensitive = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'case_sensitive', 'boolean',
+			'If this option is on, sorting is case sensitive.',
+			'', 0, 0, 'off', 'off', 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__group_irc = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'group_irc', 'boolean',
+			'If this option is on, the script pretends that IRC channel/private buffers are renamed to "irc.server.{network}.{channel}" rather than "irc.{network}.{channel}".' +
+			'This ensures that these buffers are grouped with their respective server buffer.',
+			'', 0, 0, 'on', 'on', 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__rules = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'rules', 'string',
+			'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.',
+			'', 0, 0, Config.default_rules, Config.default_rules, 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__replacements = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'replacements', 'string',
+			'An ordered list of replacement patterns to use on buffer name components, encoded as JSON. See /help autosort for commands to manipulate these replacements.',
+			'', 0, 0, Config.default_replacements, Config.default_replacements, 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__signals = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'signals', 'string',
+			'The signals that will cause autosort to resort your buffer list. Seperate signals with spaces.',
+			'', 0, 0, Config.default_signals, Config.default_signals, 0,
+			'', '', '', '', '', ''
+		)
+
+		self.__sort_on_config = weechat.config_new_option(
+			self.config_file, self.sorting_section,
+			'sort_on_config_change', 'boolean',
+			'Decides if the buffer list should be sorted when autosort configuration changes.',
+			'', 0, 0, 'on', 'on', 0,
+			'', '', '', '', '', ''
+		)
+
+		if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK:
+			log('Failed to load configuration file.')
+
+		if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK:
+			log('Failed to write configuration file.')
+
+		self.reload()
+
+	def reload(self):
+		''' Load configuration variables. '''
+
+		self.case_sensitive = weechat.config_boolean(self.__case_sensitive)
+		self.group_irc      = weechat.config_boolean(self.__group_irc)
+
+		rules_blob          = weechat.config_string(self.__rules)
+		replacements_blob   = weechat.config_string(self.__replacements)
+		signals_blob        = weechat.config_string(self.__signals)
+
+		self.rules          = RuleList.decode(rules_blob)
+		self.replacements   = decode_replacements(replacements_blob)
+		self.signals        = signals_blob.split()
+		self.sort_on_config = weechat.config_boolean(self.__sort_on_config)
+
+	def save_rules(self, run_callback = True):
+		''' Save the current rules to the configuration. '''
+		weechat.config_option_set(self.__rules, RuleList.encode(self.rules), run_callback)
+
+	def save_replacements(self, run_callback = True):
+		''' Save the current replacement patterns to the configuration. '''
+		weechat.config_option_set(self.__replacements, encode_replacements(self.replacements), run_callback)
+
+
+def pad(sequence, length, padding = None):
+	''' Pad a list until is has a certain length. '''
+	return sequence + [padding] * max(0, (length - len(sequence)))
+
+
+def log(message, buffer = 'NULL'):
+	weechat.prnt(buffer, 'autosort: {0}'.format(message))
+
+
+def get_buffers():
+	''' Get a list of all the buffers in weechat. '''
+	buffers = []
+
+	buffer_list = weechat.infolist_get('buffer', '', '')
+
+	while weechat.infolist_next(buffer_list):
+		name   = weechat.infolist_string (buffer_list, 'full_name')
+		number = weechat.infolist_integer(buffer_list, 'number')
+
+		# Buffer is merged with one we already have in the list, skip it.
+		if number <= len(buffers):
+			continue
+		buffers.append(name)
+
+	weechat.infolist_free(buffer_list)
+	return buffers
+
+
+def preprocess(buffer, config):
+	'''
+	Preprocess a buffers names.
+	'''
+	if not config.case_sensitive:
+		buffer = buffer.lower()
+
+	for replacement in config.replacements:
+		buffer = buffer.replace(replacement[0], replacement[1])
+
+	buffer = buffer.split('.')
+	if config.group_irc and len(buffer) >= 2 and buffer[0] == 'irc' and buffer[1] not in ('server', 'irc_raw'):
+		buffer.insert(1, 'server')
+
+	return buffer
+
+
+def buffer_sort_key(rules):
+	''' Create a sort key function for a buffer list from a rule list. '''
+	def key(buffer):
+		result  = []
+		name    = ''
+		for word in preprocess(buffer.decode('utf-8'), config):
+			name += ('.' if name else '') + word
+			result.append((rules.get_score(name, rules), word))
+		return result
+
+	return key
+
+
+def apply_buffer_order(buffers):
+	''' Sort the buffers in weechat according to the order in the input list.  '''
+	for i, buffer in enumerate(buffers):
+		weechat.command('', '/buffer swap {0} {1}'.format(buffer, 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 command_sort(buffer, command, args):
+	''' Sort the buffers and print a confirmation. '''
+	on_buffers_changed()
+	log("Finished sorting buffers.", buffer)
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_list(buffer, command, args):
+	''' Show the list of sorting rules. '''
+	output = 'Sorting rules:\n'
+	for i, rule in enumerate(config.rules):
+		output += '    {0}: {1} = {2}\n'.format(i, rule[0].pattern, rule[1])
+	if not len(config.rules):
+		output += '    No sorting rules configured.\n'
+	log(output, buffer)
+
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_add(buffer, command, args):
+	''' Add a rule to the rule list. '''
+	rule = RuleList.parse_rule(args)
+
+	config.rules.append(rule)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_insert(buffer, command, args):
+	''' Insert a rule at the desired position in the rule list. '''
+	index, rule = split_args(args, 2)
+	index = parse_int(index, 'index')
+	rule  = RuleList.parse_rule(rule)
+
+	config.rules.insert(index, rule)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_update(buffer, command, args):
+	''' Update a rule in the rule list. '''
+	index, rule = split_args(args, 2)
+	index = parse_int(index, 'index')
+	rule  = RuleList.parse_rule(rule)
+
+	config.rules[index] = rule
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_delete(buffer, command, args):
+	''' Delete a rule from the rule list. '''
+	index = args.strip()
+	index = parse_int(index, 'index')
+
+	config.rules.pop(index)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_move(buffer, command, args):
+	''' Move a rule to a new position. '''
+	index_a, index_b = split_args(args, 2)
+	index_a = parse_int(index_a, 'index')
+	index_b = parse_int(index_b, 'index')
+
+	config.rules.move(index_a, index_b)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_rule_swap(buffer, command, args):
+	''' Swap two rules. '''
+	index_a, index_b = split_args(args, 2)
+	index_a = parse_int(index_a, 'index')
+	index_b = parse_int(index_b, 'index')
+
+	config.rules.swap(index_a, index_b)
+	config.save_rules()
+	command_rule_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_replacement_list(buffer, command, args):
+	''' Show the list of sorting rules. '''
+	output = 'Replacement patterns:\n'
+	for i, pattern in enumerate(config.replacements):
+		output += '    {0}: {1} -> {2}\n'.format(i, pattern[0], pattern[1])
+	if not len(config.replacements):
+		output += '    No replacement patterns configured.'
+	log(output, buffer)
+
+	return weechat.WEECHAT_RC_OK
+
+
+def command_replacement_add(buffer, command, args):
+	''' Add a rule to the rule list. '''
+	pattern, replacement = split_args(args, 1, 1)
+
+	config.replacements.append((pattern, replacement))
+	config.save_replacements()
+	command_replacement_list(buffer, command, '')
+
+	return weechat.WEECHAT_RC_OK
+
+
+def command_replacement_insert(buffer, command, args):
+	''' Insert a rule at the desired position in the rule list. '''
+	index, pattern, replacement = split_args(args, 2, 1)
+	index = parse_int(index, 'index')
+
+	config.replacements.insert(index, (pattern, replacement))
+	config.save_replacements()
+	command_replacement_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_replacement_update(buffer, command, args):
+	''' Update a rule in the rule list. '''
+	index, pattern, replacement = split_args(args, 2, 1)
+	index = parse_int(index, 'index')
+
+	config.replacements[index] = (pattern, replacement)
+	config.save_replacements()
+	command_replacement_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_replacement_delete(buffer, command, args):
+	''' Delete a rule from the rule list. '''
+	index = args.strip()
+	index = parse_int(index, 'index')
+
+	config.replacements.pop(index)
+	config.save_replacements()
+	command_replacement_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_replacement_move(buffer, command, args):
+	''' Move a rule to a new position. '''
+	index_a, index_b = split_args(args, 2)
+	index_a = parse_int(index_a, 'index')
+	index_b = parse_int(index_b, 'index')
+
+	config.replacements.move(index_a, index_b)
+	config.save_replacements()
+	command_replacement_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+def command_replacement_swap(buffer, command, args):
+	''' Swap two rules. '''
+	index_a, index_b = split_args(args, 2)
+	index_a = parse_int(index_a, 'index')
+	index_b = parse_int(index_b, 'index')
+
+	config.replacements.swap(index_a, index_b)
+	config.save_replacements()
+	command_replacement_list(buffer, command, '')
+	return weechat.WEECHAT_RC_OK
+
+
+
+
+def call_command(buffer, command, args, subcommands):
+	''' Call a subccommand from a dictionary. '''
+	subcommand, tail = pad(args.split(' ', 1), 2, '')
+	subcommand = subcommand.strip()
+	if (subcommand == ''):
+		child   = subcommands.get(' ')
+	else:
+		command = command + [subcommand]
+		child   = subcommands.get(subcommand)
+
+	if isinstance(child, dict):
+		return call_command(buffer, command, tail, child)
+	elif callable(child):
+		return child(buffer, command, tail)
+
+	log('{0}: command not found'.format(' '.join(command)))
+	return weechat.WEECHAT_RC_ERROR
+
+
+def on_buffers_changed(*args, **kwargs):
+	''' Called whenever the buffer list changes. '''
+	buffers = get_buffers()
+	buffers.sort(key=buffer_sort_key(config.rules))
+	apply_buffer_order(buffers)
+	return weechat.WEECHAT_RC_OK
+
+
+def on_config_changed(*args, **kwargs):
+	''' Called whenever the configuration changes. '''
+	config.reload()
+
+	# Unhook all signals and hook the new ones.
+	for hook in hooks:
+		weechat.unhook(hook)
+	for signal in config.signals:
+		hooks.append(weechat.hook_signal(signal, 'on_buffers_changed', ''))
+
+	if config.sort_on_config:
+		on_buffers_changed()
+
+	return weechat.WEECHAT_RC_OK
+
+
+def on_autosort_command(data, buffer, args):
+	''' Called when the autosort command is invoked. '''
+	try:
+		return call_command(buffer, ['/autosort'], args, {
+			' ':      command_sort,
+			'sort':   command_sort,
+
+			'rules': {
+				' ':         command_rule_list,
+				'list':      command_rule_list,
+				'add':       command_rule_add,
+				'insert':    command_rule_insert,
+				'update':    command_rule_update,
+				'delete':    command_rule_delete,
+				'move':      command_rule_move,
+				'swap':      command_rule_swap,
+			},
+			'replacements': {
+				' ':      command_replacement_list,
+				'list':   command_replacement_list,
+				'add':    command_replacement_add,
+				'insert': command_replacement_insert,
+				'update': command_replacement_update,
+				'delete': command_replacement_delete,
+				'move':   command_replacement_move,
+				'swap':   command_replacement_swap,
+			},
+			'sort':   on_buffers_changed,
+		})
+	except HumanReadableError as e:
+		log(e, buffer)
+		return weechat.WEECHAT_RC_ERROR
+
+
+command_description = r'''
+NOTE: For the best effect, you may want to consider setting the option irc.look.server_buffer to independent and buffers.look.indenting to on.
+
+# Commands
+
+## Miscellaneous
+/autosort sort
+Manually trigger the buffer sorting.
+
+
+## Sorting rules
+
+/autosort rules list
+Print the list of sort rules.
+
+/autosort rules add <pattern> = <score>
+Add a new rule at the end of the list.
+
+/autosort rules insert <index> <pattern> = <score>
+Insert a new rule at the given index in the list.
+
+/autosort rules update <index> <pattern> = <score>
+Update a rule in the list with a new pattern and score.
+
+/autosort rules delete <index>
+Delete a rule from the list.
+
+/autosort rules move <index_from> <index_to>
+Move a rule from one position in the list to another.
+
+/autosort rules swap <index_a> <index_b>
+Swap two rules in the list
+
+
+## Replacement patterns
+
+/autosort replacements list
+Print the list of replacement patterns.
+
+/autosort replacements add <pattern> <replacement>
+Add a new replacement pattern at the end of the list.
+
+/autosort replacements insert <index> <pattern> <replacement>
+Insert a new replacement pattern at the given index in the list.
+
+/autosort replacements update <index> <pattern> <replacement>
+Update a replacement pattern in the list.
+
+/autosort replacements delete <index>
+Delete a replacement pattern from the list.
+
+/autosort replacements move <index_from> <index_to>
+Move a replacement pattern from one position in the list to another.
+
+/autosort replacements swap <index_a> <index_b>
+Swap two replacement pattern in the list
+
+
+# Introduction
+Autosort is a weechat script to automatically keep your buffers sorted.
+The sort order can be customized by defining your own sort rules,
+but the default should be sane enough for most people.
+It can also group IRC channel/private buffers under their server buffer if you like.
+
+Autosort first turns buffer names into a list of their components by splitting on them on the period character.
+For example, the buffer name "irc.server.freenode" is turned into ['irc', 'server', 'freenode'].
+The list of buffers is then lexicographically sorted.
+
+To facilitate custom sort orders, it is possible to assign a score to each component individually before the sorting is done.
+Any name component that did not get a score assigned will be sorted after those that did receive a score.
+Components are always sorted on their score first and on their name second.
+Lower scores are sorted first.
+
+## Automatic or manual sorting
+By default, autosort will automatically sort your buffer list whenever a buffer is opened, merged, unmerged or renamed.
+This should keep your buffers sorted in almost all situations.
+However, you may wish to change the list of signals that cause your buffer list to be sorted.
+Simply edit the "autosort.sorting.signals" option to add or remove any signal you like.
+If you remove all signals you can still sort your buffers manually with the "/autosort sort" command.
+To prevent all automatic sorting, "autosort.sorting.sort_on_config_change" should also be set to off.
+
+## Grouping IRC buffers
+In weechat, IRC channel/private buffers are named "irc.<network>.<#channel>",
+and IRC server buffers are named "irc.server.<network>".
+This does not work very well with lexicographical sorting if you want all buffers for one network grouped together.
+That is why autosort comes with the "autosort.sorting.group_irc" option,
+which secretly pretends IRC channel/private buffers are called "irc.server.<network>.<#channel>".
+The buffers are not actually renamed, autosort simply pretends they are for sorting purposes.
+
+## Replacement patterns
+Sometimes you may want to ignore some characters for sorting purposes.
+On Freenode for example, you may wish to ignore the difference between channels starting with a double or a single hash sign.
+To do so, simply add a replacement pattern that replaces ## with # with the following command:
+/autosort replacements add ## #
+
+Replacement patterns do not support wildcards or special characters at the moment.
+
+## Sort rules
+You can assign scores to name components by defining sort rules.
+The first rule that matches a component decides the score.
+Further rules are not examined.
+Sort rules use the following syntax:
+<glob-pattern> = <score>
+
+You can use the "/autosort rules" command to show and manipulate the list of sort rules.
+
+
+Allowed special characters in the glob patterns are:
+
+Pattern | Meaning
+--------|--------
+*       | Matches a sequence of any characters except for periods.
+?       | Matches a single character, but not a period.
+[a-z]   | Matches a single character in the given regex-like character class.
+[^ab]   | A negated regex-like character class.
+\*      | A backslash escapes the next characters and removes its special meaning.
+\\      | A literal backslash.
+
+
+## Example
+As an example, consider the following rule list:
+0: core            = 0
+1: irc             = 2
+2: *               = 1
+
+3: irc.server.*.#* = 1
+4: irc.server.*.*  = 0
+
+Rule 0 ensures the core buffer is always sorted first.
+Rule 1 sorts IRC buffers last and rule 2 puts all remaining buffers in between the two.
+
+Rule 3 and 4 would make no sense with the group_irc option off.
+With the option on though, these rules will sort private buffers before regular channel buffers.
+Rule 3 matches channel buffers and assigns them a higher score,
+while rule 4 matches the buffers that remain and assigns them a lower score.
+The same effect could also be achieved with a single rule:
+irc.server.*.[^#]* = 0
+'''
+
+command_completion = 'sort||rules list|add|insert|update|delete|move|swap||replacements list|add|insert|update|delete|move|swap'
+
+
+if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
+	config = Config('autosort')
+
+	weechat.hook_config('autosort.*',      'on_config_changed',  '')
+	weechat.hook_command('autosort', command_description, '', '', command_completion, 'on_autosort_command', 'NULL')
+	on_config_changed()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat/python/listbuffer.py	Thu May 14 11:06:43 2015 -0400
@@ -0,0 +1,467 @@
+# -*- coding: utf-8 -*-
+#
+# ListBuffer, version 0.8.1 for WeeChat version 0.3
+# Latest development version: https://github.com/FiXato/listbuffer
+#
+#   Show /list results in a common buffer and interact with them.
+#
+#   This script allows you to easily join channels from the /list output.
+#   It will open a common buffer for the /list result, through which you
+#   browse with your cursor keys, and join with the meta-enter keys.
+#   Adjust sorting with meta->, meta-< and meta-/ keybindings.
+#
+## History:
+### 2011-09-08: FiXato:
+#
+# * version 0.1:  initial release.
+#     * added a common buffer for /list results
+#     * added highlighting for currently selected line
+#     * added /join support via enter key
+#     * added scroll_top and scroll_bottom support
+#
+# * version 0.2:  /list format bugfix
+#     * added support for /list results without modes
+#     * some servers don't send 321 (/list start). Taken into account.
+#
+# * version 0.3: Sorting support
+#     * Added some basic sorting support. Scroll through sort options
+#       with meta-> and meta-< (users, channel, topic, modes)
+#
+### 2011-09-19: FiXato
+#
+# * version 0.4:
+#     * Case-insensitive buffer lookup fix.
+#     * Removed default enter keybind
+#
+### 2011-12-28: troydm:
+#
+# * version 0.5: It's an upside-down-world
+#     * Added inverted sorting support provided by Dmitry "troydm" Geurkov
+#       Use meta-/ to switch between inverted and regular sorting.
+#
+### 2012-02-10: FiXato:
+#
+# * version 0.6: Stop shoving that buffer in my face!
+#     * The listbuffer should no longer pop up by itself when you load the script.
+#       It should only pop up now when you actually do a /list query.
+#
+# * version 0.7: .. but please pop it up in my current window when I ask for it
+#     * Added setting plugins.var.python.listbuffer.autofocus
+#       This will autofocus the listbuffer in the current window if another window isn't
+#       already showing it, and of course only when the user issues /list
+#
+### 2012-07-10: FiXato:
+#
+# * version 0.7.1: Forgetful bugfix
+#     * Made sure lb_curline global variable is defined
+#
+### 2013-03-19: FiXato:
+#
+# * version 0.8: Sorted out the sorting
+#     * Added automatically updating options for sorting:
+#       * plugins.var.python.listbuffer.sort_inverted
+#       * plugins.var.python.listbuffer.sort_order
+# * version 0.8.1: Pad it baby!
+#     * Channel modes are equally padded even when there are no channel modes.
+#     * Added padding options:
+#       * plugins.var.python.listbuffer.modes_min_width
+#       * plugins.var.python.listbuffer.channel_min_width
+#       * plugins.var.python.listbuffer.users_min_width
+#
+## Acknowledgements:
+# * Dmitry "troydm" Geurkov, for providing the inverse-sorting patch to the project.
+# * Sebastien "Flashcode" Helleu, for developing the kick-ass IRC client WeeChat
+#    and the iset.pl script which inspired me to this script.
+# * Nils "nils_2" Görs, for his contributions to iset.pl which served as
+#    example code.
+# * David "drubin" Rubin, for his urlgrab.py script, which also served
+#    as example code.
+# * ArZa, whose listsort.pl script helped me getting started with
+#    grabbing the /list results. Parts of his code have been shamelessly
+#    copied and ported to Python.
+# * Khaled Mardam-Bey, for making me yearn for similar /list support in
+#    WeeChat as mIRC already offered. :P
+# * mave_, for pointing out that sort orders weren't remembered.
+#
+## TODO:
+#   - Auto-scroll selected line upon window scroll.
+#   - Add option to hide already joined channels.
+#   - Improve sorting methods
+#   - Add auto-join support
+#   - Detect if channel is already in auto-join
+#   - Allow automatically switching to the listbuffer
+#   - Add support for ALIS (/squery alis LIST * -mix 100 (IRCNet)
+#   - Make colours configurable
+#   - Limit number of channels to parse
+#   - Add filter support a la iset
+#   - Allow selecting multiple channels
+#   - Add optional command redirection.
+#
+## Copyright (c) 2011,2012,2013 Filip H.F. "FiXato" Slagter,
+#   <FiXato [at] Gmail [dot] com>
+#   http://profile.fixato.org
+#
+# 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
+# NON-INFRINGEMENT. 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    = "listbuffer"
+SCRIPT_AUTHOR  = "Filip H.F. 'FiXato' Slagter <fixato [at] gmail [dot] com>"
+SCRIPT_VERSION = "0.8.1"
+SCRIPT_LICENSE = "MIT"
+SCRIPT_DESC    = "A common buffer for /list output."
+SCRIPT_COMMAND = "listbuffer"
+
+import_ok = True
+
+try:
+  import weechat
+except ImportError:
+  print "This script must be run under WeeChat."
+  import_ok = False
+
+import re
+
+lb_settings = (
+  ("autofocus", "on", "Focus the listbuffer in the current window if it isn't already displayed by a window."),
+  ("sort_order", "users", "Last used sort order for the channel list."),
+  ("sort_inverted", "on", "Invert the sort order for the channel list."),
+  ("modes_min_width", "8", "The minimum width used for modes in the channel list. If a channel has less modes than this amount, the column will be padded with spaces."),
+  ("channel_min_width", "25", "The minimum width used for the channel name in the channel list. If a channelname is shorter than this amount, the column will be padded with spaces."),
+  ("users_min_width", "8", "The minimum width used for the usercount in the channel list. If the usercount has less digits than this amount, the column will be padded with spaces."),
+)
+lb_buffer = None
+lb_curline = 0
+lb_channels = []
+lb_network = None
+lb_list_started = False
+lb_current_sort = None
+lb_sort_inverted = False
+lb_sort_options = (
+  'channel',
+  'users',
+  'modes',
+  'topic',
+)
+
+#                              server numeric Nick Chan  Users     Modes    Topic
+lb_channel_list_expression = '(:\S+) (\d{3}) (\S+) (\S+) (\d+) :(\[(.*?)\] )?(.*)'
+
+# Create listbuffer.
+def lb_create_buffer():
+  global lb_buffer, lb_curline
+
+  if not lb_buffer:
+    lb_buffer = weechat.buffer_new("listbuffer", "lb_input_cb", \
+                "", "lb_close_cb", "")
+    lb_set_buffer_title()
+    # Sets notify to 0 as this buffer does not need to be in hotlist.
+    weechat.buffer_set(lb_buffer, "notify", "0")
+    weechat.buffer_set(lb_buffer, "nicklist", "0")
+    weechat.buffer_set(lb_buffer, "type", "free")
+    weechat.buffer_set(lb_buffer, "key_bind_ctrl-L", "/listbuffer **refresh")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-A", "/listbuffer **up")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-B", "/listbuffer **down")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-1~", "/listbuffer **scroll_top")
+    weechat.buffer_set(lb_buffer, "key_bind_meta2-4~", "/listbuffer **scroll_bottom")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-J", "/listbuffer **enter")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-M", "/listbuffer **enter")
+    weechat.buffer_set(lb_buffer, "key_bind_meta->", "/listbuffer **sort_next")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-<", "/listbuffer **sort_previous")
+    weechat.buffer_set(lb_buffer, "key_bind_meta-/", "/listbuffer **sort_invert")
+    lb_curline = 0
+  if weechat.config_get_plugin("autofocus") == "on":
+    if not weechat.window_search_with_buffer(lb_buffer):
+      weechat.command("", "/buffer " + weechat.buffer_get_string(lb_buffer,"name"))
+
+def lb_set_buffer_title():
+  global lb_buffer, lb_current_sort
+  ascdesc = '(v)' if lb_sort_inverted else '(^)'
+  weechat.buffer_set(lb_buffer, "title", lb_line_format({
+    'channel': 'Channel name%s' % (ascdesc if lb_current_sort == 'channel' else ''),
+    'users': 'Users%s' % (ascdesc if lb_current_sort == 'users' else ''),
+    'modes': 'Modes%s' % (ascdesc if lb_current_sort == 'modes' else ''),
+    'topic': 'Topic%s' % (ascdesc if lb_current_sort == 'topic' else ''),
+    'nomodes': None,
+  }))
+
+def lb_list_start(data, signal, message):
+  lb_initialise_list
+
+  return weechat.WEECHAT_RC_OK
+
+def lb_initialise_list(signal):
+  global lb_channels, lb_network, lb_list_started
+
+  lb_create_buffer()
+  lb_channels = []
+  lb_network = signal.split(',')[0]
+  lb_list_started = True
+  return
+
+
+def lb_list_chan(data, signal, message):
+  global lb_channels, lb_buffer, lb_list_started
+
+  # Work-around for IRCds which don't send 321 Numeric (/List start)
+  if not lb_list_started:
+    lb_initialise_list(signal)
+
+  for chan_data in re.findall(lb_channel_list_expression,message):
+    lb_channels.append({
+      'server':  chan_data[0][1:-1],
+      'numeric': chan_data[1],
+      'nick':    chan_data[2],
+      'channel': chan_data[3],
+      'users':   chan_data[4],
+      'nomodes': chan_data[5] == '',
+      'modes':   chan_data[6],
+      'topic':   weechat.hook_modifier_exec("irc_color_decode", "1", chan_data[7])
+    })
+  return weechat.WEECHAT_RC_OK
+
+def lb_list_end(data, signal, message):
+  global lb_list_started
+
+  # Work-around for IRCds which don't send 321 Numeric (/List start)
+  if not lb_list_started:
+    lb_initialise_list(signal)
+
+  lb_list_started = False
+  if lb_current_sort:
+    lb_sort()
+  lb_refresh()
+  return weechat.WEECHAT_RC_OK
+
+def keyEvent (data, buffer, args):
+  global lb_options
+  lb_options[args]()
+
+def lb_input_cb(data, buffer, input_data):
+  global lb_options, lb_curline
+  lb_options[input_data]()
+  return weechat.WEECHAT_RC_OK
+
+def lb_refresh():
+  global lb_channels, lb_buffer
+  weechat.buffer_clear(lb_buffer)
+
+  y = 0
+  for list_data in lb_channels:
+    lb_refresh_line(y)
+    y += 1
+  return
+
+def lb_refresh_line(y):
+  global lb_buffer, lb_curline, lb_channels
+  if y >= 0 and y < len(lb_channels):
+    formatted_line = lb_line_format(lb_channels[y], y == lb_curline)
+    weechat.prnt_y(lb_buffer, y, formatted_line)
+
+def lb_refresh_curline():
+  global lb_curline
+  lb_refresh_line(lb_curline-1)
+  lb_refresh_line(lb_curline)
+  lb_refresh_line(lb_curline+1)
+  return
+
+def lb_line_format(list_data,curr=False):
+  str = ""
+  if (curr):
+    str += weechat.color("yellow,red")
+  channel_text = list_data['channel'].ljust(int(weechat.config_get_plugin('channel_min_width')))
+  users_text = "(%s)" % list_data['users']
+  padded_users_text = users_text.rjust(int(weechat.config_get_plugin('users_min_width')) + 2)
+  str += "%s%s %s " % (weechat.color("bold"), channel_text, padded_users_text)
+  if not list_data['nomodes']:
+    modes = "[%s]" % list_data['modes']
+  else:
+    modes = "[]"
+  str += "%s: " % modes.rjust(int(weechat.config_get_plugin('modes_min_width')) + 2)
+  str += "%s" % list_data['topic']
+  return str
+
+def lb_line_up():
+  global lb_curline
+  if lb_curline <= 0:
+    return
+  lb_curline -= 1
+  lb_refresh_curline()
+  lb_check_outside_window()
+  return
+
+def lb_line_down():
+  global lb_curline, lb_channels
+  if lb_curline+1 >= len(lb_channels):
+    return
+  lb_curline += 1
+  lb_refresh_curline()
+  lb_check_outside_window()
+  return
+
+def lb_line_run():
+  global lb_channels, lb_curline, lb_network
+  buff = weechat.info_get("irc_buffer", lb_network)
+  channel = lb_channels[lb_curline]['channel']
+  command = "/join %s" % channel
+  weechat.command(buff, command)
+  return
+
+def lb_line_select():
+  return
+
+def lb_scroll_top():
+  global lb_curline
+  old_y = lb_curline
+  lb_curline = 0
+  lb_refresh_curline()
+  lb_refresh_line(old_y)
+  weechat.command(lb_buffer, "/window scroll_top")
+  return
+
+def lb_scroll_bottom():
+  global lb_curline, lb_channels
+  old_y = lb_curline
+  lb_curline = len(lb_channels)-1
+  lb_refresh_curline()
+  lb_refresh_line(old_y)
+  weechat.command(lb_buffer, "/window scroll_bottom")
+  return
+
+def lb_check_outside_window():
+  global lb_buffer, lb_curline
+  if (lb_buffer):
+    infolist = weechat.infolist_get("window", "", "current")
+    if (weechat.infolist_next(infolist)):
+      start_line_y = weechat.infolist_integer(infolist, "start_line_y")
+      chat_height = weechat.infolist_integer(infolist, "chat_height")
+      if(start_line_y > lb_curline):
+        weechat.command(lb_buffer, "/window scroll -%i" %(start_line_y - lb_curline))
+      elif(start_line_y <= lb_curline - chat_height):
+        weechat.command(lb_buffer, "/window scroll +%i"%(lb_curline - start_line_y - chat_height + 1))
+    weechat.infolist_free(infolist)
+
+def lb_sort_next():
+  global lb_current_sort, lb_sort_options
+  if lb_current_sort:
+    new_index = lb_sort_options.index(lb_current_sort) + 1
+  else:
+    new_index = 0
+
+  if len(lb_sort_options) <= new_index:
+    new_index = 0
+
+  lb_set_current_sort_order(lb_sort_options[new_index])
+  lb_sort()
+
+def lb_set_current_sort_order(value):
+  global lb_current_sort
+  lb_current_sort = value
+  weechat.config_set_plugin('sort_order', lb_current_sort)
+
+def lb_set_invert_sort_order(value):
+  global lb_sort_inverted
+  lb_sort_inverted = value
+  weechat.config_set_plugin('sort_inverted', ('on' if lb_sort_inverted else 'off'))
+
+def lb_sort_previous():
+  global lb_current_sort, lb_sort_options
+  if lb_current_sort:
+    new_index = lb_sort_options.index(lb_current_sort) - 1
+  else:
+    new_index = 0
+
+  if new_index < 0:
+    new_index = len(lb_sort_options) - 1
+
+  lb_set_current_sort_order(lb_sort_options[new_index])
+  lb_sort()
+
+def lb_sort(sort_key=None):
+  global lb_channels, lb_current_sort, lb_sort_inverted
+  if sort_key:
+    lb_set_current_sort_order(sort_key)
+  if lb_current_sort == 'users':
+    lb_channels = sorted(lb_channels, key=lambda chan_data: int(chan_data[lb_current_sort]))
+  else:
+    lb_channels = sorted(lb_channels, key=lambda chan_data: chan_data[lb_current_sort])
+  if lb_sort_inverted:
+    lb_channels.reverse()
+  lb_set_buffer_title()
+  lb_refresh()
+
+def lb_sort_invert():
+  global lb_current_sort, lb_sort_inverted
+  if lb_current_sort:
+    lb_set_invert_sort_order(not lb_sort_inverted)
+    lb_sort()
+
+def lb_close_cb(*kwargs):
+  """ A callback for buffer closing. """
+  global lb_buffer
+
+  lb_buffer = None
+  return weechat.WEECHAT_RC_OK
+
+lb_options = {
+  'refresh'     : lb_refresh,
+  'up'          : lb_line_up,
+  'down'        : lb_line_down,
+  'enter'       : lb_line_run,
+  'space'       : lb_line_select,
+  'scroll_top'  : lb_scroll_top,
+  'scroll_bottom': lb_scroll_bottom,
+  'sort_next'   : lb_sort_next,
+  'sort_previous': lb_sort_previous,
+  'sort_invert': lb_sort_invert
+}
+
+def lb_command_main(data, buffer, args):
+  if args[0:2] == "**":
+    keyEvent(data, buffer, args[2:])
+  return weechat.WEECHAT_RC_OK
+
+def lb_set_default_settings():
+  global lb_settings
+  # Set default settings
+  for option, default_value, description in lb_settings:
+     if not weechat.config_is_set_plugin(option):
+         weechat.config_set_plugin(option, default_value)
+         version = weechat.info_get("version_number", "") or 0
+         if int(version) >= 0x00030500:
+             weechat.config_set_desc_plugin(option, description)
+
+def lb_reset_stored_sort_order():
+  global lb_current_sort, lb_sort_inverted
+  lb_current_sort = weechat.config_get_plugin('sort_order')
+  lb_sort_inverted = (True if weechat.config_get_plugin('sort_inverted') == 'on' else False)
+
+if __name__ == "__main__" and import_ok:
+  if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
+                      SCRIPT_LICENSE, SCRIPT_DESC, "lb_close_cb", ""):
+    lb_set_default_settings()
+    lb_reset_stored_sort_order()
+    lb_buffer = weechat.buffer_search("python", "listbuffer")
+
+    weechat.hook_signal("*,irc_in_321", "lb_list_start", "")
+    weechat.hook_signal("*,irc_in_322", "lb_list_chan", "")
+    weechat.hook_signal("*,irc_in_323", "lb_list_end", "")
+    weechat.hook_command(SCRIPT_COMMAND,
+                          "List Buffer",
+                          "", "", "",
+                          "lb_command_main", "")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/weechat/ruby/autoload/challengeauth.rb	Thu May 14 11:06:43 2015 -0400
@@ -0,0 +1,111 @@
+# Copyright (c) 2013 Dominik Honnef <dominikh@fork-bomb.org>
+
+# 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.
+
+# History:
+# 2013-04-20, Dominik Honnef
+#   version 0.0.1: initial version
+
+require "openssl"
+
+QBot = "Q@CServe.quakenet.org"
+QBotHost = "Q!TheQBot@CServe.quakenet.org"
+Request = Struct.new(:username, :hash)
+
+def weechat_init
+  @requests = {}
+
+  Weechat.register("challengeauth",
+                   "Dominik Honnef",
+                   "0.0.1",
+                   "MIT",
+                   "Securely authenticate with QuakeNet by using CHALLENGEAUTH",
+                   "",
+                   "")
+
+  Weechat.hook_command("challengeauth",
+                       "Authenticate with Q using CHALLENGEAUTH",
+                       "[username] [password]",
+                       "",
+                       "",
+                       "challengeauth",
+                       "")
+
+  Weechat.hook_modifier("irc_in_notice", "challenge_notice", "")
+
+  return Weechat::WEECHAT_RC_OK
+end
+
+def calculate_q_hash(username, hash, challenge)
+  username = username.tr("A-Z[]\\\\^", "a-z{}|~")
+
+  key = OpenSSL::Digest::SHA256.hexdigest("#{username}:#{hash}")
+  return OpenSSL::HMAC.hexdigest("SHA256", key, challenge)
+end
+
+def get_server_buffer(server)
+  Weechat.buffer_search("irc", "server." + server)
+end
+
+def challengeauth(data, buffer, args)
+  plugin = Weechat.buffer_get_string(buffer, "localvar_plugin")
+  if plugin != "irc"
+    Weechat.print(buffer, "/challengeauth only works for IRC buffers.")
+    return Weechat.WEECHAT_RC_ERROR
+  end
+
+  server = Weechat.buffer_get_string(buffer, "localvar_server")
+  args = args.split(" ", 2)
+  username = args[0]
+  password = args[1]
+  hash = OpenSSL::Digest::SHA256.hexdigest(password[0, 10])
+
+  @requests[server] = Request.new(username, hash)
+  server_buffer = get_server_buffer(server)
+  Weechat.print(server_buffer, "Authenticating as #{username}...")
+  Weechat.command(server_buffer, "/quote PRIVMSG #{QBot} :CHALLENGE")
+
+  return Weechat::WEECHAT_RC_OK
+end
+
+def challenge_notice(modifier, data, server, line)
+  return line unless @requests.has_key?(server)
+
+  parts = line.split(" ")
+  return line unless parts.size > 5
+
+  host = parts[0][1..-1]
+  command = parts[3][1..-1]
+  challenge = parts[4]
+
+  return line if host != QBotHost || command != "CHALLENGE"
+
+  request = @requests[server]
+  response = calculate_q_hash(request.username, request.hash, challenge)
+  server_buffer = get_server_buffer(server)
+
+  Weechat.print(server_buffer, "Sending challengeauth for #{request.username}...")
+  Weechat.command(server_buffer,
+                  "/quote PRIVMSG %s :CHALLENGEAUTH %s %s HMAC-SHA-256" % [QBot, request.username, response])
+
+  @requests.delete(server)
+
+  return line
+end
--- a/weechat/weechat.conf	Thu Dec 18 21:59:14 2014 -0500
+++ b/weechat/weechat.conf	Thu May 14 11:06:43 2015 -0400
@@ -35,8 +35,8 @@
 day_change_message_1date = "-- %a, %d %b %Y --"
 day_change_message_2dates = "-- %%a, %%d %%b %%Y (%a, %d %b %Y) --"
 eat_newline_glitch = off
-highlight = "sjl,slosh,slj,clojerks,horrifying"
-highlight_regex = "(^all[,:]|backend(ia)?[^s/]|red[- ]tape|steve ?losh|rob ford|(jesus )?fucking christ|bring me my [A-Za-z0-9_]+ (pant|breeche|kilt|skirt|short|jort|plort)s?|(horse|mouse|clown)fuckers?|([[:<:]]((mother)?fuck([ie]ng?|er)?|(god?)?damn(ed)?|dammit|(bull|horse)?shite?)[[:>:]].*){3,}|actual footage)"
+highlight = "sjl,slosh,slj,clojerks,horrifying,remotes"
+highlight_regex = "(^all[,:]|backend(ia)?[^s/]|red[- ]tape|steve ?losh|rob ford|(jesus )?fucking christ|bring me my [A-Za-z0-9_]+ (pant|breeche|kilt|skirt|short|jort|plort)s?|(horse|mouse|clown)fuckers?|([[:<:]]((mother)?fuck([ie]ng?|er)?|(god?)?damn(ed)?|dammit|(bull|horse)?shite?)[[:>:]].*){3,}|actual footage|pork ?belly)"
 highlight_tags = ""
 hotlist_add_buffer_if_away = on
 hotlist_buffer_separator = ", "
@@ -250,7 +250,7 @@
 status.filling_left_right = vertical
 status.filling_top_bottom = horizontal
 status.hidden = off
-status.items = "[time],buffer_number+:+buffer_name"
+status.items = "[time],buffer_number+:+buffer_name,buffer_title"
 status.position = bottom
 status.priority = 500
 status.separator = off