]>
git.rmz.io Git - dotfiles.git/blob - weechat/python/autosort.py
8f7c263e149ae1b24868b0c840f480b887710f8a
1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2013-2017 Maarten de Vries <maarten@de-vri.es>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # Autosort automatically keeps your buffers sorted and grouped by server.
21 # You can define your own sorting rules. See /help autosort for more details.
23 # https://github.com/de-vri-es/weechat-autosort
29 # * Add more documentation on provided info hooks.
31 # * Add ${info:autosort_escape,...} to escape arguments for other info hooks.
33 # * Fix rate-limit of sorting to prevent high CPU load and lock-ups.
34 # * Fix bug in parsing empty arguments for info hooks.
35 # * Add debug_log option to aid with debugging.
36 # * Correct a few typos.
38 # * Fix the /autosort debug command for unicode.
39 # * Update the default rules to work better with Slack.
41 # * Fix python3 compatiblity.
43 # * Use colors to format the help text.
45 # * Switch to evaluated expressions for sorting.
46 # * Add `/autosort debug` command.
47 # * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules.
48 # * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules.
49 # * Make tab completion context aware.
51 # * Fix compatibility with python 3 regarding unicode handling.
53 # * Fix sorting of buffers with spaces in their name.
55 # * Ignore case in rules when doing case insensitive sorting.
57 # * Fix handling unicode buffer names.
58 # * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on.
60 # * Make script python3 compatible.
62 # * Fix sorting items without score last (regressed in 2.2).
64 # * Add configuration option for signals that trigger a sort.
65 # * Add command to manually trigger a sort (/autosort sort).
66 # * Add replacement patterns to apply before sorting.
68 # * Fix some minor style issues.
70 # * Allow for custom sort rules.
81 SCRIPT_NAME
= 'autosort'
82 SCRIPT_AUTHOR
= 'Maarten de Vries <maarten@de-vri.es>'
83 SCRIPT_VERSION
= '3.6'
84 SCRIPT_LICENSE
= 'GPL3'
85 SCRIPT_DESC
= 'Flexible automatic (or manual) buffer sorting based on eval expressions.'
90 signal_delay_timer
= None
91 sort_limit_timer
= None
95 # Make sure that unicode, bytes and str are always available in python2 and 3.
96 # For python 2, str == bytes
97 # For python 3, str == unicode
98 if sys
. version_info
[ 0 ] >= 3 :
101 def ensure_str ( input ):
103 Make sure the given type if the correct string type for the current python version.
104 That means bytes for python2 and unicode for python3.
106 if not isinstance ( input , str ):
107 if isinstance ( input , bytes ):
108 return input . encode ( 'utf-8' )
109 if isinstance ( input , unicode ):
110 return input . decode ( 'utf-8' )
114 if hasattr ( time
, 'perf_counter' ):
115 perf_counter
= time
. perf_counter
117 perf_counter
= time
. clock
119 def casefold ( string
):
120 if hasattr ( string
, 'casefold' ): return string
. casefold ()
121 # Fall back to lowercasing for python2.
122 return string
. lower ()
124 def list_swap ( values
, a
, b
):
125 values
[ a
], values
[ b
] = values
[ b
], values
[ a
]
127 def list_move ( values
, old_index
, new_index
):
128 values
. insert ( new_index
, values
. pop ( old_index
))
130 def list_find ( collection
, value
):
131 for i
, elem
in enumerate ( collection
):
132 if elem
== value
: return i
135 class HumanReadableError ( Exception ):
138 def parse_int ( arg
, arg_name
= 'argument' ):
139 ''' Parse an integer and provide a more human readable error. '''
144 raise HumanReadableError ( 'Invalid {0} : expected integer, got " {1} ".' . format ( arg_name
, arg
))
146 def decode_rules ( blob
):
147 parsed
= json
. loads ( blob
)
148 if not isinstance ( parsed
, list ):
149 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
)))
152 for i
, entry
in enumerate ( parsed
):
153 if not isinstance ( entry
, ( str , unicode )):
154 log ( 'Rule # {0} is not a string but a {1} . No rules have been loaded. Please fix the setting manually.' . format ( i
, type ( entry
)))
159 def decode_helpers ( blob
):
160 parsed
= json
. loads ( blob
)
161 if not isinstance ( parsed
, dict ):
162 log ( 'Malformed helpers, expected a JSON encoded dictionary but got a {0} . No helpers have been loaded. Please fix the setting manually.' . format ( type ( parsed
)))
165 for key
, value
in parsed
. items ():
166 if not isinstance ( value
, ( str , unicode )):
167 log ( 'Helper " {0} " is not a string but a {1} . No helpers have been loaded. Please fix setting manually.' . format ( key
, type ( value
)))
172 ''' The autosort configuration. '''
174 default_rules
= json
. dumps ([
177 '$ {buffer.plugin.name} ' ,
179 '$ {if:${plugin} ==irc?$ {server} }' ,
180 '$ {if:${plugin} ==irc?$ {info:autosort_order,${type} ,server,*,channel,private}}' ,
181 '$ {if:${plugin} ==irc?$ {hashless_name} }' ,
182 '$ {buffer.full_name} ' ,
185 default_helpers
= json
. dumps ({
186 'core_first' : '$ {if:${buffer.full_name} !=core.weechat}' ,
187 'irc_first' : '$ {if:${buffer.plugin.name} !=irc}' ,
188 'irc_last' : '$ {if:${buffer.plugin.name} ==irc}' ,
189 'irc_raw_first' : '$ {if:${buffer.full_name} !=irc.irc_raw}' ,
190 'irc_raw_last' : '$ {if:${buffer.full_name} ==irc.irc_raw}' ,
191 'hashless_name' : '$ {info:autosort_replace,#,,${info:autosort_escape,${buffer.name} }}' ,
194 default_signal_delay
= 5
195 default_sort_limit
= 100
197 default_signals
= 'buffer_opened buffer_merged buffer_unmerged buffer_renamed'
199 def __init__ ( self
, filename
):
200 ''' Initialize the configuration. '''
202 self
. filename
= filename
203 self
. config_file
= weechat
. config_new ( self
. filename
, '' , '' )
204 self
. sorting_section
= None
205 self
. v3_section
= None
207 self
. case_sensitive
= False
211 self
. signal_delay
= Config
. default_signal_delay
,
212 self
. sort_limit
= Config
. default_sort_limit
,
213 self
. sort_on_config
= True
214 self
. debug_log
= False
216 self
.__ case
_ sensitive
= None
218 self
.__ helpers
= None
219 self
.__ signals
= None
220 self
.__ signal
_ delay
= None
221 self
.__ sort
_l imit
= None
222 self
.__ sort
_ on
_ config
= None
223 self
.__ debug
_l og
= None
225 if not self
. config_file
:
226 log ( 'Failed to initialize configuration file " {0} ".' . format ( self
. filename
))
229 self
. sorting_section
= weechat
. config_new_section ( self
. config_file
, 'sorting' , False , False , '' , '' , '' , '' , '' , '' , '' , '' , '' , '' )
230 self
. v3_section
= weechat
. config_new_section ( self
. config_file
, 'v3' , False , False , '' , '' , '' , '' , '' , '' , '' , '' , '' , '' )
232 if not self
. sorting_section
:
233 log ( 'Failed to initialize section "sorting" of configuration file.' )
234 weechat
. config_free ( self
. config_file
)
237 self
.__ case
_ sensitive
= weechat
. config_new_option (
238 self
. config_file
, self
. sorting_section
,
239 'case_sensitive' , 'boolean' ,
240 'If this option is on, sorting is case sensitive.' ,
241 '' , 0 , 0 , 'off' , 'off' , 0 ,
242 '' , '' , '' , '' , '' , ''
245 weechat
. config_new_option (
246 self
. config_file
, self
. sorting_section
,
248 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.' ,
250 '' , '' , '' , '' , '' , ''
253 weechat
. config_new_option (
254 self
. config_file
, self
. sorting_section
,
255 'replacements' , 'string' ,
256 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.' ,
258 '' , '' , '' , '' , '' , ''
261 self
.__ rules
= weechat
. config_new_option (
262 self
. config_file
, self
. v3_section
,
264 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.' ,
265 '' , 0 , 0 , Config
. default_rules
, Config
. default_rules
, 0 ,
266 '' , '' , '' , '' , '' , ''
269 self
.__ helpers
= weechat
. config_new_option (
270 self
. config_file
, self
. v3_section
,
272 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.' ,
273 '' , 0 , 0 , Config
. default_helpers
, Config
. default_helpers
, 0 ,
274 '' , '' , '' , '' , '' , ''
277 self
.__ signals
= weechat
. config_new_option (
278 self
. config_file
, self
. sorting_section
,
280 'A space separated list of signals that will cause autosort to resort your buffer list.' ,
281 '' , 0 , 0 , Config
. default_signals
, Config
. default_signals
, 0 ,
282 '' , '' , '' , '' , '' , ''
285 self
.__ signal
_ delay
= weechat
. config_new_option (
286 self
. config_file
, self
. sorting_section
,
287 'signal_delay' , 'integer' ,
288 '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.' ,
289 '' , 0 , 1000 , str ( Config
. default_signal_delay
), str ( Config
. default_signal_delay
), 0 ,
290 '' , '' , '' , '' , '' , ''
293 self
.__ sort
_l imit
= weechat
. config_new_option (
294 self
. config_file
, self
. sorting_section
,
295 'sort_limit' , 'integer' ,
296 'Minimum delay in milliseconds to wait after sorting before signals can trigger a sort again. This is effectively a rate limit on sorting. Keeping signal_delay low while setting this higher can reduce excessive sorting without a long initial delay.' ,
297 '' , 0 , 1000 , str ( Config
. default_sort_limit
), str ( Config
. default_sort_limit
), 0 ,
298 '' , '' , '' , '' , '' , ''
301 self
.__ sort
_ on
_ config
= weechat
. config_new_option (
302 self
. config_file
, self
. sorting_section
,
303 'sort_on_config_change' , 'boolean' ,
304 'Decides if the buffer list should be sorted when autosort configuration changes.' ,
305 '' , 0 , 0 , 'on' , 'on' , 0 ,
306 '' , '' , '' , '' , '' , ''
309 self
.__ debug
_l og
= weechat
. config_new_option (
310 self
. config_file
, self
. sorting_section
,
311 'debug_log' , 'boolean' ,
312 'If enabled, print more debug messages. Not recommended for normal usage.' ,
313 '' , 0 , 0 , 'off' , 'off' , 0 ,
314 '' , '' , '' , '' , '' , ''
317 if weechat
. config_read ( self
. config_file
) != weechat
. WEECHAT_RC_OK
:
318 log ( 'Failed to load configuration file.' )
320 if weechat
. config_write ( self
. config_file
) != weechat
. WEECHAT_RC_OK
:
321 log ( 'Failed to write configuration file.' )
326 ''' Load configuration variables. '''
328 self
. case_sensitive
= weechat
. config_boolean ( self
.__ case
_ sensitive
)
330 rules_blob
= weechat
. config_string ( self
.__ rules
)
331 helpers_blob
= weechat
. config_string ( self
.__ helpers
)
332 signals_blob
= weechat
. config_string ( self
.__ signals
)
334 self
. rules
= decode_rules ( rules_blob
)
335 self
. helpers
= decode_helpers ( helpers_blob
)
336 self
. signals
= signals_blob
. split ()
337 self
. signal_delay
= weechat
. config_integer ( self
.__ signal
_ delay
)
338 self
. sort_limit
= weechat
. config_integer ( self
.__ sort
_l imit
)
339 self
. sort_on_config
= weechat
. config_boolean ( self
.__ sort
_ on
_ config
)
340 self
. debug_log
= weechat
. config_boolean ( self
.__ debug
_l og
)
342 def save_rules ( self
, run_callback
= True ):
343 ''' Save the current rules to the configuration. '''
344 weechat
. config_option_set ( self
.__ rules
, json
. dumps ( self
. rules
), run_callback
)
346 def save_helpers ( self
, run_callback
= True ):
347 ''' Save the current helpers to the configuration. '''
348 weechat
. config_option_set ( self
.__ helpers
, json
. dumps ( self
. helpers
), run_callback
)
351 def pad ( sequence
, length
, padding
= None ):
352 ''' Pad a list until is has a certain length. '''
353 return sequence
+ [ padding
] * max ( 0 , ( length
- len ( sequence
)))
355 def log ( message
, buffer = 'NULL' ):
356 weechat
. prnt ( buffer , 'autosort: {0} ' . format ( message
))
358 def debug ( message
, buffer = 'NULL' ):
360 weechat
. prnt ( buffer , 'autosort: debug: {0} ' . format ( message
))
363 ''' Get a list of all the buffers in weechat. '''
364 hdata
= weechat
. hdata_get ( 'buffer' )
365 buffer = weechat
. hdata_get_list ( hdata
, "gui_buffers" );
369 number
= weechat
. hdata_integer ( hdata
, buffer , 'number' )
370 result
. append (( number
, buffer ))
371 buffer = weechat
. hdata_pointer ( hdata
, buffer , 'next_buffer' )
374 class MergedBuffers ( list ):
375 """ A list of merged buffers, possibly of size 1. """
376 def __init__ ( self
, number
):
377 super ( MergedBuffers
, self
) .__ init
__ ()
380 def merge_buffer_list ( buffers
):
382 Group merged buffers together.
383 The output is a list of MergedBuffers.
385 if not buffers
: return []
387 for number
, buffer in buffers
:
388 if number
not in result
: result
[ number
] = MergedBuffers ( number
)
389 result
[ number
]. append ( buffer )
390 return result
. values ()
392 def sort_buffers ( hdata
, buffers
, rules
, helpers
, case_sensitive
):
393 for merged
in buffers
:
394 for buffer in merged
:
395 name
= weechat
. hdata_string ( hdata
, buffer , 'name' )
397 return sorted ( buffers
, key
= merged_sort_key ( rules
, helpers
, case_sensitive
))
399 def buffer_sort_key ( rules
, helpers
, case_sensitive
):
400 ''' Create a sort key function for a list of lists of merged buffers. '''
403 for helper_name
, helper
in sorted ( helpers
. items ()):
404 expanded
= weechat
. string_eval_expression ( helper
, {"buffer": buffer}
, {}, {}
)
405 extra_vars
[ helper_name
] = expanded
if case_sensitive
else casefold ( expanded
)
408 expanded
= weechat
. string_eval_expression ( rule
, {"buffer": buffer}
, extra_vars
, {})
409 result
. append ( expanded
if case_sensitive
else casefold ( expanded
))
414 def merged_sort_key ( rules
, helpers
, case_sensitive
):
415 buffer_key
= buffer_sort_key ( rules
, helpers
, case_sensitive
)
418 for buffer in merged
:
419 this
= buffer_key ( buffer )
420 if best
is None or this
< best
: best
= this
424 def apply_buffer_order ( buffers
):
425 ''' Sort the buffers in weechat according to the given order. '''
426 for i
, buffer in enumerate ( buffers
):
427 weechat
. buffer_set ( buffer [ 0 ], "number" , str ( i
+ 1 ))
429 def split_args ( args
, expected
, optional
= 0 ):
430 ''' Split an argument string in the desired number of arguments. '''
431 split
= args
. split ( ' ' , expected
- 1 )
432 if ( len ( split
) < expected
):
433 raise HumanReadableError ( 'Expected at least {0} arguments, got {1} .' . format ( expected
, len ( split
)))
434 return split
[:- 1 ] + pad ( split
[- 1 ]. split ( ' ' , optional
), optional
+ 1 , '' )
436 def do_sort ( verbose
= False ):
437 start
= perf_counter ()
439 hdata
, buffers
= get_buffers ()
440 buffers
= merge_buffer_list ( buffers
)
441 buffers
= sort_buffers ( hdata
, buffers
, config
. rules
, config
. helpers
, config
. case_sensitive
)
442 apply_buffer_order ( buffers
)
444 elapsed
= perf_counter () - start
446 log ( "Finished sorting buffers in {0:.4f} seconds." . format ( elapsed
))
448 debug ( "Finished sorting buffers in {0:.4f} seconds." . format ( elapsed
))
450 def command_sort ( buffer , command
, args
):
451 ''' Sort the buffers and print a confirmation. '''
453 return weechat
. WEECHAT_RC_OK
455 def command_debug ( buffer , command
, args
):
456 hdata
, buffers
= get_buffers ()
457 buffers
= merge_buffer_list ( buffers
)
459 # Show evaluation results.
460 log ( 'Individual evaluation results:' )
461 start
= perf_counter ()
462 key
= buffer_sort_key ( config
. rules
, config
. helpers
, config
. case_sensitive
)
464 for merged
in buffers
:
465 for buffer in merged
:
466 fullname
= weechat
. hdata_string ( hdata
, buffer , 'full_name' )
467 results
. append (( fullname
, key ( buffer )))
468 elapsed
= perf_counter () - start
470 for fullname
, result
in results
:
471 fullname
= ensure_str ( fullname
)
472 result
= [ ensure_str ( x
) for x
in result
]
473 log ( ' {0} : {1} ' . format ( fullname
, result
))
474 log ( 'Computing evaluation results took {0:.4f} seconds.' . format ( elapsed
))
476 return weechat
. WEECHAT_RC_OK
478 def command_rule_list ( buffer , command
, args
):
479 ''' Show the list of sorting rules. '''
480 output
= 'Sorting rules: \n '
481 for i
, rule
in enumerate ( config
. rules
):
482 output
+= ' {0} : {1} \n ' . format ( i
, rule
)
483 if not len ( config
. rules
):
484 output
+= ' No sorting rules configured. \n '
487 return weechat
. WEECHAT_RC_OK
490 def command_rule_add ( buffer , command
, args
):
491 ''' Add a rule to the rule list. '''
492 config
. rules
. append ( args
)
494 command_rule_list ( buffer , command
, '' )
496 return weechat
. WEECHAT_RC_OK
499 def command_rule_insert ( buffer , command
, args
):
500 ''' Insert a rule at the desired position in the rule list. '''
501 index
, rule
= split_args ( args
, 2 )
502 index
= parse_int ( index
, 'index' )
504 config
. rules
. insert ( index
, rule
)
506 command_rule_list ( buffer , command
, '' )
507 return weechat
. WEECHAT_RC_OK
510 def command_rule_update ( buffer , command
, args
):
511 ''' Update a rule in the rule list. '''
512 index
, rule
= split_args ( args
, 2 )
513 index
= parse_int ( index
, 'index' )
515 config
. rules
[ index
] = rule
517 command_rule_list ( buffer , command
, '' )
518 return weechat
. WEECHAT_RC_OK
521 def command_rule_delete ( buffer , command
, args
):
522 ''' Delete a rule from the rule list. '''
524 index
= parse_int ( index
, 'index' )
526 config
. rules
. pop ( index
)
528 command_rule_list ( buffer , command
, '' )
529 return weechat
. WEECHAT_RC_OK
532 def command_rule_move ( buffer , command
, args
):
533 ''' Move a rule to a new position. '''
534 index_a
, index_b
= split_args ( args
, 2 )
535 index_a
= parse_int ( index_a
, 'index' )
536 index_b
= parse_int ( index_b
, 'index' )
538 list_move ( config
. rules
, index_a
, index_b
)
540 command_rule_list ( buffer , command
, '' )
541 return weechat
. WEECHAT_RC_OK
544 def command_rule_swap ( buffer , command
, args
):
545 ''' Swap two rules. '''
546 index_a
, index_b
= split_args ( args
, 2 )
547 index_a
= parse_int ( index_a
, 'index' )
548 index_b
= parse_int ( index_b
, 'index' )
550 list_swap ( config
. rules
, index_a
, index_b
)
552 command_rule_list ( buffer , command
, '' )
553 return weechat
. WEECHAT_RC_OK
556 def command_helper_list ( buffer , command
, args
):
557 ''' Show the list of helpers. '''
558 output
= 'Helper variables: \n '
560 width
= max ( map ( lambda x
: len ( x
) if len ( x
) <= 30 else 0 , config
. helpers
. keys ()))
562 for name
, expression
in sorted ( config
. helpers
. items ()):
563 output
+= ' {0:>{width} }: {1} \n ' . format ( name
, expression
, width
= width
)
564 if not len ( config
. helpers
):
565 output
+= ' No helper variables configured.'
568 return weechat
. WEECHAT_RC_OK
571 def command_helper_set ( buffer , command
, args
):
572 ''' Add/update a helper to the helper list. '''
573 name
, expression
= split_args ( args
, 2 )
575 config
. helpers
[ name
] = expression
576 config
. save_helpers ()
577 command_helper_list ( buffer , command
, '' )
579 return weechat
. WEECHAT_RC_OK
581 def command_helper_delete ( buffer , command
, args
):
582 ''' Delete a helper from the helper list. '''
585 del config
. helpers
[ name
]
586 config
. save_helpers ()
587 command_helper_list ( buffer , command
, '' )
588 return weechat
. WEECHAT_RC_OK
591 def command_helper_rename ( buffer , command
, args
):
592 ''' Rename a helper to a new position. '''
593 old_name
, new_name
= split_args ( args
, 2 )
596 config
. helpers
[ new_name
] = config
. helpers
[ old_name
]
597 del config
. helpers
[ old_name
]
599 raise HumanReadableError ( 'No such helper: {0} ' . format ( old_name
))
600 config
. save_helpers ()
601 command_helper_list ( buffer , command
, '' )
602 return weechat
. WEECHAT_RC_OK
605 def command_helper_swap ( buffer , command
, args
):
606 ''' Swap two helpers. '''
607 a
, b
= split_args ( args
, 2 )
609 config
. helpers
[ b
], config
. helpers
[ a
] = config
. helpers
[ a
], config
. helpers
[ b
]
610 except KeyError as e
:
611 raise HumanReadableError ( 'No such helper: {0} ' . format ( e
. args
[ 0 ]))
613 config
. helpers
. swap ( index_a
, index_b
)
614 config
. save_helpers ()
615 command_helper_list ( buffer , command
, '' )
616 return weechat
. WEECHAT_RC_OK
618 def call_command ( buffer , command
, args
, subcommands
):
619 ''' Call a subcommand from a dictionary. '''
620 subcommand
, tail
= pad ( args
. split ( ' ' , 1 ), 2 , '' )
621 subcommand
= subcommand
. strip ()
622 if ( subcommand
== '' ):
623 child
= subcommands
. get ( ' ' )
625 command
= command
+ [ subcommand
]
626 child
= subcommands
. get ( subcommand
)
628 if isinstance ( child
, dict ):
629 return call_command ( buffer , command
, tail
, child
)
630 elif callable ( child
):
631 return child ( buffer , command
, tail
)
633 log ( ' {0} : command not found' . format ( ' ' . join ( command
)))
634 return weechat
. WEECHAT_RC_ERROR
636 def on_signal ( data
, signal
, signal_data
):
637 global signal_delay_timer
640 # If the sort limit timeout is started, we're in the hold-off time after sorting, just queue a sort.
641 if sort_limit_timer
is not None :
643 debug ( 'Signal {0} ignored, sort limit timeout is active and sort is already queued.' . format ( signal
))
645 debug ( 'Signal {0} received but sort limit timeout is active, sort is now queued.' . format ( signal
))
647 return weechat
. WEECHAT_RC_OK
649 # If the signal delay timeout is started, a signal was recently received, so ignore this signal.
650 if signal_delay_timer
is not None :
651 debug ( 'Signal {0} ignored, signal delay timeout active.' . format ( signal
))
652 return weechat
. WEECHAT_RC_OK
654 # Otherwise, start the signal delay timeout.
655 debug ( 'Signal {0} received, starting signal delay timeout of {1} ms.' . format ( signal
, config
. signal_delay
))
656 weechat
. hook_timer ( config
. signal_delay
, 0 , 1 , "on_signal_delay_timeout" , "" )
657 return weechat
. WEECHAT_RC_OK
659 def on_signal_delay_timeout ( pointer
, remaining_calls
):
660 """ Called when the signal_delay_timer triggers. """
661 global signal_delay_timer
662 global sort_limit_timer
665 signal_delay_timer
= None
667 # If the sort limit timeout was started, we're still in the no-sort period, so just queue a sort.
668 if sort_limit_timer
is not None :
669 debug ( 'Signal delay timeout expired, but sort limit timeout is active, sort is now queued.' )
671 return weechat
. WEECHAT_RC_OK
674 debug ( 'Signal delay timeout expired, starting sort.' )
677 # Start the sort limit timeout if not disabled.
678 if config
. sort_limit
> 0 :
679 debug ( 'Starting sort limit timeout of {0} ms.' . format ( config
. sort_limit
))
680 sort_limit_timer
= weechat
. hook_timer ( config
. sort_limit
, 0 , 1 , "on_sort_limit_timeout" , "" )
682 return weechat
. WEECHAT_RC_OK
684 def on_sort_limit_timeout ( pointer
, remainin_calls
):
685 """ Called when de sort_limit_timer triggers. """
686 global sort_limit_timer
689 # If no signal was received during the timeout, we're done.
691 debug ( 'Sort limit timeout expired without receiving a signal.' )
692 sort_limit_timer
= None
693 return weechat
. WEECHAT_RC_OK
695 # Otherwise it's time to sort.
696 debug ( 'Signal received during sort limit timeout, starting queued sort.' )
700 # Start the sort limit timeout again if not disabled.
701 if config
. sort_limit
> 0 :
702 debug ( 'Starting sort limit timeout of {0} ms.' . format ( config
. sort_limit
))
703 sort_limit_timer
= weechat
. hook_timer ( config
. sort_limit
, 0 , 1 , "on_sort_limit_timeout" , "" )
705 return weechat
. WEECHAT_RC_OK
709 # Unhook all signals and hook the new ones.
712 for signal
in config
. signals
:
713 hooks
. append ( weechat
. hook_signal ( signal
, 'on_signal' , '' ))
715 if config
. sort_on_config
:
716 debug ( 'Sorting because configuration changed.' )
719 def on_config_changed (* args
, ** kwargs
):
720 ''' Called whenever the configuration changes. '''
724 return weechat
. WEECHAT_RC_OK
727 if not args
: return '' , None
731 for i
, c
in enumerate ( args
):
737 return result
, args
[ i
+ 1 :]
742 def parse_args ( args
, max = None ):
745 while max is None or i
< max :
747 arg
, args
= parse_arg ( args
)
748 if arg
is None : break
750 if args
is None : break
753 def on_info_escape ( pointer
, name
, arguments
):
764 def on_info_replace ( pointer
, name
, arguments
):
765 arguments
, rest
= parse_args ( arguments
, 3 )
766 if rest
or len ( arguments
) < 3 :
767 log ( 'usage: $ {{info:{0} ,old,new,text}}' . format ( name
))
769 old
, new
, text
= arguments
771 return text
. replace ( old
, new
)
773 def on_info_order ( pointer
, name
, arguments
):
774 arguments
, rest
= parse_args ( arguments
)
775 if len ( arguments
) < 1 :
776 log ( 'usage: $ {{info:{0} ,value,first,second,third,...}}' . format ( name
))
781 if not keys
: return '0'
783 # Find the value in the keys (or '*' if we can't find it)
784 result
= list_find ( keys
, value
)
785 if result
is None : result
= list_find ( keys
, '*' )
786 if result
is None : result
= len ( keys
)
788 # Pad result with leading zero to make sure string sorting works.
789 width
= int ( math
. log10 ( len ( keys
))) + 1
790 return ' {0:0{1} }' . format ( result
, width
)
793 def on_autosort_command ( data
, buffer , args
):
794 ''' Called when the autosort command is invoked. '''
796 return call_command ( buffer , [ '/autosort' ], args
, {
798 'sort' : command_sort
,
799 'debug' : command_debug
,
802 ' ' : command_rule_list
,
803 'list' : command_rule_list
,
804 'add' : command_rule_add
,
805 'insert' : command_rule_insert
,
806 'update' : command_rule_update
,
807 'delete' : command_rule_delete
,
808 'move' : command_rule_move
,
809 'swap' : command_rule_swap
,
812 ' ' : command_helper_list
,
813 'list' : command_helper_list
,
814 'set' : command_helper_set
,
815 'delete' : command_helper_delete
,
816 'rename' : command_helper_rename
,
817 'swap' : command_helper_swap
,
820 except HumanReadableError
as e
:
822 return weechat
. WEECHAT_RC_ERROR
824 def add_completions ( completion
, words
):
826 weechat
. hook_completion_list_add ( completion
, word
, 0 , weechat
. WEECHAT_LIST_POS_END
)
828 def autosort_complete_rules ( words
, completion
):
830 add_completions ( completion
, [ 'add' , 'delete' , 'insert' , 'list' , 'move' , 'swap' , 'update' ])
831 if len ( words
) == 1 and words
[ 0 ] in ( 'delete' , 'insert' , 'move' , 'swap' , 'update' ):
832 add_completions ( completion
, map ( str , range ( len ( config
. rules
))))
833 if len ( words
) == 2 and words
[ 0 ] in ( 'move' , 'swap' ):
834 add_completions ( completion
, map ( str , range ( len ( config
. rules
))))
835 if len ( words
) == 2 and words
[ 0 ] in ( 'update' ):
837 add_completions ( completion
, [ config
. rules
[ int ( words
[ 1 ])]])
838 except KeyError : pass
839 except ValueError : pass
841 add_completions ( completion
, [ '' ])
842 return weechat
. WEECHAT_RC_OK
844 def autosort_complete_helpers ( words
, completion
):
846 add_completions ( completion
, [ 'delete' , 'list' , 'rename' , 'set' , 'swap' ])
847 elif len ( words
) == 1 and words
[ 0 ] in ( 'delete' , 'rename' , 'set' , 'swap' ):
848 add_completions ( completion
, sorted ( config
. helpers
. keys ()))
849 elif len ( words
) == 2 and words
[ 0 ] == 'swap' :
850 add_completions ( completion
, sorted ( config
. helpers
. keys ()))
851 elif len ( words
) == 2 and words
[ 0 ] == 'rename' :
852 add_completions ( completion
, sorted ( config
. helpers
. keys ()))
853 elif len ( words
) == 2 and words
[ 0 ] == 'set' :
855 add_completions ( completion
, [ config
. helpers
[ words
[ 1 ]]])
856 except KeyError : pass
857 return weechat
. WEECHAT_RC_OK
859 def on_autosort_complete ( data
, name
, buffer , completion
):
860 cmdline
= weechat
. buffer_get_string ( buffer , "input" )
861 cursor
= weechat
. buffer_get_integer ( buffer , "input_pos" )
862 prefix
= cmdline
[: cursor
]
863 words
= prefix
. split ()[ 1 :]
865 # If the current word isn't finished yet,
866 # ignore it for coming up with completion suggestions.
867 if prefix
[- 1 ] != ' ' : words
= words
[:- 1 ]
870 add_completions ( completion
, [ 'debug' , 'helpers' , 'rules' , 'sort' ])
871 elif words
[ 0 ] == 'rules' :
872 return autosort_complete_rules ( words
[ 1 :], completion
)
873 elif words
[ 0 ] == 'helpers' :
874 return autosort_complete_helpers ( words
[ 1 :], completion
)
875 return weechat
. WEECHAT_RC_OK
877 command_description
= r
''' {*white} # General commands {reset}
879 {*white} /autosort {brown} sort {reset}
880 Manually trigger the buffer sorting.
882 {*white} /autosort {brown} debug {reset}
883 Show the evaluation results of the sort rules for each buffer.
886 {*white} # Sorting rule commands {reset}
888 {*white} /autosort {brown} rules list {reset}
889 Print the list of sort rules.
891 {*white} /autosort {brown} rules add {cyan} <expression> {reset}
892 Add a new rule at the end of the list.
894 {*white} /autosort {brown} rules insert {cyan} <index> <expression> {reset}
895 Insert a new rule at the given index in the list.
897 {*white} /autosort {brown} rules update {cyan} <index> <expression> {reset}
898 Update a rule in the list with a new expression.
900 {*white} /autosort {brown} rules delete {cyan} <index>
901 Delete a rule from the list.
903 {*white} /autosort {brown} rules move {cyan} <index_from> <index_to> {reset}
904 Move a rule from one position in the list to another.
906 {*white} /autosort {brown} rules swap {cyan} <index_a> <index_b> {reset}
907 Swap two rules in the list
910 {*white} # Helper variable commands {reset}
912 {*white} /autosort {brown} helpers list
913 Print the list of helper variables.
915 {*white} /autosort {brown} helpers set {cyan} <name> <expression>
916 Add or update a helper variable with the given name.
918 {*white} /autosort {brown} helpers delete {cyan} <name>
919 Delete a helper variable.
921 {*white} /autosort {brown} helpers rename {cyan} <old_name> <new_name>
922 Rename a helper variable.
924 {*white} /autosort {brown} helpers swap {cyan} <name_a> <name_b>
925 Swap the expressions of two helper variables in the list.
928 {*white} # Info hooks {reset}
929 Autosort comes with a number of info hooks to add some extra functionality to regular weechat eval strings.
930 Info hooks can be used in eval strings in the form of {cyan} $ {{info:some_hook,arguments} } {reset} .
932 Commas and backslashes in arguments to autosort info hooks (except for {cyan} $ {{info:autosort_escape} } {reset} ) must be escaped with a backslash.
934 {*white} $ {{info:{brown} autosort_replace {white} , {cyan} pattern {white} , {cyan} replacement {white} , {cyan} source {white} }} {reset}
935 Replace all occurrences of {cyan} pattern {reset} with {cyan} replacement {reset} in the string {cyan} source {reset} .
936 Can be used to ignore certain strings when sorting by replacing them with an empty string.
938 For example: {cyan} $ {{info:autosort_replace,cat,dog,the dog is meowing} } {reset} expands to "the cat is meowing".
940 {*white} $ {{info:{brown} autosort_order {white} , {cyan} value {white} , {cyan} option0 {white} , {cyan} option1 {white} , {cyan} option2 {white} , {cyan} ... {white} }}
941 Generate a zero-padded number that corresponds to the index of {cyan} value {reset} in the list of options.
942 If one of the options is the special value {brown} * {reset} , then any value not explicitly mentioned will be sorted at that position.
943 Otherwise, any value that does not match an option is assigned the highest number available.
944 Can be used to easily sort buffers based on a manual sequence.
946 For example: {cyan} $ {{info:autosort_order,${{server} },freenode,oftc,efnet}} {reset} will sort freenode before oftc, followed by efnet and then any remaining servers.
947 Alternatively, {cyan} $ {{info:autosort_order,${{server} },freenode,oftc,*,efnet}} {reset} will sort any unlisted servers after freenode and oftc, but before efnet.
949 {*white} $ {{info:{brown} autosort_escape {white} , {cyan} text {white} }} {reset}
950 Escape commas and backslashes in {cyan} text {reset} by prepending them with a backslash.
951 This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks.
952 Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.
954 For example, it can be used to safely pass buffer names to {cyan} $ {{info:autosort_replace} } {reset} like so:
955 {cyan} $ {{info:autosort_replace,##,#,${{info:autosort_escape,${{buffer.name} }}}}} {reset} .
958 {*white} # Description
959 Autosort is a weechat script to automatically keep your buffers sorted. The sort
960 order can be customized by defining your own sort rules, but the default should
961 be sane enough for most people. It can also group IRC channel/private buffers
962 under their server buffer if you like.
964 {*white} # Sort rules {reset}
965 Autosort evaluates a list of eval expressions (see {*default} /help eval {reset} ) and sorts the
966 buffers based on evaluated result. Earlier rules will be considered first. Only
967 if earlier rules produced identical results is the result of the next rule
968 considered for sorting purposes.
970 You can debug your sort rules with the ` {*default} /autosort debug {reset} ` command, which will
971 print the evaluation results of each rule for each buffer.
973 {*brown} NOTE: {reset} The sort rules for version 3 are not compatible with version 2 or vice
974 versa. You will have to manually port your old rules to version 3 if you have any.
976 {*white} # Helper variables {reset}
977 You may define helper variables for the main sort rules to keep your rules
978 readable. They can be used in the main sort rules as variables. For example,
979 a helper variable named ` {cyan} foo {reset} ` can be accessed in a main rule with the
980 string ` {cyan} $ {{foo} } {reset} `.
982 {*white} # Automatic or manual sorting {reset}
983 By default, autosort will automatically sort your buffer list whenever a buffer
984 is opened, merged, unmerged or renamed. This should keep your buffers sorted in
985 almost all situations. However, you may wish to change the list of signals that
986 cause your buffer list to be sorted. Simply edit the ` {cyan} autosort.sorting.signals {reset} `
987 option to add or remove any signal you like.
989 If you remove all signals you can still sort your buffers manually with the
990 ` {*default} /autosort sort {reset} ` command. To prevent all automatic sorting, the option
991 ` {cyan} autosort.sorting.sort_on_config_change {reset} ` should also be disabled.
993 {*white} # Recommended settings
994 For the best visual effect, consider setting the following options:
995 {*white} /set {cyan} irc.look.server_buffer {reset} {brown} independent {reset}
996 {*white} /set {cyan} buffers.look.indenting {reset} {brown} on {reset}
998 The first setting allows server buffers to be sorted independently, which is
999 needed to create a hierarchical tree view of the server and channel buffers.
1000 The second one indents channel and private buffers in the buffer list of the
1001 ` {*default} buffers.pl {reset} ` script.
1003 If you are using the {*default} buflist {reset} plugin you can (ab)use Unicode to draw a tree
1004 structure with the following setting (modify to suit your need):
1005 {*white} /set {cyan} buflist.format.indent {brown} "$ {{color:237} }$ {{if:${{buffer.next_buffer.local_variables.type} }=~^(channel|private)$?├─:└─}}" {reset}
1008 command_completion
= ' %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) '
1010 info_replace_description
= (
1011 'Replace all occurrences of `pattern` with `replacement` in the string `source`. '
1012 'Can be used to ignore certain strings when sorting by replacing them with an empty string. '
1013 'See /help autosort for examples.'
1015 info_replace_arguments
= 'pattern,replacement,source'
1017 info_order_description
= (
1018 'Generate a zero-padded number that corresponds to the index of `value` in the list of options. '
1019 'If one of the options is the special value `*`, then any value not explicitly mentioned will be sorted at that position. '
1020 'Otherwise, any value that does not match an option is assigned the highest number available. '
1021 'Can be used to easily sort buffers based on a manual sequence. '
1022 'See /help autosort for examples.'
1024 info_order_arguments
= 'value,first,second,third,...'
1026 info_escape_description
= (
1027 'Escape commas and backslashes in `text` by prepending them with a backslash. '
1028 'This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. '
1029 'Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.'
1030 'See /help autosort for examples.'
1032 info_escape_arguments
= 'text'
1035 if weechat
. register ( SCRIPT_NAME
, SCRIPT_AUTHOR
, SCRIPT_VERSION
, SCRIPT_LICENSE
, SCRIPT_DESC
, "" , "" ):
1036 config
= Config ( 'autosort' )
1039 'default' : weechat
. color ( 'default' ),
1040 'reset' : weechat
. color ( 'reset' ),
1041 'black' : weechat
. color ( 'black' ),
1042 'red' : weechat
. color ( 'red' ),
1043 'green' : weechat
. color ( 'green' ),
1044 'brown' : weechat
. color ( 'brown' ),
1045 'yellow' : weechat
. color ( 'yellow' ),
1046 'blue' : weechat
. color ( 'blue' ),
1047 'magenta' : weechat
. color ( 'magenta' ),
1048 'cyan' : weechat
. color ( 'cyan' ),
1049 'white' : weechat
. color ( 'white' ),
1050 '*default' : weechat
. color ( '*default' ),
1051 '*black' : weechat
. color ( '*black' ),
1052 '*red' : weechat
. color ( '*red' ),
1053 '*green' : weechat
. color ( '*green' ),
1054 '*brown' : weechat
. color ( '*brown' ),
1055 '*yellow' : weechat
. color ( '*yellow' ),
1056 '*blue' : weechat
. color ( '*blue' ),
1057 '*magenta' : weechat
. color ( '*magenta' ),
1058 '*cyan' : weechat
. color ( '*cyan' ),
1059 '*white' : weechat
. color ( '*white' ),
1062 weechat
. hook_config ( 'autosort.*' , 'on_config_changed' , '' )
1063 weechat
. hook_completion ( 'plugin_autosort' , '' , 'on_autosort_complete' , '' )
1064 weechat
. hook_command ( 'autosort' , command_description
. format (** colors
), '' , '' , command_completion
, 'on_autosort_command' , '' )
1065 weechat
. hook_info ( 'autosort_escape' , info_escape_description
, info_escape_arguments
, 'on_info_escape' , '' )
1066 weechat
. hook_info ( 'autosort_replace' , info_replace_description
, info_replace_arguments
, 'on_info_replace' , '' )
1067 weechat
. hook_info ( 'autosort_order' , info_order_description
, info_order_arguments
, 'on_info_order' , '' )