]>
git.rmz.io Git - dotfiles.git/blob - weechat/python/autosort.py
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 # * Fix the /autosort debug command for unicode.
30 # * Update the default rules to work better with Slack.
32 # * Fix python3 compatiblity.
34 # * Use colors to format the help text.
36 # * Switch to evaluated expressions for sorting.
37 # * Add `/autosort debug` command.
38 # * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules.
39 # * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules.
40 # * Make tab completion context aware.
42 # * Fix compatibility with python 3 regarding unicode handling.
44 # * Fix sorting of buffers with spaces in their name.
46 # * Ignore case in rules when doing case insensitive sorting.
48 # * Fix handling unicode buffer names.
49 # * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on.
51 # * Make script python3 compatible.
53 # * Fix sorting items without score last (regressed in 2.2).
55 # * Add configuration option for signals that trigger a sort.
56 # * Add command to manually trigger a sort (/autosort sort).
57 # * Add replacement patterns to apply before sorting.
59 # * Fix some minor style issues.
61 # * Allow for custom sort rules.
72 SCRIPT_NAME
= 'autosort'
73 SCRIPT_AUTHOR
= 'Maarten de Vries <maarten@de-vri.es>'
74 SCRIPT_VERSION
= '3.3'
75 SCRIPT_LICENSE
= 'GPL3'
76 SCRIPT_DESC
= 'Flexible automatic (or manual) buffer sorting based on eval expressions.'
83 # Make sure that unicode, bytes and str are always available in python2 and 3.
84 # For python 2, str == bytes
85 # For python 3, str == unicode
86 if sys
. version_info
[ 0 ] >= 3 :
89 def ensure_str ( input ):
91 Make sure the given type if the correct string type for the current python version.
92 That means bytes for python2 and unicode for python3.
94 if not isinstance ( input , str ):
95 if isinstance ( input , bytes ):
96 return input . encode ( 'utf-8' )
97 if isinstance ( input , unicode ):
98 return input . decode ( 'utf-8' )
102 if hasattr ( time
, 'perf_counter' ):
103 perf_counter
= time
. perf_counter
105 perf_counter
= time
. clock
107 def casefold ( string
):
108 if hasattr ( string
, 'casefold' ): return string
. casefold ()
109 # Fall back to lowercasing for python2.
110 return string
. lower ()
112 def list_swap ( values
, a
, b
):
113 values
[ a
], values
[ b
] = values
[ b
], values
[ a
]
115 def list_move ( values
, old_index
, new_index
):
116 values
. insert ( new_index
, values
. pop ( old_index
))
118 def list_find ( collection
, value
):
119 for i
, elem
in enumerate ( collection
):
120 if elem
== value
: return i
123 class HumanReadableError ( Exception ):
126 def parse_int ( arg
, arg_name
= 'argument' ):
127 ''' Parse an integer and provide a more human readable error. '''
132 raise HumanReadableError ( 'Invalid {0} : expected integer, got " {1} ".' . format ( arg_name
, arg
))
134 def decode_rules ( blob
):
135 parsed
= json
. loads ( blob
)
136 if not isinstance ( parsed
, list ):
137 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
)))
140 for i
, entry
in enumerate ( parsed
):
141 if not isinstance ( entry
, ( str , unicode )):
142 log ( 'Rule # {0} is not a string but a {1} . No rules have been loaded. Please fix the setting manually.' . format ( i
, type ( entry
)))
147 def decode_helpers ( blob
):
148 parsed
= json
. loads ( blob
)
149 if not isinstance ( parsed
, dict ):
150 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
)))
153 for key
, value
in parsed
. items ():
154 if not isinstance ( value
, ( str , unicode )):
155 log ( 'Helper " {0} " is not a string but a {1} . No helpers have been loaded. Please fix seting manually.' . format ( key
, type ( value
)))
160 ''' The autosort configuration. '''
162 default_rules
= json
. dumps ([
165 '$ {buffer.plugin.name} ' ,
167 '$ {if:${plugin} ==irc?$ {server} }' ,
168 '$ {if:${plugin} ==irc?$ {info:autosort_order,${type} ,server,*,channel,private}}' ,
169 '$ {if:${plugin} ==irc?$ {hashless_name} }' ,
170 '$ {buffer.full_name} ' ,
173 default_helpers
= json
. dumps ({
174 'core_first' : '$ {if:${buffer.full_name} !=core.weechat}' ,
175 'irc_first' : '$ {if:${buffer.plugin.name} !=irc}' ,
176 'irc_last' : '$ {if:${buffer.plugin.name} ==irc}' ,
177 'irc_raw_first' : '$ {if:${buffer.full_name} !=irc.irc_raw}' ,
178 'irc_raw_last' : '$ {if:${buffer.full_name} ==irc.irc_raw}' ,
179 'hashless_name' : '$ {info:autosort_replace,#,,${buffer.name} }' ,
182 default_signal_delay
= 5
184 default_signals
= 'buffer_opened buffer_merged buffer_unmerged buffer_renamed'
186 def __init__ ( self
, filename
):
187 ''' Initialize the configuration. '''
189 self
. filename
= filename
190 self
. config_file
= weechat
. config_new ( self
. filename
, '' , '' )
191 self
. sorting_section
= None
192 self
. v3_section
= None
194 self
. case_sensitive
= False
198 self
. signal_delay
= Config
. default_signal_delay
,
199 self
. sort_on_config
= True
201 self
.__ case
_ sensitive
= None
203 self
.__ helpers
= None
204 self
.__ signals
= None
205 self
.__ signal
_ delay
= None
206 self
.__ sort
_ on
_ config
= None
208 if not self
. config_file
:
209 log ( 'Failed to initialize configuration file " {0} ".' . format ( self
. filename
))
212 self
. sorting_section
= weechat
. config_new_section ( self
. config_file
, 'sorting' , False , False , '' , '' , '' , '' , '' , '' , '' , '' , '' , '' )
213 self
. v3_section
= weechat
. config_new_section ( self
. config_file
, 'v3' , False , False , '' , '' , '' , '' , '' , '' , '' , '' , '' , '' )
215 if not self
. sorting_section
:
216 log ( 'Failed to initialize section "sorting" of configuration file.' )
217 weechat
. config_free ( self
. config_file
)
220 self
.__ case
_ sensitive
= weechat
. config_new_option (
221 self
. config_file
, self
. sorting_section
,
222 'case_sensitive' , 'boolean' ,
223 'If this option is on, sorting is case sensitive.' ,
224 '' , 0 , 0 , 'off' , 'off' , 0 ,
225 '' , '' , '' , '' , '' , ''
228 weechat
. config_new_option (
229 self
. config_file
, self
. sorting_section
,
231 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.' ,
233 '' , '' , '' , '' , '' , ''
236 weechat
. config_new_option (
237 self
. config_file
, self
. sorting_section
,
238 'replacements' , 'string' ,
239 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.' ,
241 '' , '' , '' , '' , '' , ''
244 self
.__ rules
= weechat
. config_new_option (
245 self
. config_file
, self
. v3_section
,
247 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.' ,
248 '' , 0 , 0 , Config
. default_rules
, Config
. default_rules
, 0 ,
249 '' , '' , '' , '' , '' , ''
252 self
.__ helpers
= weechat
. config_new_option (
253 self
. config_file
, self
. v3_section
,
255 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.' ,
256 '' , 0 , 0 , Config
. default_helpers
, Config
. default_helpers
, 0 ,
257 '' , '' , '' , '' , '' , ''
260 self
.__ signals
= weechat
. config_new_option (
261 self
. config_file
, self
. sorting_section
,
263 'A space separated list of signals that will cause autosort to resort your buffer list.' ,
264 '' , 0 , 0 , Config
. default_signals
, Config
. default_signals
, 0 ,
265 '' , '' , '' , '' , '' , ''
268 self
.__ signal
_ delay
= weechat
. config_new_option (
269 self
. config_file
, self
. sorting_section
,
270 'signal_delay' , 'integer' ,
271 '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.' ,
272 '' , 0 , 1000 , str ( Config
. default_signal_delay
), str ( Config
. default_signal_delay
), 0 ,
273 '' , '' , '' , '' , '' , ''
276 self
.__ sort
_ on
_ config
= weechat
. config_new_option (
277 self
. config_file
, self
. sorting_section
,
278 'sort_on_config_change' , 'boolean' ,
279 'Decides if the buffer list should be sorted when autosort configuration changes.' ,
280 '' , 0 , 0 , 'on' , 'on' , 0 ,
281 '' , '' , '' , '' , '' , ''
284 if weechat
. config_read ( self
. config_file
) != weechat
. WEECHAT_RC_OK
:
285 log ( 'Failed to load configuration file.' )
287 if weechat
. config_write ( self
. config_file
) != weechat
. WEECHAT_RC_OK
:
288 log ( 'Failed to write configuration file.' )
293 ''' Load configuration variables. '''
295 self
. case_sensitive
= weechat
. config_boolean ( self
.__ case
_ sensitive
)
297 rules_blob
= weechat
. config_string ( self
.__ rules
)
298 helpers_blob
= weechat
. config_string ( self
.__ helpers
)
299 signals_blob
= weechat
. config_string ( self
.__ signals
)
301 self
. rules
= decode_rules ( rules_blob
)
302 self
. helpers
= decode_helpers ( helpers_blob
)
303 self
. signals
= signals_blob
. split ()
304 self
. signal_delay
= weechat
. config_integer ( self
.__ signal
_ delay
)
305 self
. sort_on_config
= weechat
. config_boolean ( self
.__ sort
_ on
_ config
)
307 def save_rules ( self
, run_callback
= True ):
308 ''' Save the current rules to the configuration. '''
309 weechat
. config_option_set ( self
.__ rules
, json
. dumps ( self
. rules
), run_callback
)
311 def save_helpers ( self
, run_callback
= True ):
312 ''' Save the current helpers to the configuration. '''
313 weechat
. config_option_set ( self
.__ helpers
, json
. dumps ( self
. helpers
), run_callback
)
316 def pad ( sequence
, length
, padding
= None ):
317 ''' Pad a list until is has a certain length. '''
318 return sequence
+ [ padding
] * max ( 0 , ( length
- len ( sequence
)))
321 def log ( message
, buffer = 'NULL' ):
322 weechat
. prnt ( buffer , 'autosort: {0} ' . format ( message
))
326 ''' Get a list of all the buffers in weechat. '''
327 hdata
= weechat
. hdata_get ( 'buffer' )
328 buffer = weechat
. hdata_get_list ( hdata
, "gui_buffers" );
332 number
= weechat
. hdata_integer ( hdata
, buffer , 'number' )
333 result
. append (( number
, buffer ))
334 buffer = weechat
. hdata_pointer ( hdata
, buffer , 'next_buffer' )
337 class MergedBuffers ( list ):
338 """ A list of merged buffers, possibly of size 1. """
339 def __init__ ( self
, number
):
340 super ( MergedBuffers
, self
) .__ init
__ ()
343 def merge_buffer_list ( buffers
):
345 Group merged buffers together.
346 The output is a list of MergedBuffers.
348 if not buffers
: return []
350 for number
, buffer in buffers
:
351 if number
not in result
: result
[ number
] = MergedBuffers ( number
)
352 result
[ number
]. append ( buffer )
353 return result
. values ()
355 def sort_buffers ( hdata
, buffers
, rules
, helpers
, case_sensitive
):
356 for merged
in buffers
:
357 for buffer in merged
:
358 name
= weechat
. hdata_string ( hdata
, buffer , 'name' )
360 return sorted ( buffers
, key
= merged_sort_key ( rules
, helpers
, case_sensitive
))
362 def buffer_sort_key ( rules
, helpers
, case_sensitive
):
363 ''' Create a sort key function for a list of lists of merged buffers. '''
366 for helper_name
, helper
in sorted ( helpers
. items ()):
367 expanded
= weechat
. string_eval_expression ( helper
, {"buffer": buffer}
, {}, {}
)
368 extra_vars
[ helper_name
] = expanded
if case_sensitive
else casefold ( expanded
)
371 expanded
= weechat
. string_eval_expression ( rule
, {"buffer": buffer}
, extra_vars
, {})
372 result
. append ( expanded
if case_sensitive
else casefold ( expanded
))
377 def merged_sort_key ( rules
, helpers
, case_sensitive
):
378 buffer_key
= buffer_sort_key ( rules
, helpers
, case_sensitive
)
381 for buffer in merged
:
382 this
= buffer_key ( buffer )
383 if best
is None or this
< best
: best
= this
387 def apply_buffer_order ( buffers
):
388 ''' Sort the buffers in weechat according to the given order. '''
389 for i
, buffer in enumerate ( buffers
):
390 weechat
. buffer_set ( buffer [ 0 ], "number" , str ( i
+ 1 ))
392 def split_args ( args
, expected
, optional
= 0 ):
393 ''' Split an argument string in the desired number of arguments. '''
394 split
= args
. split ( ' ' , expected
- 1 )
395 if ( len ( split
) < expected
):
396 raise HumanReadableError ( 'Expected at least {0} arguments, got {1} .' . format ( expected
, len ( split
)))
397 return split
[:- 1 ] + pad ( split
[- 1 ]. split ( ' ' , optional
), optional
+ 1 , '' )
400 hdata
, buffers
= get_buffers ()
401 buffers
= merge_buffer_list ( buffers
)
402 buffers
= sort_buffers ( hdata
, buffers
, config
. rules
, config
. helpers
, config
. case_sensitive
)
403 apply_buffer_order ( buffers
)
405 def command_sort ( buffer , command
, args
):
406 ''' Sort the buffers and print a confirmation. '''
407 start
= perf_counter ()
409 elapsed
= perf_counter () - start
410 log ( "Finished sorting buffers in {0:.4f} seconds." . format ( elapsed
))
411 return weechat
. WEECHAT_RC_OK
413 def command_debug ( buffer , command
, args
):
414 hdata
, buffers
= get_buffers ()
415 buffers
= merge_buffer_list ( buffers
)
417 # Show evaluation results.
418 log ( 'Individual evaluation results:' )
419 start
= perf_counter ()
420 key
= buffer_sort_key ( config
. rules
, config
. helpers
, config
. case_sensitive
)
422 for merged
in buffers
:
423 for buffer in merged
:
424 fullname
= weechat
. hdata_string ( hdata
, buffer , 'full_name' )
425 results
. append (( fullname
, key ( buffer )))
426 elapsed
= perf_counter () - start
428 for fullname
, result
in results
:
429 fullname
= ensure_str ( fullname
)
430 result
= [ ensure_str ( x
) for x
in result
]
431 log ( ' {0} : {1} ' . format ( fullname
, result
))
432 log ( 'Computing evalutaion results took {0:.4f} seconds.' . format ( elapsed
))
434 return weechat
. WEECHAT_RC_OK
436 def command_rule_list ( buffer , command
, args
):
437 ''' Show the list of sorting rules. '''
438 output
= 'Sorting rules: \n '
439 for i
, rule
in enumerate ( config
. rules
):
440 output
+= ' {0} : {1} \n ' . format ( i
, rule
)
441 if not len ( config
. rules
):
442 output
+= ' No sorting rules configured. \n '
445 return weechat
. WEECHAT_RC_OK
448 def command_rule_add ( buffer , command
, args
):
449 ''' Add a rule to the rule list. '''
450 config
. rules
. append ( args
)
452 command_rule_list ( buffer , command
, '' )
454 return weechat
. WEECHAT_RC_OK
457 def command_rule_insert ( buffer , command
, args
):
458 ''' Insert a rule at the desired position in the rule list. '''
459 index
, rule
= split_args ( args
, 2 )
460 index
= parse_int ( index
, 'index' )
462 config
. rules
. insert ( index
, rule
)
464 command_rule_list ( buffer , command
, '' )
465 return weechat
. WEECHAT_RC_OK
468 def command_rule_update ( buffer , command
, args
):
469 ''' Update a rule in the rule list. '''
470 index
, rule
= split_args ( args
, 2 )
471 index
= parse_int ( index
, 'index' )
473 config
. rules
[ index
] = rule
475 command_rule_list ( buffer , command
, '' )
476 return weechat
. WEECHAT_RC_OK
479 def command_rule_delete ( buffer , command
, args
):
480 ''' Delete a rule from the rule list. '''
482 index
= parse_int ( index
, 'index' )
484 config
. rules
. pop ( index
)
486 command_rule_list ( buffer , command
, '' )
487 return weechat
. WEECHAT_RC_OK
490 def command_rule_move ( buffer , command
, args
):
491 ''' Move a rule to a new position. '''
492 index_a
, index_b
= split_args ( args
, 2 )
493 index_a
= parse_int ( index_a
, 'index' )
494 index_b
= parse_int ( index_b
, 'index' )
496 list_move ( config
. rules
, index_a
, index_b
)
498 command_rule_list ( buffer , command
, '' )
499 return weechat
. WEECHAT_RC_OK
502 def command_rule_swap ( buffer , command
, args
):
503 ''' Swap two rules. '''
504 index_a
, index_b
= split_args ( args
, 2 )
505 index_a
= parse_int ( index_a
, 'index' )
506 index_b
= parse_int ( index_b
, 'index' )
508 list_swap ( config
. rules
, index_a
, index_b
)
510 command_rule_list ( buffer , command
, '' )
511 return weechat
. WEECHAT_RC_OK
514 def command_helper_list ( buffer , command
, args
):
515 ''' Show the list of helpers. '''
516 output
= 'Helper variables: \n '
518 width
= max ( map ( lambda x
: len ( x
) if len ( x
) <= 30 else 0 , config
. helpers
. keys ()))
520 for name
, expression
in sorted ( config
. helpers
. items ()):
521 output
+= ' {0:>{width} }: {1} \n ' . format ( name
, expression
, width
= width
)
522 if not len ( config
. helpers
):
523 output
+= ' No helper variables configured.'
526 return weechat
. WEECHAT_RC_OK
529 def command_helper_set ( buffer , command
, args
):
530 ''' Add/update a helper to the helper list. '''
531 name
, expression
= split_args ( args
, 2 )
533 config
. helpers
[ name
] = expression
534 config
. save_helpers ()
535 command_helper_list ( buffer , command
, '' )
537 return weechat
. WEECHAT_RC_OK
539 def command_helper_delete ( buffer , command
, args
):
540 ''' Delete a helper from the helper list. '''
543 del config
. helpers
[ name
]
544 config
. save_helpers ()
545 command_helper_list ( buffer , command
, '' )
546 return weechat
. WEECHAT_RC_OK
549 def command_helper_rename ( buffer , command
, args
):
550 ''' Rename a helper to a new position. '''
551 old_name
, new_name
= split_args ( args
, 2 )
554 config
. helpers
[ new_name
] = config
. helpers
[ old_name
]
555 del config
. helpers
[ old_name
]
557 raise HumanReadableError ( 'No such helper: {0} ' . format ( old_name
))
558 config
. save_helpers ()
559 command_helper_list ( buffer , command
, '' )
560 return weechat
. WEECHAT_RC_OK
563 def command_helper_swap ( buffer , command
, args
):
564 ''' Swap two helpers. '''
565 a
, b
= split_args ( args
, 2 )
567 config
. helpers
[ b
], config
. helpers
[ a
] = config
. helpers
[ a
], config
. helpers
[ b
]
568 except KeyError as e
:
569 raise HumanReadableError ( 'No such helper: {0} ' . format ( e
. args
[ 0 ]))
571 config
. helpers
. swap ( index_a
, index_b
)
572 config
. save_helpers ()
573 command_helper_list ( buffer , command
, '' )
574 return weechat
. WEECHAT_RC_OK
576 def call_command ( buffer , command
, args
, subcommands
):
577 ''' Call a subccommand from a dictionary. '''
578 subcommand
, tail
= pad ( args
. split ( ' ' , 1 ), 2 , '' )
579 subcommand
= subcommand
. strip ()
580 if ( subcommand
== '' ):
581 child
= subcommands
. get ( ' ' )
583 command
= command
+ [ subcommand
]
584 child
= subcommands
. get ( subcommand
)
586 if isinstance ( child
, dict ):
587 return call_command ( buffer , command
, tail
, child
)
588 elif callable ( child
):
589 return child ( buffer , command
, tail
)
591 log ( ' {0} : command not found' . format ( ' ' . join ( command
)))
592 return weechat
. WEECHAT_RC_ERROR
594 def on_signal (* args
, ** kwargs
):
596 ''' Called whenever the buffer list changes. '''
597 if timer
is not None :
598 weechat
. unhook ( timer
)
600 weechat
. hook_timer ( config
. signal_delay
, 0 , 1 , "on_timeout" , "" )
601 return weechat
. WEECHAT_RC_OK
603 def on_timeout ( pointer
, remaining_calls
):
607 return weechat
. WEECHAT_RC_OK
610 # Unhook all signals and hook the new ones.
613 for signal
in config
. signals
:
614 hooks
. append ( weechat
. hook_signal ( signal
, 'on_signal' , '' ))
616 if config
. sort_on_config
:
619 def on_config_changed (* args
, ** kwargs
):
620 ''' Called whenever the configuration changes. '''
624 return weechat
. WEECHAT_RC_OK
627 if not args
: return None , None
631 for i
, c
in enumerate ( args
):
637 return result
, args
[ i
+ 1 :]
642 def parse_args ( args
, max = None ):
645 while max is None or i
< max :
646 arg
, args
= parse_arg ( args
)
647 if arg
is None : break
652 def on_info_replace ( pointer
, name
, arguments
):
653 arguments
, rest
= parse_args ( arguments
, 3 )
654 if rest
or len ( arguments
) < 3 :
655 log ( 'usage: $ {{info:{0} ,old,new,text}}' . format ( name
))
657 old
, new
, text
= arguments
659 return text
. replace ( old
, new
)
661 def on_info_order ( pointer
, name
, arguments
):
662 arguments
, rest
= parse_args ( arguments
)
663 if len ( arguments
) < 1 :
664 log ( 'usage: $ {{info:{0} ,value,first,second,third,...}}' . format ( name
))
669 if not keys
: return '0'
671 # Find the value in the keys (or '*' if we can't find it)
672 result
= list_find ( keys
, value
)
673 if result
is None : result
= list_find ( keys
, '*' )
674 if result
is None : result
= len ( keys
)
676 # Pad result with leading zero to make sure string sorting works.
677 width
= int ( math
. log10 ( len ( keys
))) + 1
678 return ' {0:0{1} }' . format ( result
, width
)
681 def on_autosort_command ( data
, buffer , args
):
682 ''' Called when the autosort command is invoked. '''
684 return call_command ( buffer , [ '/autosort' ], args
, {
686 'sort' : command_sort
,
687 'debug' : command_debug
,
690 ' ' : command_rule_list
,
691 'list' : command_rule_list
,
692 'add' : command_rule_add
,
693 'insert' : command_rule_insert
,
694 'update' : command_rule_update
,
695 'delete' : command_rule_delete
,
696 'move' : command_rule_move
,
697 'swap' : command_rule_swap
,
700 ' ' : command_helper_list
,
701 'list' : command_helper_list
,
702 'set' : command_helper_set
,
703 'delete' : command_helper_delete
,
704 'rename' : command_helper_rename
,
705 'swap' : command_helper_swap
,
708 except HumanReadableError
as e
:
710 return weechat
. WEECHAT_RC_ERROR
712 def add_completions ( completion
, words
):
714 weechat
. hook_completion_list_add ( completion
, word
, 0 , weechat
. WEECHAT_LIST_POS_END
)
716 def autosort_complete_rules ( words
, completion
):
718 add_completions ( completion
, [ 'add' , 'delete' , 'insert' , 'list' , 'move' , 'swap' , 'update' ])
719 if len ( words
) == 1 and words
[ 0 ] in ( 'delete' , 'insert' , 'move' , 'swap' , 'update' ):
720 add_completions ( completion
, map ( str , range ( len ( config
. rules
))))
721 if len ( words
) == 2 and words
[ 0 ] in ( 'move' , 'swap' ):
722 add_completions ( completion
, map ( str , range ( len ( config
. rules
))))
723 if len ( words
) == 2 and words
[ 0 ] in ( 'update' ):
725 add_completions ( completion
, [ config
. rules
[ int ( words
[ 1 ])]])
726 except KeyError : pass
727 except ValueError : pass
729 add_completions ( completion
, [ '' ])
730 return weechat
. WEECHAT_RC_OK
732 def autosort_complete_helpers ( words
, completion
):
734 add_completions ( completion
, [ 'delete' , 'list' , 'rename' , 'set' , 'swap' ])
735 elif len ( words
) == 1 and words
[ 0 ] in ( 'delete' , 'rename' , 'set' , 'swap' ):
736 add_completions ( completion
, sorted ( config
. helpers
. keys ()))
737 elif len ( words
) == 2 and words
[ 0 ] == 'swap' :
738 add_completions ( completion
, sorted ( config
. helpers
. keys ()))
739 elif len ( words
) == 2 and words
[ 0 ] == 'rename' :
740 add_completions ( completion
, sorted ( config
. helpers
. keys ()))
741 elif len ( words
) == 2 and words
[ 0 ] == 'set' :
743 add_completions ( completion
, [ config
. helpers
[ words
[ 1 ]]])
744 except KeyError : pass
745 return weechat
. WEECHAT_RC_OK
747 def on_autosort_complete ( data
, name
, buffer , completion
):
748 cmdline
= weechat
. buffer_get_string ( buffer , "input" )
749 cursor
= weechat
. buffer_get_integer ( buffer , "input_pos" )
750 prefix
= cmdline
[: cursor
]
751 words
= prefix
. split ()[ 1 :]
753 # If the current word isn't finished yet,
754 # ignore it for coming up with completion suggestions.
755 if prefix
[- 1 ] != ' ' : words
= words
[:- 1 ]
758 add_completions ( completion
, [ 'debug' , 'helpers' , 'rules' , 'sort' ])
759 elif words
[ 0 ] == 'rules' :
760 return autosort_complete_rules ( words
[ 1 :], completion
)
761 elif words
[ 0 ] == 'helpers' :
762 return autosort_complete_helpers ( words
[ 1 :], completion
)
763 return weechat
. WEECHAT_RC_OK
765 command_description
= r
''' {*white} # General commands {reset}
767 {*white} /autosort {brown} sort {reset}
768 Manually trigger the buffer sorting.
770 {*white} /autosort {brown} debug {reset}
771 Show the evaluation results of the sort rules for each buffer.
774 {*white} # Sorting rule commands {reset}
776 {*white} /autosort {brown} rules list {reset}
777 Print the list of sort rules.
779 {*white} /autosort {brown} rules add {cyan} <expression> {reset}
780 Add a new rule at the end of the list.
782 {*white} /autosort {brown} rules insert {cyan} <index> <expression> {reset}
783 Insert a new rule at the given index in the list.
785 {*white} /autosort {brown} rules update {cyan} <index> <expression> {reset}
786 Update a rule in the list with a new expression.
788 {*white} /autosort {brown} rules delete {cyan} <index>
789 Delete a rule from the list.
791 {*white} /autosort {brown} rules move {cyan} <index_from> <index_to> {reset}
792 Move a rule from one position in the list to another.
794 {*white} /autosort {brown} rules swap {cyan} <index_a> <index_b> {reset}
795 Swap two rules in the list
798 {*white} # Helper variable commands {reset}
800 {*white} /autosort {brown} helpers list
801 Print the list of helper variables.
803 {*white} /autosort {brown} helpers set {cyan} <name> <expression>
804 Add or update a helper variable with the given name.
806 {*white} /autosort {brown} helpers delete {cyan} <name>
807 Delete a helper variable.
809 {*white} /autosort {brown} helpers rename {cyan} <old_name> <new_name>
810 Rename a helper variable.
812 {*white} /autosort {brown} helpers swap {cyan} <name_a> <name_b>
813 Swap the expressions of two helper variables in the list.
816 {*white} # Description
817 Autosort is a weechat script to automatically keep your buffers sorted. The sort
818 order can be customized by defining your own sort rules, but the default should
819 be sane enough for most people. It can also group IRC channel/private buffers
820 under their server buffer if you like.
822 {*white} # Sort rules {reset}
823 Autosort evaluates a list of eval expressions (see {*default} /help eval {reset} ) and sorts the
824 buffers based on evaluated result. Earlier rules will be considered first. Only
825 if earlier rules produced identical results is the result of the next rule
826 considered for sorting purposes.
828 You can debug your sort rules with the ` {*default} /autosort debug {reset} ` command, which will
829 print the evaluation results of each rule for each buffer.
831 {*brown} NOTE: {reset} The sort rules for version 3 are not compatible with version 2 or vice
832 versa. You will have to manually port your old rules to version 3 if you have any.
834 {*white} # Helper variables {reset}
835 You may define helper variables for the main sort rules to keep your rules
836 readable. They can be used in the main sort rules as variables. For example,
837 a helper variable named ` {cyan} foo {reset} ` can be accessed in a main rule with the
838 string ` {cyan} $ {{foo} } {reset} `.
840 {*white} # Replacing substrings {reset}
841 There is no default method for replacing text inside eval expressions. However,
842 autosort adds a `replace` info hook that can be used inside eval expressions:
843 {cyan} $ {{info:autosort_replace,from,to,text} } {reset}
845 For example, to strip all hashes from a buffer name, you could write:
846 {cyan} $ {{info:autosort_replace,#,,${{buffer.name} }}} {reset}
848 You can escape commas and backslashes inside the arguments by prefixing them with
851 {*white} # Automatic or manual sorting {reset}
852 By default, autosort will automatically sort your buffer list whenever a buffer
853 is opened, merged, unmerged or renamed. This should keep your buffers sorted in
854 almost all situations. However, you may wish to change the list of signals that
855 cause your buffer list to be sorted. Simply edit the ` {cyan} autosort.sorting.signals {reset} `
856 option to add or remove any signal you like.
858 If you remove all signals you can still sort your buffers manually with the
859 ` {*default} /autosort sort {reset} ` command. To prevent all automatic sorting, the option
860 ` {cyan} autosort.sorting.sort_on_config_change {reset} ` should also be disabled.
862 {*white} # Recommended settings
863 For the best visual effect, consider setting the following options:
864 {*white} /set {cyan} irc.look.server_buffer {reset} {brown} independent {reset}
865 {*white} /set {cyan} buffers.look.indenting {reset} {brown} on {reset}
867 The first setting allows server buffers to be sorted independently, which is
868 needed to create a hierarchical tree view of the server and channel buffers.
869 The second one indents channel and private buffers in the buffer list of the
870 ` {*default} buffers.pl {reset} ` script.
872 If you are using the {*default} buflist {reset} plugin you can (ab)use Unicode to draw a tree
873 structure with the following setting (modify to suit your need):
874 {*white} /set {cyan} buflist.format.indent {brown} "$ {{color:237} }$ {{if:${{buffer.next_buffer.local_variables.type} }=~^(channel|private)$?├─:└─}}" {reset}
877 command_completion
= ' %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) '
879 info_replace_description
= 'Replace all occurences of `from` with `to` in the string `text`.'
880 info_replace_arguments
= 'from,to,text'
882 info_order_description
= (
883 'Get a zero padded index of a value in a list of possible values.'
884 'If the value is not found, the index for `*` is returned.'
885 'If there is no `*` in the list, the highest index + 1 is returned.'
887 info_order_arguments
= 'value,first,second,third,...'
890 if weechat
. register ( SCRIPT_NAME
, SCRIPT_AUTHOR
, SCRIPT_VERSION
, SCRIPT_LICENSE
, SCRIPT_DESC
, "" , "" ):
891 config
= Config ( 'autosort' )
894 'default' : weechat
. color ( 'default' ),
895 'reset' : weechat
. color ( 'reset' ),
896 'black' : weechat
. color ( 'black' ),
897 'red' : weechat
. color ( 'red' ),
898 'green' : weechat
. color ( 'green' ),
899 'brown' : weechat
. color ( 'brown' ),
900 'yellow' : weechat
. color ( 'yellow' ),
901 'blue' : weechat
. color ( 'blue' ),
902 'magenta' : weechat
. color ( 'magenta' ),
903 'cyan' : weechat
. color ( 'cyan' ),
904 'white' : weechat
. color ( 'white' ),
905 '*default' : weechat
. color ( '*default' ),
906 '*black' : weechat
. color ( '*black' ),
907 '*red' : weechat
. color ( '*red' ),
908 '*green' : weechat
. color ( '*green' ),
909 '*brown' : weechat
. color ( '*brown' ),
910 '*yellow' : weechat
. color ( '*yellow' ),
911 '*blue' : weechat
. color ( '*blue' ),
912 '*magenta' : weechat
. color ( '*magenta' ),
913 '*cyan' : weechat
. color ( '*cyan' ),
914 '*white' : weechat
. color ( '*white' ),
917 weechat
. hook_config ( 'autosort.*' , 'on_config_changed' , '' )
918 weechat
. hook_completion ( 'plugin_autosort' , '' , 'on_autosort_complete' , '' )
919 weechat
. hook_command ( 'autosort' , command_description
. format (** colors
), '' , '' , command_completion
, 'on_autosort_command' , '' )
920 weechat
. hook_info ( 'autosort_replace' , info_replace_description
, info_replace_arguments
, 'on_info_replace' , '' )
921 weechat
. hook_info ( 'autosort_order' , info_order_description
, info_order_arguments
, 'on_info_order' , '' )