···11+#
22+# weechat -- autosort.conf
33+#
44+# WARNING: It is NOT recommended to edit this file by hand,
55+# especially if WeeChat is running.
66+#
77+# Use /set or similar command to change settings in WeeChat.
88+#
99+# For more info, see: https://weechat.org/doc/quickstart
1010+#
1111+1212+[sorting]
1313+case_sensitive = off
1414+replacements = ""
1515+rules = ""
1616+signal_delay = 5
1717+signals = "buffer_opened buffer_merged buffer_unmerged buffer_renamed"
1818+sort_on_config_change = on
1919+2020+[v3]
2121+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}"}"
2222+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}"]"
+20
weechat/.weechat/colorize_nicks.conf
···11+#
22+# weechat -- colorize_nicks.conf
33+#
44+# WARNING: It is NOT recommended to edit this file by hand,
55+# especially if WeeChat is running.
66+#
77+# Use /set or similar command to change settings in WeeChat.
88+#
99+# For more info, see: https://weechat.org/doc/quickstart
1010+#
1111+1212+[look]
1313+blacklist_channels = ""
1414+blacklist_nicks = "so,root"
1515+colorize_input = off
1616+greedy_matching = on
1717+ignore_nicks_in_urls = off
1818+ignore_tags = ""
1919+match_limit = 20
2020+min_nick_length = 2
···3232python.apply_corrections.print_limit = "1"
3333python.autojoin.autosave = "off"
3434python.check_license = "off"
3535+python.go.auto_jump = "off"
3636+python.go.buffer_number = "on"
3737+python.go.color_name = "black,cyan"
3838+python.go.color_name_highlight = "red,cyan"
3939+python.go.color_name_highlight_selected = "red,brown"
4040+python.go.color_name_selected = "black,brown"
4141+python.go.color_number = "yellow,magenta"
4242+python.go.color_number_selected = "yellow,red"
4343+python.go.fuzzy_search = "off"
4444+python.go.message = "Go to: "
4545+python.go.short_name = "off"
4646+python.go.sort = "number,beginning"
4747+python.go.use_core_instead_weechat = "off"
3548python.grep.clear_buffer = "off"
3649python.grep.default_tail_head = "10"
3750python.grep.go_to_buffer = "on"
···5770python.apply_corrections.message_limit = "Number of messages to store per nick."
5871python.apply_corrections.print_format = "Format string for the printed corrections."
5972python.apply_corrections.print_limit = "Maximum number of lines to correct."
7373+python.go.auto_jump = "automatically jump to buffer when it is uniquely selected (default: "off")"
7474+python.go.buffer_number = "display buffer number (default: "on")"
7575+python.go.color_name = "color for buffer name (not selected) (default: "black,cyan")"
7676+python.go.color_name_highlight = "color for highlight in buffer name (not selected) (default: "red,cyan")"
7777+python.go.color_name_highlight_selected = "color for highlight in a selected buffer name (default: "red,brown")"
7878+python.go.color_name_selected = "color for a selected buffer name (default: "black,brown")"
7979+python.go.color_number = "color for buffer number (not selected) (default: "yellow,magenta")"
8080+python.go.color_number_selected = "color for selected buffer number (default: "yellow,red")"
8181+python.go.fuzzy_search = "search buffer matches using approximation (default: "off")"
8282+python.go.message = "message to display before list of buffers (default: "Go to: ")"
8383+python.go.short_name = "display and search in short names instead of buffer name (default: "off")"
8484+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")"
8585+python.go.use_core_instead_weechat = "use name "core" instead of "weechat" for core buffer (default: "off")"
6086python.screen_away.away_suffix = "What to append to your nick when you're away."
6187python.screen_away.command_on_attach = "Commands to execute on attach, separated by semicolon"
6288python.screen_away.command_on_detach = "Commands to execute on detach, separated by semicolon"
+30-13
weechat/.weechat/python/autojoin.py
···4343# 2014-05-22, Nathaniel Wesley Filardo <PADEBR2M2JIQN02N9OO5JM0CTN8K689P@cmx.ietfng.org>
4444# version 0.2.5: Fix keyed channel support
4545#
4646+# 2016-01-13, The fox in the shell <KellerFuchs@hashbang.sh>
4747+# version 0.2.6: Support keeping chan list as secured data
4848+#
4649# @TODO: add options to ignore certain buffers
4750# @TODO: maybe add an option to enable autosaving on part/join messages
4851···51545255SCRIPT_NAME = "autojoin"
5356SCRIPT_AUTHOR = "xt <xt@bash.no>"
5454-SCRIPT_VERSION = "0.2.5"
5757+SCRIPT_VERSION = "0.2.6"
5558SCRIPT_LICENSE = "GPL3"
5659SCRIPT_DESC = "Configure autojoin for all servers according to currently joined channels"
5760SCRIPT_COMMAND = "autojoin"
···89929093 # print/execute commands
9194 for server, channels in items.iteritems():
9292- channels = channels.rstrip(',')
9393- command = "/set irc.server.%s.autojoin '%s'" % (server, channels)
9494- w.command('', command)
9595+ process_server(server, channels)
95969697 return w.WEECHAT_RC_OK
9798···111112 match = re.match(pattern, callback_data)
112113113114 if match: # check if nick is my nick. In that case: save
114114- channel = match.group(2)
115115- channels = channels.rstrip(',')
116116- command = "/set irc.server.%s.autojoin '%s'" % (server, channels)
117117- w.command('', command)
115115+ process_server(server, channels)
118116 else: # someone else: ignore
119117 continue
120118···126124 """But I can't believe somebody would want that behaviour"""
127125 items = find_channels()
128126127127+ if args == '--run':
128128+ run = True
129129+ else:
130130+ run = False
131131+129132 # print/execute commands
130133 for server, channels in items.iteritems():
134134+ process_server(server, channels, run)
135135+136136+ return w.WEECHAT_RC_OK
137137+138138+def process_server(server, channels, run=True):
139139+ option = "irc.server.%s.autojoin" % server
131140 channels = channels.rstrip(',')
141141+ oldchans = w.config_string(w.config_get(option))
142142+132143 if not channels: # empty channel list
133133- continue
134134- command = '/set irc.server.%s.autojoin %s' % (server, channels)
135135- if args == '--run':
144144+ return
145145+146146+ # Note: re already caches the result of regexp compilation
147147+ sec = re.match('^\${sec\.data\.(.*)}$', oldchans)
148148+ if sec:
149149+ secvar = sec.group(1)
150150+ command = "/secure set %s %s" % (secvar, channels)
151151+ else:
152152+ command = "/set irc.server.%s.autojoin '%s'" % (server, channels)
153153+154154+ if run:
136155 w.command('', command)
137156 else:
138157 w.prnt('', command)
139139-140140- return w.WEECHAT_RC_OK
141158142159def find_channels():
143160 """Return list of servers and channels"""
···11+# -*- coding: utf-8 -*-
22+#
33+# Copyright (C) 2013-2017 Maarten de Vries <maarten@de-vri.es>
44+#
55+# This program is free software; you can redistribute it and/or modify
66+# it under the terms of the GNU General Public License as published by
77+# the Free Software Foundation; either version 3 of the License, or
88+# (at your option) any later version.
99+#
1010+# This program is distributed in the hope that it will be useful,
1111+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1212+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1313+# GNU General Public License for more details.
1414+#
1515+# You should have received a copy of the GNU General Public License
1616+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717+#
1818+1919+#
2020+# Autosort automatically keeps your buffers sorted and grouped by server.
2121+# You can define your own sorting rules. See /help autosort for more details.
2222+#
2323+# https://github.com/de-vri-es/weechat-autosort
2424+#
2525+2626+#
2727+# Changelog:
2828+# 3.3:
2929+# * Fix the /autosort debug command for unicode.
3030+# * Update the default rules to work better with Slack.
3131+# 3.2:
3232+# * Fix python3 compatiblity.
3333+# 3.1:
3434+# * Use colors to format the help text.
3535+# 3.0:
3636+# * Switch to evaluated expressions for sorting.
3737+# * Add `/autosort debug` command.
3838+# * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules.
3939+# * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules.
4040+# * Make tab completion context aware.
4141+# 2.8:
4242+# * Fix compatibility with python 3 regarding unicode handling.
4343+# 2.7:
4444+# * Fix sorting of buffers with spaces in their name.
4545+# 2.6:
4646+# * Ignore case in rules when doing case insensitive sorting.
4747+# 2.5:
4848+# * Fix handling unicode buffer names.
4949+# * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on.
5050+# 2.4:
5151+# * Make script python3 compatible.
5252+# 2.3:
5353+# * Fix sorting items without score last (regressed in 2.2).
5454+# 2.2:
5555+# * Add configuration option for signals that trigger a sort.
5656+# * Add command to manually trigger a sort (/autosort sort).
5757+# * Add replacement patterns to apply before sorting.
5858+# 2.1:
5959+# * Fix some minor style issues.
6060+# 2.0:
6161+# * Allow for custom sort rules.
6262+#
6363+6464+6565+import json
6666+import math
6767+import re
6868+import sys
6969+import time
7070+import weechat
7171+7272+SCRIPT_NAME = 'autosort'
7373+SCRIPT_AUTHOR = 'Maarten de Vries <maarten@de-vri.es>'
7474+SCRIPT_VERSION = '3.3'
7575+SCRIPT_LICENSE = 'GPL3'
7676+SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.'
7777+7878+7979+config = None
8080+hooks = []
8181+timer = None
8282+8383+# Make sure that unicode, bytes and str are always available in python2 and 3.
8484+# For python 2, str == bytes
8585+# For python 3, str == unicode
8686+if sys.version_info[0] >= 3:
8787+ unicode = str
8888+8989+def ensure_str(input):
9090+ '''
9191+ Make sure the given type if the correct string type for the current python version.
9292+ That means bytes for python2 and unicode for python3.
9393+ '''
9494+ if not isinstance(input, str):
9595+ if isinstance(input, bytes):
9696+ return input.encode('utf-8')
9797+ if isinstance(input, unicode):
9898+ return input.decode('utf-8')
9999+ return input
100100+101101+102102+if hasattr(time, 'perf_counter'):
103103+ perf_counter = time.perf_counter
104104+else:
105105+ perf_counter = time.clock
106106+107107+def casefold(string):
108108+ if hasattr(string, 'casefold'): return string.casefold()
109109+ # Fall back to lowercasing for python2.
110110+ return string.lower()
111111+112112+def list_swap(values, a, b):
113113+ values[a], values[b] = values[b], values[a]
114114+115115+def list_move(values, old_index, new_index):
116116+ values.insert(new_index, values.pop(old_index))
117117+118118+def list_find(collection, value):
119119+ for i, elem in enumerate(collection):
120120+ if elem == value: return i
121121+ return None
122122+123123+class HumanReadableError(Exception):
124124+ pass
125125+126126+def parse_int(arg, arg_name = 'argument'):
127127+ ''' Parse an integer and provide a more human readable error. '''
128128+ arg = arg.strip()
129129+ try:
130130+ return int(arg)
131131+ except ValueError:
132132+ raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg))
133133+134134+def decode_rules(blob):
135135+ parsed = json.loads(blob)
136136+ if not isinstance(parsed, list):
137137+ 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)))
138138+ return []
139139+140140+ for i, entry in enumerate(parsed):
141141+ if not isinstance(entry, (str, unicode)):
142142+ log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry)))
143143+ return []
144144+145145+ return parsed
146146+147147+def decode_helpers(blob):
148148+ parsed = json.loads(blob)
149149+ if not isinstance(parsed, dict):
150150+ 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)))
151151+ return {}
152152+153153+ for key, value in parsed.items():
154154+ if not isinstance(value, (str, unicode)):
155155+ log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix seting manually.'.format(key, type(value)))
156156+ return {}
157157+ return parsed
158158+159159+class Config:
160160+ ''' The autosort configuration. '''
161161+162162+ default_rules = json.dumps([
163163+ '${core_first}',
164164+ '${irc_last}',
165165+ '${buffer.plugin.name}',
166166+ '${irc_raw_first}',
167167+ '${if:${plugin}==irc?${server}}',
168168+ '${if:${plugin}==irc?${info:autosort_order,${type},server,*,channel,private}}',
169169+ '${if:${plugin}==irc?${hashless_name}}',
170170+ '${buffer.full_name}',
171171+ ])
172172+173173+ default_helpers = json.dumps({
174174+ 'core_first': '${if:${buffer.full_name}!=core.weechat}',
175175+ 'irc_first': '${if:${buffer.plugin.name}!=irc}',
176176+ 'irc_last': '${if:${buffer.plugin.name}==irc}',
177177+ 'irc_raw_first': '${if:${buffer.full_name}!=irc.irc_raw}',
178178+ 'irc_raw_last': '${if:${buffer.full_name}==irc.irc_raw}',
179179+ 'hashless_name': '${info:autosort_replace,#,,${buffer.name}}',
180180+ })
181181+182182+ default_signal_delay = 5
183183+184184+ default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed'
185185+186186+ def __init__(self, filename):
187187+ ''' Initialize the configuration. '''
188188+189189+ self.filename = filename
190190+ self.config_file = weechat.config_new(self.filename, '', '')
191191+ self.sorting_section = None
192192+ self.v3_section = None
193193+194194+ self.case_sensitive = False
195195+ self.rules = []
196196+ self.helpers = {}
197197+ self.signals = []
198198+ self.signal_delay = Config.default_signal_delay,
199199+ self.sort_on_config = True
200200+201201+ self.__case_sensitive = None
202202+ self.__rules = None
203203+ self.__helpers = None
204204+ self.__signals = None
205205+ self.__signal_delay = None
206206+ self.__sort_on_config = None
207207+208208+ if not self.config_file:
209209+ log('Failed to initialize configuration file "{0}".'.format(self.filename))
210210+ return
211211+212212+ self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '')
213213+ self.v3_section = weechat.config_new_section(self.config_file, 'v3', False, False, '', '', '', '', '', '', '', '', '', '')
214214+215215+ if not self.sorting_section:
216216+ log('Failed to initialize section "sorting" of configuration file.')
217217+ weechat.config_free(self.config_file)
218218+ return
219219+220220+ self.__case_sensitive = weechat.config_new_option(
221221+ self.config_file, self.sorting_section,
222222+ 'case_sensitive', 'boolean',
223223+ 'If this option is on, sorting is case sensitive.',
224224+ '', 0, 0, 'off', 'off', 0,
225225+ '', '', '', '', '', ''
226226+ )
227227+228228+ weechat.config_new_option(
229229+ self.config_file, self.sorting_section,
230230+ 'rules', 'string',
231231+ 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.',
232232+ '', 0, 0, '', '', 0,
233233+ '', '', '', '', '', ''
234234+ )
235235+236236+ weechat.config_new_option(
237237+ self.config_file, self.sorting_section,
238238+ 'replacements', 'string',
239239+ 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.',
240240+ '', 0, 0, '', '', 0,
241241+ '', '', '', '', '', ''
242242+ )
243243+244244+ self.__rules = weechat.config_new_option(
245245+ self.config_file, self.v3_section,
246246+ 'rules', 'string',
247247+ 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.',
248248+ '', 0, 0, Config.default_rules, Config.default_rules, 0,
249249+ '', '', '', '', '', ''
250250+ )
251251+252252+ self.__helpers = weechat.config_new_option(
253253+ self.config_file, self.v3_section,
254254+ 'helpers', 'string',
255255+ 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.',
256256+ '', 0, 0, Config.default_helpers, Config.default_helpers, 0,
257257+ '', '', '', '', '', ''
258258+ )
259259+260260+ self.__signals = weechat.config_new_option(
261261+ self.config_file, self.sorting_section,
262262+ 'signals', 'string',
263263+ 'A space separated list of signals that will cause autosort to resort your buffer list.',
264264+ '', 0, 0, Config.default_signals, Config.default_signals, 0,
265265+ '', '', '', '', '', ''
266266+ )
267267+268268+ self.__signal_delay = weechat.config_new_option(
269269+ self.config_file, self.sorting_section,
270270+ 'signal_delay', 'integer',
271271+ '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.',
272272+ '', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0,
273273+ '', '', '', '', '', ''
274274+ )
275275+276276+ self.__sort_on_config = weechat.config_new_option(
277277+ self.config_file, self.sorting_section,
278278+ 'sort_on_config_change', 'boolean',
279279+ 'Decides if the buffer list should be sorted when autosort configuration changes.',
280280+ '', 0, 0, 'on', 'on', 0,
281281+ '', '', '', '', '', ''
282282+ )
283283+284284+ if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK:
285285+ log('Failed to load configuration file.')
286286+287287+ if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK:
288288+ log('Failed to write configuration file.')
289289+290290+ self.reload()
291291+292292+ def reload(self):
293293+ ''' Load configuration variables. '''
294294+295295+ self.case_sensitive = weechat.config_boolean(self.__case_sensitive)
296296+297297+ rules_blob = weechat.config_string(self.__rules)
298298+ helpers_blob = weechat.config_string(self.__helpers)
299299+ signals_blob = weechat.config_string(self.__signals)
300300+301301+ self.rules = decode_rules(rules_blob)
302302+ self.helpers = decode_helpers(helpers_blob)
303303+ self.signals = signals_blob.split()
304304+ self.signal_delay = weechat.config_integer(self.__signal_delay)
305305+ self.sort_on_config = weechat.config_boolean(self.__sort_on_config)
306306+307307+ def save_rules(self, run_callback = True):
308308+ ''' Save the current rules to the configuration. '''
309309+ weechat.config_option_set(self.__rules, json.dumps(self.rules), run_callback)
310310+311311+ def save_helpers(self, run_callback = True):
312312+ ''' Save the current helpers to the configuration. '''
313313+ weechat.config_option_set(self.__helpers, json.dumps(self.helpers), run_callback)
314314+315315+316316+def pad(sequence, length, padding = None):
317317+ ''' Pad a list until is has a certain length. '''
318318+ return sequence + [padding] * max(0, (length - len(sequence)))
319319+320320+321321+def log(message, buffer = 'NULL'):
322322+ weechat.prnt(buffer, 'autosort: {0}'.format(message))
323323+324324+325325+def get_buffers():
326326+ ''' Get a list of all the buffers in weechat. '''
327327+ hdata = weechat.hdata_get('buffer')
328328+ buffer = weechat.hdata_get_list(hdata, "gui_buffers");
329329+330330+ result = []
331331+ while buffer:
332332+ number = weechat.hdata_integer(hdata, buffer, 'number')
333333+ result.append((number, buffer))
334334+ buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer')
335335+ return hdata, result
336336+337337+class MergedBuffers(list):
338338+ """ A list of merged buffers, possibly of size 1. """
339339+ def __init__(self, number):
340340+ super(MergedBuffers, self).__init__()
341341+ self.number = number
342342+343343+def merge_buffer_list(buffers):
344344+ '''
345345+ Group merged buffers together.
346346+ The output is a list of MergedBuffers.
347347+ '''
348348+ if not buffers: return []
349349+ result = {}
350350+ for number, buffer in buffers:
351351+ if number not in result: result[number] = MergedBuffers(number)
352352+ result[number].append(buffer)
353353+ return result.values()
354354+355355+def sort_buffers(hdata, buffers, rules, helpers, case_sensitive):
356356+ for merged in buffers:
357357+ for buffer in merged:
358358+ name = weechat.hdata_string(hdata, buffer, 'name')
359359+360360+ return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive))
361361+362362+def buffer_sort_key(rules, helpers, case_sensitive):
363363+ ''' Create a sort key function for a list of lists of merged buffers. '''
364364+ def key(buffer):
365365+ extra_vars = {}
366366+ for helper_name, helper in sorted(helpers.items()):
367367+ expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {})
368368+ extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded)
369369+ result = []
370370+ for rule in rules:
371371+ expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {})
372372+ result.append(expanded if case_sensitive else casefold(expanded))
373373+ return result
374374+375375+ return key
376376+377377+def merged_sort_key(rules, helpers, case_sensitive):
378378+ buffer_key = buffer_sort_key(rules, helpers, case_sensitive)
379379+ def key(merged):
380380+ best = None
381381+ for buffer in merged:
382382+ this = buffer_key(buffer)
383383+ if best is None or this < best: best = this
384384+ return best
385385+ return key
386386+387387+def apply_buffer_order(buffers):
388388+ ''' Sort the buffers in weechat according to the given order. '''
389389+ for i, buffer in enumerate(buffers):
390390+ weechat.buffer_set(buffer[0], "number", str(i + 1))
391391+392392+def split_args(args, expected, optional = 0):
393393+ ''' Split an argument string in the desired number of arguments. '''
394394+ split = args.split(' ', expected - 1)
395395+ if (len(split) < expected):
396396+ raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split)))
397397+ return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '')
398398+399399+def do_sort():
400400+ hdata, buffers = get_buffers()
401401+ buffers = merge_buffer_list(buffers)
402402+ buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive)
403403+ apply_buffer_order(buffers)
404404+405405+def command_sort(buffer, command, args):
406406+ ''' Sort the buffers and print a confirmation. '''
407407+ start = perf_counter()
408408+ do_sort()
409409+ elapsed = perf_counter() - start
410410+ log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed))
411411+ return weechat.WEECHAT_RC_OK
412412+413413+def command_debug(buffer, command, args):
414414+ hdata, buffers = get_buffers()
415415+ buffers = merge_buffer_list(buffers)
416416+417417+ # Show evaluation results.
418418+ log('Individual evaluation results:')
419419+ start = perf_counter()
420420+ key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive)
421421+ results = []
422422+ for merged in buffers:
423423+ for buffer in merged:
424424+ fullname = weechat.hdata_string(hdata, buffer, 'full_name')
425425+ results.append((fullname, key(buffer)))
426426+ elapsed = perf_counter() - start
427427+428428+ for fullname, result in results:
429429+ fullname = ensure_str(fullname)
430430+ result = [ensure_str(x) for x in result]
431431+ log('{0}: {1}'.format(fullname, result))
432432+ log('Computing evalutaion results took {0:.4f} seconds.'.format(elapsed))
433433+434434+ return weechat.WEECHAT_RC_OK
435435+436436+def command_rule_list(buffer, command, args):
437437+ ''' Show the list of sorting rules. '''
438438+ output = 'Sorting rules:\n'
439439+ for i, rule in enumerate(config.rules):
440440+ output += ' {0}: {1}\n'.format(i, rule)
441441+ if not len(config.rules):
442442+ output += ' No sorting rules configured.\n'
443443+ log(output )
444444+445445+ return weechat.WEECHAT_RC_OK
446446+447447+448448+def command_rule_add(buffer, command, args):
449449+ ''' Add a rule to the rule list. '''
450450+ config.rules.append(args)
451451+ config.save_rules()
452452+ command_rule_list(buffer, command, '')
453453+454454+ return weechat.WEECHAT_RC_OK
455455+456456+457457+def command_rule_insert(buffer, command, args):
458458+ ''' Insert a rule at the desired position in the rule list. '''
459459+ index, rule = split_args(args, 2)
460460+ index = parse_int(index, 'index')
461461+462462+ config.rules.insert(index, rule)
463463+ config.save_rules()
464464+ command_rule_list(buffer, command, '')
465465+ return weechat.WEECHAT_RC_OK
466466+467467+468468+def command_rule_update(buffer, command, args):
469469+ ''' Update a rule in the rule list. '''
470470+ index, rule = split_args(args, 2)
471471+ index = parse_int(index, 'index')
472472+473473+ config.rules[index] = rule
474474+ config.save_rules()
475475+ command_rule_list(buffer, command, '')
476476+ return weechat.WEECHAT_RC_OK
477477+478478+479479+def command_rule_delete(buffer, command, args):
480480+ ''' Delete a rule from the rule list. '''
481481+ index = args.strip()
482482+ index = parse_int(index, 'index')
483483+484484+ config.rules.pop(index)
485485+ config.save_rules()
486486+ command_rule_list(buffer, command, '')
487487+ return weechat.WEECHAT_RC_OK
488488+489489+490490+def command_rule_move(buffer, command, args):
491491+ ''' Move a rule to a new position. '''
492492+ index_a, index_b = split_args(args, 2)
493493+ index_a = parse_int(index_a, 'index')
494494+ index_b = parse_int(index_b, 'index')
495495+496496+ list_move(config.rules, index_a, index_b)
497497+ config.save_rules()
498498+ command_rule_list(buffer, command, '')
499499+ return weechat.WEECHAT_RC_OK
500500+501501+502502+def command_rule_swap(buffer, command, args):
503503+ ''' Swap two rules. '''
504504+ index_a, index_b = split_args(args, 2)
505505+ index_a = parse_int(index_a, 'index')
506506+ index_b = parse_int(index_b, 'index')
507507+508508+ list_swap(config.rules, index_a, index_b)
509509+ config.save_rules()
510510+ command_rule_list(buffer, command, '')
511511+ return weechat.WEECHAT_RC_OK
512512+513513+514514+def command_helper_list(buffer, command, args):
515515+ ''' Show the list of helpers. '''
516516+ output = 'Helper variables:\n'
517517+518518+ width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys()))
519519+520520+ for name, expression in sorted(config.helpers.items()):
521521+ output += ' {0:>{width}}: {1}\n'.format(name, expression, width=width)
522522+ if not len(config.helpers):
523523+ output += ' No helper variables configured.'
524524+ log(output)
525525+526526+ return weechat.WEECHAT_RC_OK
527527+528528+529529+def command_helper_set(buffer, command, args):
530530+ ''' Add/update a helper to the helper list. '''
531531+ name, expression = split_args(args, 2)
532532+533533+ config.helpers[name] = expression
534534+ config.save_helpers()
535535+ command_helper_list(buffer, command, '')
536536+537537+ return weechat.WEECHAT_RC_OK
538538+539539+def command_helper_delete(buffer, command, args):
540540+ ''' Delete a helper from the helper list. '''
541541+ name = args.strip()
542542+543543+ del config.helpers[name]
544544+ config.save_helpers()
545545+ command_helper_list(buffer, command, '')
546546+ return weechat.WEECHAT_RC_OK
547547+548548+549549+def command_helper_rename(buffer, command, args):
550550+ ''' Rename a helper to a new position. '''
551551+ old_name, new_name = split_args(args, 2)
552552+553553+ try:
554554+ config.helpers[new_name] = config.helpers[old_name]
555555+ del config.helpers[old_name]
556556+ except KeyError:
557557+ raise HumanReadableError('No such helper: {0}'.format(old_name))
558558+ config.save_helpers()
559559+ command_helper_list(buffer, command, '')
560560+ return weechat.WEECHAT_RC_OK
561561+562562+563563+def command_helper_swap(buffer, command, args):
564564+ ''' Swap two helpers. '''
565565+ a, b = split_args(args, 2)
566566+ try:
567567+ config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b]
568568+ except KeyError as e:
569569+ raise HumanReadableError('No such helper: {0}'.format(e.args[0]))
570570+571571+ config.helpers.swap(index_a, index_b)
572572+ config.save_helpers()
573573+ command_helper_list(buffer, command, '')
574574+ return weechat.WEECHAT_RC_OK
575575+576576+def call_command(buffer, command, args, subcommands):
577577+ ''' Call a subccommand from a dictionary. '''
578578+ subcommand, tail = pad(args.split(' ', 1), 2, '')
579579+ subcommand = subcommand.strip()
580580+ if (subcommand == ''):
581581+ child = subcommands.get(' ')
582582+ else:
583583+ command = command + [subcommand]
584584+ child = subcommands.get(subcommand)
585585+586586+ if isinstance(child, dict):
587587+ return call_command(buffer, command, tail, child)
588588+ elif callable(child):
589589+ return child(buffer, command, tail)
590590+591591+ log('{0}: command not found'.format(' '.join(command)))
592592+ return weechat.WEECHAT_RC_ERROR
593593+594594+def on_signal(*args, **kwargs):
595595+ global timer
596596+ ''' Called whenever the buffer list changes. '''
597597+ if timer is not None:
598598+ weechat.unhook(timer)
599599+ timer = None
600600+ weechat.hook_timer(config.signal_delay, 0, 1, "on_timeout", "")
601601+ return weechat.WEECHAT_RC_OK
602602+603603+def on_timeout(pointer, remaining_calls):
604604+ global timer
605605+ timer = None
606606+ do_sort()
607607+ return weechat.WEECHAT_RC_OK
608608+609609+def apply_config():
610610+ # Unhook all signals and hook the new ones.
611611+ for hook in hooks:
612612+ weechat.unhook(hook)
613613+ for signal in config.signals:
614614+ hooks.append(weechat.hook_signal(signal, 'on_signal', ''))
615615+616616+ if config.sort_on_config:
617617+ do_sort()
618618+619619+def on_config_changed(*args, **kwargs):
620620+ ''' Called whenever the configuration changes. '''
621621+ config.reload()
622622+ apply_config()
623623+624624+ return weechat.WEECHAT_RC_OK
625625+626626+def parse_arg(args):
627627+ if not args: return None, None
628628+629629+ result = ''
630630+ escaped = False
631631+ for i, c in enumerate(args):
632632+ if not escaped:
633633+ if c == '\\':
634634+ escaped = True
635635+ continue
636636+ elif c == ',':
637637+ return result, args[i+1:]
638638+ result += c
639639+ escaped = False
640640+ return result, None
641641+642642+def parse_args(args, max = None):
643643+ result = []
644644+ i = 0
645645+ while max is None or i < max:
646646+ arg, args = parse_arg(args)
647647+ if arg is None: break
648648+ result.append(arg)
649649+ i += 1
650650+ return result, args
651651+652652+def on_info_replace(pointer, name, arguments):
653653+ arguments, rest = parse_args(arguments, 3)
654654+ if rest or len(arguments) < 3:
655655+ log('usage: ${{info:{0},old,new,text}}'.format(name))
656656+ return ''
657657+ old, new, text = arguments
658658+659659+ return text.replace(old, new)
660660+661661+def on_info_order(pointer, name, arguments):
662662+ arguments, rest = parse_args(arguments)
663663+ if len(arguments) < 1:
664664+ log('usage: ${{info:{0},value,first,second,third,...}}'.format(name))
665665+ return ''
666666+667667+ value = arguments[0]
668668+ keys = arguments[1:]
669669+ if not keys: return '0'
670670+671671+ # Find the value in the keys (or '*' if we can't find it)
672672+ result = list_find(keys, value)
673673+ if result is None: result = list_find(keys, '*')
674674+ if result is None: result = len(keys)
675675+676676+ # Pad result with leading zero to make sure string sorting works.
677677+ width = int(math.log10(len(keys))) + 1
678678+ return '{0:0{1}}'.format(result, width)
679679+680680+681681+def on_autosort_command(data, buffer, args):
682682+ ''' Called when the autosort command is invoked. '''
683683+ try:
684684+ return call_command(buffer, ['/autosort'], args, {
685685+ ' ': command_sort,
686686+ 'sort': command_sort,
687687+ 'debug': command_debug,
688688+689689+ 'rules': {
690690+ ' ': command_rule_list,
691691+ 'list': command_rule_list,
692692+ 'add': command_rule_add,
693693+ 'insert': command_rule_insert,
694694+ 'update': command_rule_update,
695695+ 'delete': command_rule_delete,
696696+ 'move': command_rule_move,
697697+ 'swap': command_rule_swap,
698698+ },
699699+ 'helpers': {
700700+ ' ': command_helper_list,
701701+ 'list': command_helper_list,
702702+ 'set': command_helper_set,
703703+ 'delete': command_helper_delete,
704704+ 'rename': command_helper_rename,
705705+ 'swap': command_helper_swap,
706706+ },
707707+ })
708708+ except HumanReadableError as e:
709709+ log(e)
710710+ return weechat.WEECHAT_RC_ERROR
711711+712712+def add_completions(completion, words):
713713+ for word in words:
714714+ weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END)
715715+716716+def autosort_complete_rules(words, completion):
717717+ if len(words) == 0:
718718+ add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update'])
719719+ if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'):
720720+ add_completions(completion, map(str, range(len(config.rules))))
721721+ if len(words) == 2 and words[0] in ('move', 'swap'):
722722+ add_completions(completion, map(str, range(len(config.rules))))
723723+ if len(words) == 2 and words[0] in ('update'):
724724+ try:
725725+ add_completions(completion, [config.rules[int(words[1])]])
726726+ except KeyError: pass
727727+ except ValueError: pass
728728+ else:
729729+ add_completions(completion, [''])
730730+ return weechat.WEECHAT_RC_OK
731731+732732+def autosort_complete_helpers(words, completion):
733733+ if len(words) == 0:
734734+ add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap'])
735735+ elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'):
736736+ add_completions(completion, sorted(config.helpers.keys()))
737737+ elif len(words) == 2 and words[0] == 'swap':
738738+ add_completions(completion, sorted(config.helpers.keys()))
739739+ elif len(words) == 2 and words[0] == 'rename':
740740+ add_completions(completion, sorted(config.helpers.keys()))
741741+ elif len(words) == 2 and words[0] == 'set':
742742+ try:
743743+ add_completions(completion, [config.helpers[words[1]]])
744744+ except KeyError: pass
745745+ return weechat.WEECHAT_RC_OK
746746+747747+def on_autosort_complete(data, name, buffer, completion):
748748+ cmdline = weechat.buffer_get_string(buffer, "input")
749749+ cursor = weechat.buffer_get_integer(buffer, "input_pos")
750750+ prefix = cmdline[:cursor]
751751+ words = prefix.split()[1:]
752752+753753+ # If the current word isn't finished yet,
754754+ # ignore it for coming up with completion suggestions.
755755+ if prefix[-1] != ' ': words = words[:-1]
756756+757757+ if len(words) == 0:
758758+ add_completions(completion, ['debug', 'helpers', 'rules', 'sort'])
759759+ elif words[0] == 'rules':
760760+ return autosort_complete_rules(words[1:], completion)
761761+ elif words[0] == 'helpers':
762762+ return autosort_complete_helpers(words[1:], completion)
763763+ return weechat.WEECHAT_RC_OK
764764+765765+command_description = r'''{*white}# General commands{reset}
766766+767767+{*white}/autosort {brown}sort{reset}
768768+Manually trigger the buffer sorting.
769769+770770+{*white}/autosort {brown}debug{reset}
771771+Show the evaluation results of the sort rules for each buffer.
772772+773773+774774+{*white}# Sorting rule commands{reset}
775775+776776+{*white}/autosort{brown} rules list{reset}
777777+Print the list of sort rules.
778778+779779+{*white}/autosort {brown}rules add {cyan}<expression>{reset}
780780+Add a new rule at the end of the list.
781781+782782+{*white}/autosort {brown}rules insert {cyan}<index> <expression>{reset}
783783+Insert a new rule at the given index in the list.
784784+785785+{*white}/autosort {brown}rules update {cyan}<index> <expression>{reset}
786786+Update a rule in the list with a new expression.
787787+788788+{*white}/autosort {brown}rules delete {cyan}<index>
789789+Delete a rule from the list.
790790+791791+{*white}/autosort {brown}rules move {cyan}<index_from> <index_to>{reset}
792792+Move a rule from one position in the list to another.
793793+794794+{*white}/autosort {brown}rules swap {cyan}<index_a> <index_b>{reset}
795795+Swap two rules in the list
796796+797797+798798+{*white}# Helper variable commands{reset}
799799+800800+{*white}/autosort {brown}helpers list
801801+Print the list of helper variables.
802802+803803+{*white}/autosort {brown}helpers set {cyan}<name> <expression>
804804+Add or update a helper variable with the given name.
805805+806806+{*white}/autosort {brown}helpers delete {cyan}<name>
807807+Delete a helper variable.
808808+809809+{*white}/autosort {brown}helpers rename {cyan}<old_name> <new_name>
810810+Rename a helper variable.
811811+812812+{*white}/autosort {brown}helpers swap {cyan}<name_a> <name_b>
813813+Swap the expressions of two helper variables in the list.
814814+815815+816816+{*white}# Description
817817+Autosort is a weechat script to automatically keep your buffers sorted. The sort
818818+order can be customized by defining your own sort rules, but the default should
819819+be sane enough for most people. It can also group IRC channel/private buffers
820820+under their server buffer if you like.
821821+822822+{*white}# Sort rules{reset}
823823+Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the
824824+buffers based on evaluated result. Earlier rules will be considered first. Only
825825+if earlier rules produced identical results is the result of the next rule
826826+considered for sorting purposes.
827827+828828+You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will
829829+print the evaluation results of each rule for each buffer.
830830+831831+{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice
832832+versa. You will have to manually port your old rules to version 3 if you have any.
833833+834834+{*white}# Helper variables{reset}
835835+You may define helper variables for the main sort rules to keep your rules
836836+readable. They can be used in the main sort rules as variables. For example,
837837+a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the
838838+string `{cyan}${{foo}}{reset}`.
839839+840840+{*white}# Replacing substrings{reset}
841841+There is no default method for replacing text inside eval expressions. However,
842842+autosort adds a `replace` info hook that can be used inside eval expressions:
843843+ {cyan}${{info:autosort_replace,from,to,text}}{reset}
844844+845845+For example, to strip all hashes from a buffer name, you could write:
846846+ {cyan}${{info:autosort_replace,#,,${{buffer.name}}}}{reset}
847847+848848+You can escape commas and backslashes inside the arguments by prefixing them with
849849+a backslash.
850850+851851+{*white}# Automatic or manual sorting{reset}
852852+By default, autosort will automatically sort your buffer list whenever a buffer
853853+is opened, merged, unmerged or renamed. This should keep your buffers sorted in
854854+almost all situations. However, you may wish to change the list of signals that
855855+cause your buffer list to be sorted. Simply edit the `{cyan}autosort.sorting.signals{reset}`
856856+option to add or remove any signal you like.
857857+858858+If you remove all signals you can still sort your buffers manually with the
859859+`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option
860860+`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled.
861861+862862+{*white}# Recommended settings
863863+For the best visual effect, consider setting the following options:
864864+ {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset}
865865+ {*white}/set {cyan}buffers.look.indenting{reset} {brown}on{reset}
866866+867867+The first setting allows server buffers to be sorted independently, which is
868868+needed to create a hierarchical tree view of the server and channel buffers.
869869+The second one indents channel and private buffers in the buffer list of the
870870+`{*default}buffers.pl{reset}` script.
871871+872872+If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree
873873+structure with the following setting (modify to suit your need):
874874+ {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset}
875875+'''
876876+877877+command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)'
878878+879879+info_replace_description = 'Replace all occurences of `from` with `to` in the string `text`.'
880880+info_replace_arguments = 'from,to,text'
881881+882882+info_order_description = (
883883+ 'Get a zero padded index of a value in a list of possible values.'
884884+ 'If the value is not found, the index for `*` is returned.'
885885+ 'If there is no `*` in the list, the highest index + 1 is returned.'
886886+)
887887+info_order_arguments = 'value,first,second,third,...'
888888+889889+890890+if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
891891+ config = Config('autosort')
892892+893893+ colors = {
894894+ 'default': weechat.color('default'),
895895+ 'reset': weechat.color('reset'),
896896+ 'black': weechat.color('black'),
897897+ 'red': weechat.color('red'),
898898+ 'green': weechat.color('green'),
899899+ 'brown': weechat.color('brown'),
900900+ 'yellow': weechat.color('yellow'),
901901+ 'blue': weechat.color('blue'),
902902+ 'magenta': weechat.color('magenta'),
903903+ 'cyan': weechat.color('cyan'),
904904+ 'white': weechat.color('white'),
905905+ '*default': weechat.color('*default'),
906906+ '*black': weechat.color('*black'),
907907+ '*red': weechat.color('*red'),
908908+ '*green': weechat.color('*green'),
909909+ '*brown': weechat.color('*brown'),
910910+ '*yellow': weechat.color('*yellow'),
911911+ '*blue': weechat.color('*blue'),
912912+ '*magenta': weechat.color('*magenta'),
913913+ '*cyan': weechat.color('*cyan'),
914914+ '*white': weechat.color('*white'),
915915+ }
916916+917917+ weechat.hook_config('autosort.*', 'on_config_changed', '')
918918+ weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '')
919919+ weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '')
920920+ weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '')
921921+ weechat.hook_info('autosort_order', info_order_description, info_order_arguments, 'on_info_order', '')
922922+923923+ apply_config()
+400
weechat/.weechat/python/colorize_nicks.py
···11+# -*- coding: utf-8 -*-
22+#
33+# Copyright (c) 2010 by xt <xt@bash.no>
44+#
55+# This program is free software; you can redistribute it and/or modify
66+# it under the terms of the GNU General Public License as published by
77+# the Free Software Foundation; either version 3 of the License, or
88+# (at your option) any later version.
99+#
1010+# This program is distributed in the hope that it will be useful,
1111+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1212+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1313+# GNU General Public License for more details.
1414+#
1515+# You should have received a copy of the GNU General Public License
1616+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717+#
1818+1919+# This script colors nicks in IRC channels in the actual message
2020+# not just in the prefix section.
2121+#
2222+#
2323+# History:
2424+# 2018-04-06: Joey Pabalinas <joeypabalinas@gmail.com>
2525+# version 26: fix freezes with too many nicks in one line
2626+# 2018-03-18: nils_2
2727+# version 25: fix unable to run function colorize_config_reload_cb()
2828+# 2017-06-20: lbeziaud <louis.beziaud@ens-rennes.fr>
2929+# version 24: colorize utf8 nicks
3030+# 2017-03-01, arza <arza@arza.us>
3131+# version 23: don't colorize nicklist group names
3232+# 2016-05-01, Simmo Saan <simmo.saan@gmail.com>
3333+# version 22: invalidate cached colors on hash algorithm change
3434+# 2015-07-28, xt
3535+# version 21: fix problems with nicks with commas in them
3636+# 2015-04-19, xt
3737+# version 20: fix ignore of nicks in URLs
3838+# 2015-04-18, xt
3939+# version 19: new option ignore nicks in URLs
4040+# 2015-03-03, xt
4141+# version 18: iterate buffers looking for nicklists instead of servers
4242+# 2015-02-23, holomorph
4343+# version 17: fix coloring in non-channel buffers (#58)
4444+# 2014-09-17, holomorph
4545+# version 16: use weechat config facilities
4646+# clean unused, minor linting, some simplification
4747+# 2014-05-05, holomorph
4848+# version 15: fix python2-specific re.search check
4949+# 2013-01-29, nils_2
5050+# version 14: make script compatible with Python 3.x
5151+# 2012-10-19, ldvx
5252+# version 13: Iterate over every word to prevent incorrect colorization of
5353+# nicks. Added option greedy_matching.
5454+# 2012-04-28, ldvx
5555+# version 12: added ignore_tags to avoid colorizing nicks if tags are present
5656+# 2012-01-14, nesthib
5757+# version 11: input_text_display hook and modifier to colorize nicks in input bar
5858+# 2010-12-22, xt
5959+# version 10: hook config option for updating blacklist
6060+# 2010-12-20, xt
6161+# version 0.9: hook new config option for weechat 0.3.4
6262+# 2010-11-01, nils_2
6363+# version 0.8: hook_modifier() added to communicate with rainbow_text
6464+# 2010-10-01, xt
6565+# version 0.7: changes to support non-irc-plugins
6666+# 2010-07-29, xt
6767+# version 0.6: compile regexp as per patch from Chris quigybo@hotmail.com
6868+# 2010-07-19, xt
6969+# version 0.5: fix bug with incorrect coloring of own nick
7070+# 2010-06-02, xt
7171+# version 0.4: update to reflect API changes
7272+# 2010-03-26, xt
7373+# version 0.3: fix error with exception
7474+# 2010-03-24, xt
7575+# version 0.2: use ignore_channels when populating to increase performance.
7676+# 2010-02-03, xt
7777+# version 0.1: initial (based on ruby script by dominikh)
7878+#
7979+# Known issues: nicks will not get colorized if they begin with a character
8080+# such as ~ (which some irc networks do happen to accept)
8181+8282+import weechat
8383+import re
8484+w = weechat
8585+8686+SCRIPT_NAME = "colorize_nicks"
8787+SCRIPT_AUTHOR = "xt <xt@bash.no>"
8888+SCRIPT_VERSION = "26"
8989+SCRIPT_LICENSE = "GPL"
9090+SCRIPT_DESC = "Use the weechat nick colors in the chat area"
9191+9292+# Based on the recommendations in RFC 7613. A valid nick is composed
9393+# of anything but " ,*?.!@".
9494+VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@]+)'
9595+valid_nick_re = re.compile(VALID_NICK)
9696+ignore_channels = []
9797+ignore_nicks = []
9898+9999+# Dict with every nick on every channel with its color as lookup value
100100+colored_nicks = {}
101101+102102+CONFIG_FILE_NAME = "colorize_nicks"
103103+104104+# config file and options
105105+colorize_config_file = ""
106106+colorize_config_option = {}
107107+108108+def colorize_config_init():
109109+ '''
110110+ Initialization of configuration file.
111111+ Sections: look.
112112+ '''
113113+ global colorize_config_file, colorize_config_option
114114+ colorize_config_file = weechat.config_new(CONFIG_FILE_NAME,
115115+ "", "")
116116+ if colorize_config_file == "":
117117+ return
118118+119119+ # section "look"
120120+ section_look = weechat.config_new_section(
121121+ colorize_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "")
122122+ if section_look == "":
123123+ weechat.config_free(colorize_config_file)
124124+ return
125125+ colorize_config_option["blacklist_channels"] = weechat.config_new_option(
126126+ colorize_config_file, section_look, "blacklist_channels",
127127+ "string", "Comma separated list of channels", "", 0, 0,
128128+ "", "", 0, "", "", "", "", "", "")
129129+ colorize_config_option["blacklist_nicks"] = weechat.config_new_option(
130130+ colorize_config_file, section_look, "blacklist_nicks",
131131+ "string", "Comma separated list of nicks", "", 0, 0,
132132+ "so,root", "so,root", 0, "", "", "", "", "", "")
133133+ colorize_config_option["min_nick_length"] = weechat.config_new_option(
134134+ colorize_config_file, section_look, "min_nick_length",
135135+ "integer", "Minimum length nick to colorize", "",
136136+ 2, 20, "", "", 0, "", "", "", "", "", "")
137137+ colorize_config_option["colorize_input"] = weechat.config_new_option(
138138+ colorize_config_file, section_look, "colorize_input",
139139+ "boolean", "Whether to colorize input", "", 0,
140140+ 0, "off", "off", 0, "", "", "", "", "", "")
141141+ colorize_config_option["ignore_tags"] = weechat.config_new_option(
142142+ colorize_config_file, section_look, "ignore_tags",
143143+ "string", "Comma separated list of tags to ignore; i.e. irc_join,irc_part,irc_quit", "", 0, 0,
144144+ "", "", 0, "", "", "", "", "", "")
145145+ colorize_config_option["greedy_matching"] = weechat.config_new_option(
146146+ colorize_config_file, section_look, "greedy_matching",
147147+ "boolean", "If off, then use lazy matching instead", "", 0,
148148+ 0, "on", "on", 0, "", "", "", "", "", "")
149149+ colorize_config_option["match_limit"] = weechat.config_new_option(
150150+ colorize_config_file, section_look, "match_limit",
151151+ "integer", "Fall back to lazy matching if greedy matches exceeds this number", "",
152152+ 20, 1000, "", "", 0, "", "", "", "", "", "")
153153+ colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option(
154154+ colorize_config_file, section_look, "ignore_nicks_in_urls",
155155+ "boolean", "If on, don't colorize nicks inside URLs", "", 0,
156156+ 0, "off", "off", 0, "", "", "", "", "", "")
157157+158158+def colorize_config_read():
159159+ ''' Read configuration file. '''
160160+ global colorize_config_file
161161+ return weechat.config_read(colorize_config_file)
162162+163163+def colorize_nick_color(nick, my_nick):
164164+ ''' Retrieve nick color from weechat. '''
165165+ if nick == my_nick:
166166+ return w.color(w.config_string(w.config_get('weechat.color.chat_nick_self')))
167167+ else:
168168+ return w.info_get('irc_nick_color', nick)
169169+170170+def colorize_cb(data, modifier, modifier_data, line):
171171+ ''' Callback that does the colorizing, and returns new line if changed '''
172172+173173+ global ignore_nicks, ignore_channels, colored_nicks
174174+175175+176176+ full_name = modifier_data.split(';')[1]
177177+ channel = '.'.join(full_name.split('.')[1:])
178178+179179+ buffer = w.buffer_search('', full_name)
180180+ # Check if buffer has colorized nicks
181181+ if buffer not in colored_nicks:
182182+ return line
183183+184184+ if channel and channel in ignore_channels:
185185+ return line
186186+187187+ min_length = w.config_integer(colorize_config_option['min_nick_length'])
188188+ reset = w.color('reset')
189189+190190+ # Don't colorize if the ignored tag is present in message
191191+ tags_line = modifier_data.rsplit(';')
192192+ if len(tags_line) >= 3:
193193+ tags_line = tags_line[2].split(',')
194194+ for i in w.config_string(colorize_config_option['ignore_tags']).split(','):
195195+ if i in tags_line:
196196+ return line
197197+198198+ for words in valid_nick_re.findall(line):
199199+ nick = words[1]
200200+ # Check that nick is not ignored and longer than minimum length
201201+ if len(nick) < min_length or nick in ignore_nicks:
202202+ continue
203203+204204+ # If the matched word is not a known nick, we try to match the
205205+ # word without its first or last character (if not a letter).
206206+ # This is necessary as "foo:" is a valid nick, which could be
207207+ # adressed as "foo::".
208208+ if nick not in colored_nicks[buffer]:
209209+ if not nick[-1].isalpha() and not nick[0].isalpha():
210210+ if nick[1:-1] in colored_nicks[buffer]:
211211+ nick = nick[1:-1]
212212+ elif not nick[0].isalpha():
213213+ if nick[1:] in colored_nicks[buffer]:
214214+ nick = nick[1:]
215215+ elif not nick[-1].isalpha():
216216+ if nick[:-1] in colored_nicks[buffer]:
217217+ nick = nick[:-1]
218218+219219+ # Check that nick is in the dictionary colored_nicks
220220+ if nick in colored_nicks[buffer]:
221221+ nick_color = colored_nicks[buffer][nick]
222222+223223+ try:
224224+ # Let's use greedy matching. Will check against every word in a line.
225225+ if w.config_boolean(colorize_config_option['greedy_matching']):
226226+ cnt = 0
227227+ limit = w.config_integer(colorize_config_option['match_limit'])
228228+229229+ for word in line.split():
230230+ cnt += 1
231231+ assert cnt < limit
232232+ # if cnt > limit:
233233+ # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.');
234234+235235+ if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \
236236+ word.startswith(('http://', 'https://')):
237237+ continue
238238+239239+ if nick in word:
240240+ # Is there a nick that contains nick and has a greater lenght?
241241+ # If so let's save that nick into var biggest_nick
242242+ biggest_nick = ""
243243+ for i in colored_nicks[buffer]:
244244+ cnt += 1
245245+ assert cnt < limit
246246+247247+ if nick in i and nick != i and len(i) > len(nick):
248248+ if i in word:
249249+ # If a nick with greater len is found, and that word
250250+ # also happens to be in word, then let's save this nick
251251+ biggest_nick = i
252252+ # If there's a nick with greater len, then let's skip this
253253+ # As we will have the chance to colorize when biggest_nick
254254+ # iterates being nick.
255255+ if len(biggest_nick) > 0 and biggest_nick in word:
256256+ pass
257257+ elif len(word) < len(biggest_nick) or len(biggest_nick) == 0:
258258+ new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset))
259259+ line = line.replace(word, new_word)
260260+261261+ # Switch to lazy matching
262262+ else:
263263+ raise AssertionError
264264+265265+ except AssertionError:
266266+ # Let's use lazy matching for nick
267267+ nick_color = colored_nicks[buffer][nick]
268268+ # The two .? are in case somebody writes "nick:", "nick,", etc
269269+ # to address somebody
270270+ regex = r"(\A|\s).?(%s).?(\Z|\s)" % re.escape(nick)
271271+ match = re.search(regex, line)
272272+ if match is not None:
273273+ new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):]
274274+ line = new_line
275275+276276+ return line
277277+278278+def colorize_input_cb(data, modifier, modifier_data, line):
279279+ ''' Callback that does the colorizing in input '''
280280+281281+ global ignore_nicks, ignore_channels, colored_nicks
282282+283283+ min_length = w.config_integer(colorize_config_option['min_nick_length'])
284284+285285+ if not w.config_boolean(colorize_config_option['colorize_input']):
286286+ return line
287287+288288+ buffer = w.current_buffer()
289289+ # Check if buffer has colorized nicks
290290+ if buffer not in colored_nicks:
291291+ return line
292292+293293+ channel = w.buffer_get_string(buffer, 'name')
294294+ if channel and channel in ignore_channels:
295295+ return line
296296+297297+ reset = w.color('reset')
298298+299299+ for words in valid_nick_re.findall(line):
300300+ nick = words[1]
301301+ # Check that nick is not ignored and longer than minimum length
302302+ if len(nick) < min_length or nick in ignore_nicks:
303303+ continue
304304+ if nick in colored_nicks[buffer]:
305305+ nick_color = colored_nicks[buffer][nick]
306306+ line = line.replace(nick, '%s%s%s' % (nick_color, nick, reset))
307307+308308+ return line
309309+310310+def populate_nicks(*args):
311311+ ''' Fills entire dict with all nicks weechat can see and what color it has
312312+ assigned to it. '''
313313+ global colored_nicks
314314+315315+ colored_nicks = {}
316316+317317+ buffers = w.infolist_get('buffer', '', '')
318318+ while w.infolist_next(buffers):
319319+ buffer_ptr = w.infolist_pointer(buffers, 'pointer')
320320+ my_nick = w.buffer_get_string(buffer_ptr, 'localvar_nick')
321321+ nicklist = w.infolist_get('nicklist', buffer_ptr, '')
322322+ while w.infolist_next(nicklist):
323323+ if buffer_ptr not in colored_nicks:
324324+ colored_nicks[buffer_ptr] = {}
325325+326326+ if w.infolist_string(nicklist, 'type') != 'nick':
327327+ continue
328328+329329+ nick = w.infolist_string(nicklist, 'name')
330330+ nick_color = colorize_nick_color(nick, my_nick)
331331+332332+ colored_nicks[buffer_ptr][nick] = nick_color
333333+334334+ w.infolist_free(nicklist)
335335+336336+ w.infolist_free(buffers)
337337+338338+ return w.WEECHAT_RC_OK
339339+340340+def add_nick(data, signal, type_data):
341341+ ''' Add nick to dict of colored nicks '''
342342+ global colored_nicks
343343+344344+ # Nicks can have , in them in some protocols
345345+ splitted = type_data.split(',')
346346+ pointer = splitted[0]
347347+ nick = ",".join(splitted[1:])
348348+ if pointer not in colored_nicks:
349349+ colored_nicks[pointer] = {}
350350+351351+ my_nick = w.buffer_get_string(pointer, 'localvar_nick')
352352+ nick_color = colorize_nick_color(nick, my_nick)
353353+354354+ colored_nicks[pointer][nick] = nick_color
355355+356356+ return w.WEECHAT_RC_OK
357357+358358+def remove_nick(data, signal, type_data):
359359+ ''' Remove nick from dict with colored nicks '''
360360+ global colored_nicks
361361+362362+ # Nicks can have , in them in some protocols
363363+ splitted = type_data.split(',')
364364+ pointer = splitted[0]
365365+ nick = ",".join(splitted[1:])
366366+367367+ if pointer in colored_nicks and nick in colored_nicks[pointer]:
368368+ del colored_nicks[pointer][nick]
369369+370370+ return w.WEECHAT_RC_OK
371371+372372+def update_blacklist(*args):
373373+ ''' Set the blacklist for channels and nicks. '''
374374+ global ignore_channels, ignore_nicks
375375+ ignore_channels = w.config_string(colorize_config_option['blacklist_channels']).split(',')
376376+ ignore_nicks = w.config_string(colorize_config_option['blacklist_nicks']).split(',')
377377+ return w.WEECHAT_RC_OK
378378+379379+if __name__ == "__main__":
380380+ if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
381381+ SCRIPT_DESC, "", ""):
382382+ colorize_config_init()
383383+ colorize_config_read()
384384+385385+ # Run once to get data ready
386386+ update_blacklist()
387387+ populate_nicks()
388388+389389+ w.hook_signal('nicklist_nick_added', 'add_nick', '')
390390+ w.hook_signal('nicklist_nick_removed', 'remove_nick', '')
391391+ w.hook_modifier('weechat_print', 'colorize_cb', '')
392392+ # Hook config for changing colors
393393+ w.hook_config('weechat.color.chat_nick_colors', 'populate_nicks', '')
394394+ w.hook_config('weechat.look.nick_color_hash', 'populate_nicks', '')
395395+ # Hook for working togheter with other scripts (like colorize_lines)
396396+ w.hook_modifier('colorize_nicks', 'colorize_cb', '')
397397+ # Hook for modifying input
398398+ w.hook_modifier('250|input_text_display', 'colorize_input_cb', '')
399399+ # Hook for updating blacklist (this could be improved to use fnmatch)
400400+ weechat.hook_config('%s.look.blacklist*' % SCRIPT_NAME, 'update_blacklist', '')
+561
weechat/.weechat/python/go.py
···11+# -*- coding: utf-8 -*-
22+#
33+# Copyright (C) 2009-2014 Sébastien Helleu <flashcode@flashtux.org>
44+# Copyright (C) 2010 m4v <lambdae2@gmail.com>
55+# Copyright (C) 2011 stfn <stfnmd@googlemail.com>
66+#
77+# This program is free software; you can redistribute it and/or modify
88+# it under the terms of the GNU General Public License as published by
99+# the Free Software Foundation; either version 3 of the License, or
1010+# (at your option) any later version.
1111+#
1212+# This program is distributed in the hope that it will be useful,
1313+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1414+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1515+# GNU General Public License for more details.
1616+#
1717+# You should have received a copy of the GNU General Public License
1818+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1919+#
2020+2121+#
2222+# History:
2323+#
2424+# 2017-04-01, Sébastien Helleu <flashcode@flashtux.org>:
2525+# version 2.5: add option "buffer_number"
2626+# 2017-03-02, Sébastien Helleu <flashcode@flashtux.org>:
2727+# version 2.4: fix syntax and indentation error
2828+# 2017-02-25, Simmo Saan <simmo.saan@gmail.com>
2929+# version 2.3: fix fuzzy search breaking buffer number search display
3030+# 2016-01-28, ylambda <ylambda@koalabeast.com>
3131+# version 2.2: add option "fuzzy_search"
3232+# 2015-11-12, nils_2 <weechatter@arcor.de>
3333+# version 2.1: fix problem with buffer short_name "weechat", using option
3434+# "use_core_instead_weechat", see:
3535+# https://github.com/weechat/weechat/issues/574
3636+# 2014-05-12, Sébastien Helleu <flashcode@flashtux.org>:
3737+# version 2.0: add help on options, replace option "sort_by_activity" by
3838+# "sort" (add sort by name and first match at beginning of
3939+# name and by number), PEP8 compliance
4040+# 2012-11-26, Nei <anti.teamidiot.de>
4141+# version 1.9: add auto_jump option to automatically go to buffer when it
4242+# is uniquely selected
4343+# 2012-09-17, Sébastien Helleu <flashcode@flashtux.org>:
4444+# version 1.8: fix jump to non-active merged buffers (jump with buffer name
4545+# instead of number)
4646+# 2012-01-03 nils_2 <weechatter@arcor.de>
4747+# version 1.7: add option "use_core_instead_weechat"
4848+# 2012-01-03, Sébastien Helleu <flashcode@flashtux.org>:
4949+# version 1.6: make script compatible with Python 3.x
5050+# 2011-08-24, stfn <stfnmd@googlemail.com>:
5151+# version 1.5: /go with name argument jumps directly to buffer
5252+# Remember cursor position in buffer input
5353+# 2011-05-31, Elián Hanisch <lambdae2@gmail.com>:
5454+# version 1.4: Sort list of buffers by activity.
5555+# 2011-04-25, Sébastien Helleu <flashcode@flashtux.org>:
5656+# version 1.3: add info "go_running" (used by script input_lock.rb)
5757+# 2010-11-01, Sébastien Helleu <flashcode@flashtux.org>:
5858+# version 1.2: use high priority for hooks to prevent conflict with other
5959+# plugins/scripts (WeeChat >= 0.3.4 only)
6060+# 2010-03-25, Elián Hanisch <lambdae2@gmail.com>:
6161+# version 1.1: use a space to match the end of a string
6262+# 2009-11-16, Sébastien Helleu <flashcode@flashtux.org>:
6363+# version 1.0: add new option to display short names
6464+# 2009-06-15, Sébastien Helleu <flashcode@flashtux.org>:
6565+# version 0.9: fix typo in /help go with command /key
6666+# 2009-05-16, Sébastien Helleu <flashcode@flashtux.org>:
6767+# version 0.8: search buffer by number, fix bug when window is split
6868+# 2009-05-03, Sébastien Helleu <flashcode@flashtux.org>:
6969+# version 0.7: eat tab key (do not complete input, just move buffer
7070+# pointer)
7171+# 2009-05-02, Sébastien Helleu <flashcode@flashtux.org>:
7272+# version 0.6: sync with last API changes
7373+# 2009-03-22, Sébastien Helleu <flashcode@flashtux.org>:
7474+# version 0.5: update modifier signal name for input text display,
7575+# fix arguments for function string_remove_color
7676+# 2009-02-18, Sébastien Helleu <flashcode@flashtux.org>:
7777+# version 0.4: do not hook command and init options if register failed
7878+# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
7979+# version 0.3: case insensitive search for buffers names
8080+# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
8181+# version 0.2: add help about Tab key
8282+# 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
8383+# version 0.1: initial release
8484+#
8585+8686+"""
8787+Quick jump to buffers.
8888+(this script requires WeeChat 0.3.0 or newer)
8989+"""
9090+9191+from __future__ import print_function
9292+9393+SCRIPT_NAME = 'go'
9494+SCRIPT_AUTHOR = 'Sébastien Helleu <flashcode@flashtux.org>'
9595+SCRIPT_VERSION = '2.5'
9696+SCRIPT_LICENSE = 'GPL3'
9797+SCRIPT_DESC = 'Quick jump to buffers'
9898+9999+SCRIPT_COMMAND = 'go'
100100+101101+IMPORT_OK = True
102102+103103+try:
104104+ import weechat
105105+except ImportError:
106106+ print('This script must be run under WeeChat.')
107107+ print('Get WeeChat now at: http://www.weechat.org/')
108108+ IMPORT_OK = False
109109+110110+import re
111111+112112+# script options
113113+SETTINGS = {
114114+ 'color_number': (
115115+ 'yellow,magenta',
116116+ 'color for buffer number (not selected)'),
117117+ 'color_number_selected': (
118118+ 'yellow,red',
119119+ 'color for selected buffer number'),
120120+ 'color_name': (
121121+ 'black,cyan',
122122+ 'color for buffer name (not selected)'),
123123+ 'color_name_selected': (
124124+ 'black,brown',
125125+ 'color for a selected buffer name'),
126126+ 'color_name_highlight': (
127127+ 'red,cyan',
128128+ 'color for highlight in buffer name (not selected)'),
129129+ 'color_name_highlight_selected': (
130130+ 'red,brown',
131131+ 'color for highlight in a selected buffer name'),
132132+ 'message': (
133133+ 'Go to: ',
134134+ 'message to display before list of buffers'),
135135+ 'short_name': (
136136+ 'off',
137137+ 'display and search in short names instead of buffer name'),
138138+ 'sort': (
139139+ 'number,beginning',
140140+ 'comma-separated list of keys to sort buffers '
141141+ '(the order is important, sorts are performed in the given order): '
142142+ 'name = sort by name (or short name), ',
143143+ 'hotlist = sort by hotlist order, '
144144+ 'number = first match a buffer number before digits in name, '
145145+ 'beginning = first match at beginning of names (or short names); '
146146+ 'the default sort of buffers is by numbers'),
147147+ 'use_core_instead_weechat': (
148148+ 'off',
149149+ 'use name "core" instead of "weechat" for core buffer'),
150150+ 'auto_jump': (
151151+ 'off',
152152+ 'automatically jump to buffer when it is uniquely selected'),
153153+ 'fuzzy_search': (
154154+ 'off',
155155+ 'search buffer matches using approximation'),
156156+ 'buffer_number': (
157157+ 'on',
158158+ 'display buffer number'),
159159+}
160160+161161+# hooks management
162162+HOOK_COMMAND_RUN = {
163163+ 'input': ('/input *', 'go_command_run_input'),
164164+ 'buffer': ('/buffer *', 'go_command_run_buffer'),
165165+ 'window': ('/window *', 'go_command_run_window'),
166166+}
167167+hooks = {}
168168+169169+# input before command /go (we'll restore it later)
170170+saved_input = ''
171171+saved_input_pos = 0
172172+173173+# last user input (if changed, we'll update list of matching buffers)
174174+old_input = None
175175+176176+# matching buffers
177177+buffers = []
178178+buffers_pos = 0
179179+180180+181181+def go_option_enabled(option):
182182+ """Checks if a boolean script option is enabled or not."""
183183+ return weechat.config_string_to_boolean(weechat.config_get_plugin(option))
184184+185185+186186+def go_info_running(data, info_name, arguments):
187187+ """Returns "1" if go is running, otherwise "0"."""
188188+ return '1' if 'modifier' in hooks else '0'
189189+190190+191191+def go_unhook_one(hook):
192192+ """Unhook something hooked by this script."""
193193+ global hooks
194194+ if hook in hooks:
195195+ weechat.unhook(hooks[hook])
196196+ del hooks[hook]
197197+198198+199199+def go_unhook_all():
200200+ """Unhook all."""
201201+ go_unhook_one('modifier')
202202+ for hook in HOOK_COMMAND_RUN:
203203+ go_unhook_one(hook)
204204+205205+206206+def go_hook_all():
207207+ """Hook command_run and modifier."""
208208+ global hooks
209209+ priority = ''
210210+ version = weechat.info_get('version_number', '') or 0
211211+ # use high priority for hook to prevent conflict with other plugins/scripts
212212+ # (WeeChat >= 0.3.4 only)
213213+ if int(version) >= 0x00030400:
214214+ priority = '2000|'
215215+ for hook, value in HOOK_COMMAND_RUN.items():
216216+ if hook not in hooks:
217217+ hooks[hook] = weechat.hook_command_run(
218218+ '%s%s' % (priority, value[0]),
219219+ value[1], '')
220220+ if 'modifier' not in hooks:
221221+ hooks['modifier'] = weechat.hook_modifier(
222222+ 'input_text_display_with_cursor', 'go_input_modifier', '')
223223+224224+225225+def go_start(buf):
226226+ """Start go on buffer."""
227227+ global saved_input, saved_input_pos, old_input, buffers_pos
228228+ go_hook_all()
229229+ saved_input = weechat.buffer_get_string(buf, 'input')
230230+ saved_input_pos = weechat.buffer_get_integer(buf, 'input_pos')
231231+ weechat.buffer_set(buf, 'input', '')
232232+ old_input = None
233233+ buffers_pos = 0
234234+235235+236236+def go_end(buf):
237237+ """End go on buffer."""
238238+ global saved_input, saved_input_pos, old_input
239239+ go_unhook_all()
240240+ weechat.buffer_set(buf, 'input', saved_input)
241241+ weechat.buffer_set(buf, 'input_pos', str(saved_input_pos))
242242+ old_input = None
243243+244244+245245+def go_match_beginning(buf, string):
246246+ """Check if a string matches the beginning of buffer name/short name."""
247247+ if not string:
248248+ return False
249249+ esc_str = re.escape(string)
250250+ if re.search(r'^#?' + esc_str, buf['name']) \
251251+ or re.search(r'^#?' + esc_str, buf['short_name']):
252252+ return True
253253+ return False
254254+255255+256256+def go_match_fuzzy(name, string):
257257+ """Check if string matches name using approximation."""
258258+ if not string:
259259+ return False
260260+261261+ name_len = len(name)
262262+ string_len = len(string)
263263+264264+ if string_len > name_len:
265265+ return False
266266+ if name_len == string_len:
267267+ return name == string
268268+269269+ # Attempt to match all chars somewhere in name
270270+ prev_index = -1
271271+ for i, char in enumerate(string):
272272+ index = name.find(char, prev_index+1)
273273+ if index == -1:
274274+ return False
275275+ prev_index = index
276276+ return True
277277+278278+279279+def go_now(buf, args):
280280+ """Go to buffer specified by args."""
281281+ listbuf = go_matching_buffers(args)
282282+ if not listbuf:
283283+ return
284284+285285+ # prefer buffer that matches at beginning (if option is enabled)
286286+ if 'beginning' in weechat.config_get_plugin('sort').split(','):
287287+ for index in range(len(listbuf)):
288288+ if go_match_beginning(listbuf[index], args):
289289+ weechat.command(buf,
290290+ '/buffer ' + str(listbuf[index]['full_name']))
291291+ return
292292+293293+ # jump to first buffer in matching buffers by default
294294+ weechat.command(buf, '/buffer ' + str(listbuf[0]['full_name']))
295295+296296+297297+def go_cmd(data, buf, args):
298298+ """Command "/go": just hook what we need."""
299299+ global hooks
300300+ if args:
301301+ go_now(buf, args)
302302+ elif 'modifier' in hooks:
303303+ go_end(buf)
304304+ else:
305305+ go_start(buf)
306306+ return weechat.WEECHAT_RC_OK
307307+308308+309309+def go_matching_buffers(strinput):
310310+ """Return a list with buffers matching user input."""
311311+ global buffers_pos
312312+ listbuf = []
313313+ if len(strinput) == 0:
314314+ buffers_pos = 0
315315+ strinput = strinput.lower()
316316+ infolist = weechat.infolist_get('buffer', '', '')
317317+ while weechat.infolist_next(infolist):
318318+ short_name = weechat.infolist_string(infolist, 'short_name')
319319+ if go_option_enabled('short_name'):
320320+ name = weechat.infolist_string(infolist, 'short_name')
321321+ else:
322322+ name = weechat.infolist_string(infolist, 'name')
323323+ if name == 'weechat' \
324324+ and go_option_enabled('use_core_instead_weechat') \
325325+ and weechat.infolist_string(infolist, 'plugin_name') == 'core':
326326+ name = 'core'
327327+ number = weechat.infolist_integer(infolist, 'number')
328328+ full_name = weechat.infolist_string(infolist, 'full_name')
329329+ if not full_name:
330330+ full_name = '%s.%s' % (
331331+ weechat.infolist_string(infolist, 'plugin_name'),
332332+ weechat.infolist_string(infolist, 'name'))
333333+ pointer = weechat.infolist_pointer(infolist, 'pointer')
334334+ matching = name.lower().find(strinput) >= 0
335335+ if not matching and strinput[-1] == ' ':
336336+ matching = name.lower().endswith(strinput.strip())
337337+ if not matching and go_option_enabled('fuzzy_search'):
338338+ matching = go_match_fuzzy(name.lower(), strinput)
339339+ if not matching and strinput.isdigit():
340340+ matching = str(number).startswith(strinput)
341341+ if len(strinput) == 0 or matching:
342342+ listbuf.append({
343343+ 'number': number,
344344+ 'short_name': short_name,
345345+ 'name': name,
346346+ 'full_name': full_name,
347347+ 'pointer': pointer,
348348+ })
349349+ weechat.infolist_free(infolist)
350350+351351+ # sort buffers
352352+ hotlist = []
353353+ infolist = weechat.infolist_get('hotlist', '', '')
354354+ while weechat.infolist_next(infolist):
355355+ hotlist.append(
356356+ weechat.infolist_pointer(infolist, 'buffer_pointer'))
357357+ weechat.infolist_free(infolist)
358358+ last_index_hotlist = len(hotlist)
359359+360360+ def _sort_name(buf):
361361+ """Sort buffers by name (or short name)."""
362362+ return buf['name']
363363+364364+ def _sort_hotlist(buf):
365365+ """Sort buffers by hotlist order."""
366366+ try:
367367+ return hotlist.index(buf['pointer'])
368368+ except ValueError:
369369+ # not in hotlist, always last.
370370+ return last_index_hotlist
371371+372372+ def _sort_match_number(buf):
373373+ """Sort buffers by match on number."""
374374+ return 0 if str(buf['number']) == strinput else 1
375375+376376+ def _sort_match_beginning(buf):
377377+ """Sort buffers by match at beginning."""
378378+ return 0 if go_match_beginning(buf, strinput) else 1
379379+380380+ funcs = {
381381+ 'name': _sort_name,
382382+ 'hotlist': _sort_hotlist,
383383+ 'number': _sort_match_number,
384384+ 'beginning': _sort_match_beginning,
385385+ }
386386+387387+ for key in weechat.config_get_plugin('sort').split(','):
388388+ if key in funcs:
389389+ listbuf = sorted(listbuf, key=funcs[key])
390390+391391+ if not strinput:
392392+ index = [i for i, buf in enumerate(listbuf)
393393+ if buf['pointer'] == weechat.current_buffer()]
394394+ if index:
395395+ buffers_pos = index[0]
396396+397397+ return listbuf
398398+399399+400400+def go_buffers_to_string(listbuf, pos, strinput):
401401+ """Return string built with list of buffers found (matching user input)."""
402402+ string = ''
403403+ strinput = strinput.lower()
404404+ for i in range(len(listbuf)):
405405+ selected = '_selected' if i == pos else ''
406406+ buffer_name = listbuf[i]['name']
407407+ index = buffer_name.lower().find(strinput)
408408+ if index >= 0:
409409+ index2 = index + len(strinput)
410410+ name = '%s%s%s%s%s' % (
411411+ buffer_name[:index],
412412+ weechat.color(weechat.config_get_plugin(
413413+ 'color_name_highlight' + selected)),
414414+ buffer_name[index:index2],
415415+ weechat.color(weechat.config_get_plugin(
416416+ 'color_name' + selected)),
417417+ buffer_name[index2:])
418418+ elif go_option_enabled("fuzzy_search") and \
419419+ go_match_fuzzy(buffer_name.lower(), strinput):
420420+ name = ""
421421+ prev_index = -1
422422+ for char in strinput.lower():
423423+ index = buffer_name.lower().find(char, prev_index+1)
424424+ if prev_index < 0:
425425+ name += buffer_name[:index]
426426+ name += weechat.color(weechat.config_get_plugin(
427427+ 'color_name_highlight' + selected))
428428+ if prev_index >= 0 and index > prev_index+1:
429429+ name += weechat.color(weechat.config_get_plugin(
430430+ 'color_name' + selected))
431431+ name += buffer_name[prev_index+1:index]
432432+ name += weechat.color(weechat.config_get_plugin(
433433+ 'color_name_highlight' + selected))
434434+ name += buffer_name[index]
435435+ prev_index = index
436436+437437+ name += weechat.color(weechat.config_get_plugin(
438438+ 'color_name' + selected))
439439+ name += buffer_name[prev_index+1:]
440440+ else:
441441+ name = buffer_name
442442+ string += ' '
443443+ if go_option_enabled('buffer_number'):
444444+ string += '%s%s' % (
445445+ weechat.color(weechat.config_get_plugin(
446446+ 'color_number' + selected)),
447447+ str(listbuf[i]['number']))
448448+ string += '%s%s%s' % (
449449+ weechat.color(weechat.config_get_plugin(
450450+ 'color_name' + selected)),
451451+ name,
452452+ weechat.color('reset'))
453453+ return ' ' + string if string else ''
454454+455455+456456+def go_input_modifier(data, modifier, modifier_data, string):
457457+ """This modifier is called when input text item is built by WeeChat.
458458+459459+ This is commonly called after changes in input or cursor move: it builds
460460+ a new input with prefix ("Go to:"), and suffix (list of buffers found).
461461+ """
462462+ global old_input, buffers, buffers_pos
463463+ if modifier_data != weechat.current_buffer():
464464+ return ''
465465+ names = ''
466466+ new_input = weechat.string_remove_color(string, '')
467467+ new_input = new_input.lstrip()
468468+ if old_input is None or new_input != old_input:
469469+ old_buffers = buffers
470470+ buffers = go_matching_buffers(new_input)
471471+ if buffers != old_buffers and len(new_input) > 0:
472472+ if len(buffers) == 1 and go_option_enabled('auto_jump'):
473473+ weechat.command(modifier_data, '/wait 1ms /input return')
474474+ buffers_pos = 0
475475+ old_input = new_input
476476+ names = go_buffers_to_string(buffers, buffers_pos, new_input.strip())
477477+ return weechat.config_get_plugin('message') + string + names
478478+479479+480480+def go_command_run_input(data, buf, command):
481481+ """Function called when a command "/input xxx" is run."""
482482+ global buffers, buffers_pos
483483+ if command == '/input search_text' or command.find('/input jump') == 0:
484484+ # search text or jump to another buffer is forbidden now
485485+ return weechat.WEECHAT_RC_OK_EAT
486486+ elif command == '/input complete_next':
487487+ # choose next buffer in list
488488+ buffers_pos += 1
489489+ if buffers_pos >= len(buffers):
490490+ buffers_pos = 0
491491+ weechat.hook_signal_send('input_text_changed',
492492+ weechat.WEECHAT_HOOK_SIGNAL_STRING, '')
493493+ return weechat.WEECHAT_RC_OK_EAT
494494+ elif command == '/input complete_previous':
495495+ # choose previous buffer in list
496496+ buffers_pos -= 1
497497+ if buffers_pos < 0:
498498+ buffers_pos = len(buffers) - 1
499499+ weechat.hook_signal_send('input_text_changed',
500500+ weechat.WEECHAT_HOOK_SIGNAL_STRING, '')
501501+ return weechat.WEECHAT_RC_OK_EAT
502502+ elif command == '/input return':
503503+ # switch to selected buffer (if any)
504504+ go_end(buf)
505505+ if len(buffers) > 0:
506506+ weechat.command(
507507+ buf, '/buffer ' + str(buffers[buffers_pos]['full_name']))
508508+ return weechat.WEECHAT_RC_OK_EAT
509509+ return weechat.WEECHAT_RC_OK
510510+511511+512512+def go_command_run_buffer(data, buf, command):
513513+ """Function called when a command "/buffer xxx" is run."""
514514+ return weechat.WEECHAT_RC_OK_EAT
515515+516516+517517+def go_command_run_window(data, buf, command):
518518+ """Function called when a command "/window xxx" is run."""
519519+ return weechat.WEECHAT_RC_OK_EAT
520520+521521+522522+def go_unload_script():
523523+ """Function called when script is unloaded."""
524524+ go_unhook_all()
525525+ return weechat.WEECHAT_RC_OK
526526+527527+528528+def go_main():
529529+ """Entry point."""
530530+ if not weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
531531+ SCRIPT_LICENSE, SCRIPT_DESC,
532532+ 'go_unload_script', ''):
533533+ return
534534+ weechat.hook_command(
535535+ SCRIPT_COMMAND,
536536+ 'Quick jump to buffers', '[name]',
537537+ 'name: directly jump to buffer by name (without argument, list is '
538538+ 'displayed)\n\n'
539539+ 'You can bind command to a key, for example:\n'
540540+ ' /key bind meta-g /go\n\n'
541541+ 'You can use completion key (commonly Tab and shift-Tab) to select '
542542+ 'next/previous buffer in list.',
543543+ '%(buffers_names)',
544544+ 'go_cmd', '')
545545+546546+ # set default settings
547547+ version = weechat.info_get('version_number', '') or 0
548548+ for option, value in SETTINGS.items():
549549+ if not weechat.config_is_set_plugin(option):
550550+ weechat.config_set_plugin(option, value[0])
551551+ if int(version) >= 0x00030500:
552552+ weechat.config_set_desc_plugin(
553553+ option, '%s (default: "%s")' % (value[1], value[0]))
554554+ weechat.hook_info('go_running',
555555+ 'Return "1" if go is running, otherwise "0"',
556556+ '',
557557+ 'go_info_running', '')
558558+559559+560560+if __name__ == "__main__" and IMPORT_OK:
561561+ go_main()