1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2009-2014 Sébastien Helleu <flashcode@flashtux.org>
4 # Copyright (C) 2010 m4v <lambdae2@gmail.com>
5 # Copyright (C) 2011 stfn <stfnmd@googlemail.com>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # 2019-07-11, Simmo Saan <simmo.saan@gmail.com>
25 # version 2.6: fix detection of "/input search_text_here"
26 # 2017-04-01, Sébastien Helleu <flashcode@flashtux.org>:
27 # version 2.5: add option "buffer_number"
28 # 2017-03-02, Sébastien Helleu <flashcode@flashtux.org>:
29 # version 2.4: fix syntax and indentation error
30 # 2017-02-25, Simmo Saan <simmo.saan@gmail.com>
31 # version 2.3: fix fuzzy search breaking buffer number search display
32 # 2016-01-28, ylambda <ylambda@koalabeast.com>
33 # version 2.2: add option "fuzzy_search"
34 # 2015-11-12, nils_2 <weechatter@arcor.de>
35 # version 2.1: fix problem with buffer short_name "weechat", using option
36 # "use_core_instead_weechat", see:
37 # https://github.com/weechat/weechat/issues/574
38 # 2014-05-12, Sébastien Helleu <flashcode@flashtux.org>:
39 # version 2.0: add help on options, replace option "sort_by_activity" by
40 # "sort" (add sort by name and first match at beginning of
41 # name and by number), PEP8 compliance
42 # 2012-11-26, Nei <anti.teamidiot.de>
43 # version 1.9: add auto_jump option to automatically go to buffer when it
44 # is uniquely selected
45 # 2012-09-17, Sébastien Helleu <flashcode@flashtux.org>:
46 # version 1.8: fix jump to non-active merged buffers (jump with buffer name
48 # 2012-01-03 nils_2 <weechatter@arcor.de>
49 # version 1.7: add option "use_core_instead_weechat"
50 # 2012-01-03, Sébastien Helleu <flashcode@flashtux.org>:
51 # version 1.6: make script compatible with Python 3.x
52 # 2011-08-24, stfn <stfnmd@googlemail.com>:
53 # version 1.5: /go with name argument jumps directly to buffer
54 # Remember cursor position in buffer input
55 # 2011-05-31, Elián Hanisch <lambdae2@gmail.com>:
56 # version 1.4: Sort list of buffers by activity.
57 # 2011-04-25, Sébastien Helleu <flashcode@flashtux.org>:
58 # version 1.3: add info "go_running" (used by script input_lock.rb)
59 # 2010-11-01, Sébastien Helleu <flashcode@flashtux.org>:
60 # version 1.2: use high priority for hooks to prevent conflict with other
61 # plugins/scripts (WeeChat >= 0.3.4 only)
62 # 2010-03-25, Elián Hanisch <lambdae2@gmail.com>:
63 # version 1.1: use a space to match the end of a string
64 # 2009-11-16, Sébastien Helleu <flashcode@flashtux.org>:
65 # version 1.0: add new option to display short names
66 # 2009-06-15, Sébastien Helleu <flashcode@flashtux.org>:
67 # version 0.9: fix typo in /help go with command /key
68 # 2009-05-16, Sébastien Helleu <flashcode@flashtux.org>:
69 # version 0.8: search buffer by number, fix bug when window is split
70 # 2009-05-03, Sébastien Helleu <flashcode@flashtux.org>:
71 # version 0.7: eat tab key (do not complete input, just move buffer
73 # 2009-05-02, Sébastien Helleu <flashcode@flashtux.org>:
74 # version 0.6: sync with last API changes
75 # 2009-03-22, Sébastien Helleu <flashcode@flashtux.org>:
76 # version 0.5: update modifier signal name for input text display,
77 # fix arguments for function string_remove_color
78 # 2009-02-18, Sébastien Helleu <flashcode@flashtux.org>:
79 # version 0.4: do not hook command and init options if register failed
80 # 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
81 # version 0.3: case insensitive search for buffers names
82 # 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
83 # version 0.2: add help about Tab key
84 # 2009-02-08, Sébastien Helleu <flashcode@flashtux.org>:
85 # version 0.1: initial release
89 Quick jump to buffers.
90 (this script requires WeeChat 0.3.0 or newer)
93 from __future__
import print_function
96 SCRIPT_AUTHOR
= 'Sébastien Helleu <flashcode@flashtux.org>'
97 SCRIPT_VERSION
= '2.6'
98 SCRIPT_LICENSE
= 'GPL3'
99 SCRIPT_DESC
= 'Quick jump to buffers'
101 SCRIPT_COMMAND
= 'go'
108 print('This script must be run under WeeChat.')
109 print('Get WeeChat now at: http://www.weechat.org/')
118 'color for buffer number (not selected)'),
119 'color_number_selected': (
121 'color for selected buffer number'),
124 'color for buffer name (not selected)'),
125 'color_name_selected': (
127 'color for a selected buffer name'),
128 'color_name_highlight': (
130 'color for highlight in buffer name (not selected)'),
131 'color_name_highlight_selected': (
133 'color for highlight in a selected buffer name'),
136 'message to display before list of buffers'),
139 'display and search in short names instead of buffer name'),
142 'comma-separated list of keys to sort buffers '
143 '(the order is important, sorts are performed in the given order): '
144 'name = sort by name (or short name), ',
145 'hotlist = sort by hotlist order, '
146 'number = first match a buffer number before digits in name, '
147 'beginning = first match at beginning of names (or short names); '
148 'the default sort of buffers is by numbers'),
149 'use_core_instead_weechat': (
151 'use name "core" instead of "weechat" for core buffer'),
154 'automatically jump to buffer when it is uniquely selected'),
157 'search buffer matches using approximation'),
160 'display buffer number'),
165 'input': ('/input *', 'go_command_run_input'),
166 'buffer': ('/buffer *', 'go_command_run_buffer'),
167 'window': ('/window *', 'go_command_run_window'),
171 # input before command /go (we'll restore it later)
175 # last user input (if changed, we'll update list of matching buffers)
183 def go_option_enabled(option
):
184 """Checks if a boolean script option is enabled or not."""
185 return weechat
.config_string_to_boolean(weechat
.config_get_plugin(option
))
188 def go_info_running(data
, info_name
, arguments
):
189 """Returns "1" if go is running, otherwise "0"."""
190 return '1' if 'modifier' in hooks
else '0'
193 def go_unhook_one(hook
):
194 """Unhook something hooked by this script."""
197 weechat
.unhook(hooks
[hook
])
203 go_unhook_one('modifier')
204 for hook
in HOOK_COMMAND_RUN
:
209 """Hook command_run and modifier."""
212 version
= weechat
.info_get('version_number', '') or 0
213 # use high priority for hook to prevent conflict with other plugins/scripts
214 # (WeeChat >= 0.3.4 only)
215 if int(version
) >= 0x00030400:
217 for hook
, value
in HOOK_COMMAND_RUN
.items():
218 if hook
not in hooks
:
219 hooks
[hook
] = weechat
.hook_command_run(
220 '%s%s' % (priority
, value
[0]),
222 if 'modifier' not in hooks
:
223 hooks
['modifier'] = weechat
.hook_modifier(
224 'input_text_display_with_cursor', 'go_input_modifier', '')
228 """Start go on buffer."""
229 global saved_input
, saved_input_pos
, old_input
, buffers_pos
231 saved_input
= weechat
.buffer_get_string(buf
, 'input')
232 saved_input_pos
= weechat
.buffer_get_integer(buf
, 'input_pos')
233 weechat
.buffer_set(buf
, 'input', '')
239 """End go on buffer."""
240 global saved_input
, saved_input_pos
, old_input
242 weechat
.buffer_set(buf
, 'input', saved_input
)
243 weechat
.buffer_set(buf
, 'input_pos', str(saved_input_pos
))
247 def go_match_beginning(buf
, string
):
248 """Check if a string matches the beginning of buffer name/short name."""
251 esc_str
= re
.escape(string
)
252 if re
.search(r
'^#?' + esc_str
, buf
['name']) \
253 or re
.search(r
'^#?' + esc_str
, buf
['short_name']):
258 def go_match_fuzzy(name
, string
):
259 """Check if string matches name using approximation."""
264 string_len
= len(string
)
266 if string_len
> name_len
:
268 if name_len
== string_len
:
269 return name
== string
271 # Attempt to match all chars somewhere in name
273 for i
, char
in enumerate(string
):
274 index
= name
.find(char
, prev_index
+1)
281 def go_now(buf
, args
):
282 """Go to buffer specified by args."""
283 listbuf
= go_matching_buffers(args
)
287 # prefer buffer that matches at beginning (if option is enabled)
288 if 'beginning' in weechat
.config_get_plugin('sort').split(','):
289 for index
in range(len(listbuf
)):
290 if go_match_beginning(listbuf
[index
], args
):
292 '/buffer ' + str(listbuf
[index
]['full_name']))
295 # jump to first buffer in matching buffers by default
296 weechat
.command(buf
, '/buffer ' + str(listbuf
[0]['full_name']))
299 def go_cmd(data
, buf
, args
):
300 """Command "/go": just hook what we need."""
304 elif 'modifier' in hooks
:
308 return weechat
.WEECHAT_RC_OK
311 def go_matching_buffers(strinput
):
312 """Return a list with buffers matching user input."""
315 if len(strinput
) == 0:
317 strinput
= strinput
.lower()
318 infolist
= weechat
.infolist_get('buffer', '', '')
319 while weechat
.infolist_next(infolist
):
320 short_name
= weechat
.infolist_string(infolist
, 'short_name')
321 if go_option_enabled('short_name'):
322 name
= weechat
.infolist_string(infolist
, 'short_name')
324 name
= weechat
.infolist_string(infolist
, 'name')
325 if name
== 'weechat' \
326 and go_option_enabled('use_core_instead_weechat') \
327 and weechat
.infolist_string(infolist
, 'plugin_name') == 'core':
329 number
= weechat
.infolist_integer(infolist
, 'number')
330 full_name
= weechat
.infolist_string(infolist
, 'full_name')
332 full_name
= '%s.%s' % (
333 weechat
.infolist_string(infolist
, 'plugin_name'),
334 weechat
.infolist_string(infolist
, 'name'))
335 pointer
= weechat
.infolist_pointer(infolist
, 'pointer')
336 matching
= name
.lower().find(strinput
) >= 0
337 if not matching
and strinput
[-1] == ' ':
338 matching
= name
.lower().endswith(strinput
.strip())
339 if not matching
and go_option_enabled('fuzzy_search'):
340 matching
= go_match_fuzzy(name
.lower(), strinput
)
341 if not matching
and strinput
.isdigit():
342 matching
= str(number
).startswith(strinput
)
343 if len(strinput
) == 0 or matching
:
346 'short_name': short_name
,
348 'full_name': full_name
,
351 weechat
.infolist_free(infolist
)
355 infolist
= weechat
.infolist_get('hotlist', '', '')
356 while weechat
.infolist_next(infolist
):
358 weechat
.infolist_pointer(infolist
, 'buffer_pointer'))
359 weechat
.infolist_free(infolist
)
360 last_index_hotlist
= len(hotlist
)
363 """Sort buffers by name (or short name)."""
366 def _sort_hotlist(buf
):
367 """Sort buffers by hotlist order."""
369 return hotlist
.index(buf
['pointer'])
371 # not in hotlist, always last.
372 return last_index_hotlist
374 def _sort_match_number(buf
):
375 """Sort buffers by match on number."""
376 return 0 if str(buf
['number']) == strinput
else 1
378 def _sort_match_beginning(buf
):
379 """Sort buffers by match at beginning."""
380 return 0 if go_match_beginning(buf
, strinput
) else 1
384 'hotlist': _sort_hotlist
,
385 'number': _sort_match_number
,
386 'beginning': _sort_match_beginning
,
389 for key
in weechat
.config_get_plugin('sort').split(','):
391 listbuf
= sorted(listbuf
, key
=funcs
[key
])
394 index
= [i
for i
, buf
in enumerate(listbuf
)
395 if buf
['pointer'] == weechat
.current_buffer()]
397 buffers_pos
= index
[0]
402 def go_buffers_to_string(listbuf
, pos
, strinput
):
403 """Return string built with list of buffers found (matching user input)."""
405 strinput
= strinput
.lower()
406 for i
in range(len(listbuf
)):
407 selected
= '_selected' if i
== pos
else ''
408 buffer_name
= listbuf
[i
]['name']
409 index
= buffer_name
.lower().find(strinput
)
411 index2
= index
+ len(strinput
)
412 name
= '%s%s%s%s%s' % (
414 weechat
.color(weechat
.config_get_plugin(
415 'color_name_highlight' + selected
)),
416 buffer_name
[index
:index2
],
417 weechat
.color(weechat
.config_get_plugin(
418 'color_name' + selected
)),
419 buffer_name
[index2
:])
420 elif go_option_enabled("fuzzy_search") and \
421 go_match_fuzzy(buffer_name
.lower(), strinput
):
424 for char
in strinput
.lower():
425 index
= buffer_name
.lower().find(char
, prev_index
+1)
427 name
+= buffer_name
[:index
]
428 name
+= weechat
.color(weechat
.config_get_plugin(
429 'color_name_highlight' + selected
))
430 if prev_index
>= 0 and index
> prev_index
+1:
431 name
+= weechat
.color(weechat
.config_get_plugin(
432 'color_name' + selected
))
433 name
+= buffer_name
[prev_index
+1:index
]
434 name
+= weechat
.color(weechat
.config_get_plugin(
435 'color_name_highlight' + selected
))
436 name
+= buffer_name
[index
]
439 name
+= weechat
.color(weechat
.config_get_plugin(
440 'color_name' + selected
))
441 name
+= buffer_name
[prev_index
+1:]
445 if go_option_enabled('buffer_number'):
447 weechat
.color(weechat
.config_get_plugin(
448 'color_number' + selected
)),
449 str(listbuf
[i
]['number']))
450 string
+= '%s%s%s' % (
451 weechat
.color(weechat
.config_get_plugin(
452 'color_name' + selected
)),
454 weechat
.color('reset'))
455 return ' ' + string
if string
else ''
458 def go_input_modifier(data
, modifier
, modifier_data
, string
):
459 """This modifier is called when input text item is built by WeeChat.
461 This is commonly called after changes in input or cursor move: it builds
462 a new input with prefix ("Go to:"), and suffix (list of buffers found).
464 global old_input
, buffers
, buffers_pos
465 if modifier_data
!= weechat
.current_buffer():
468 new_input
= weechat
.string_remove_color(string
, '')
469 new_input
= new_input
.lstrip()
470 if old_input
is None or new_input
!= old_input
:
471 old_buffers
= buffers
472 buffers
= go_matching_buffers(new_input
)
473 if buffers
!= old_buffers
and len(new_input
) > 0:
474 if len(buffers
) == 1 and go_option_enabled('auto_jump'):
475 weechat
.command(modifier_data
, '/wait 1ms /input return')
477 old_input
= new_input
478 names
= go_buffers_to_string(buffers
, buffers_pos
, new_input
.strip())
479 return weechat
.config_get_plugin('message') + string
+ names
482 def go_command_run_input(data
, buf
, command
):
483 """Function called when a command "/input xxx" is run."""
484 global buffers
, buffers_pos
485 if command
.startswith('/input search_text') or command
.startswith('/input jump'):
486 # search text or jump to another buffer is forbidden now
487 return weechat
.WEECHAT_RC_OK_EAT
488 elif command
== '/input complete_next':
489 # choose next buffer in list
491 if buffers_pos
>= len(buffers
):
493 weechat
.hook_signal_send('input_text_changed',
494 weechat
.WEECHAT_HOOK_SIGNAL_STRING
, '')
495 return weechat
.WEECHAT_RC_OK_EAT
496 elif command
== '/input complete_previous':
497 # choose previous buffer in list
500 buffers_pos
= len(buffers
) - 1
501 weechat
.hook_signal_send('input_text_changed',
502 weechat
.WEECHAT_HOOK_SIGNAL_STRING
, '')
503 return weechat
.WEECHAT_RC_OK_EAT
504 elif command
== '/input return':
505 # switch to selected buffer (if any)
509 buf
, '/buffer ' + str(buffers
[buffers_pos
]['full_name']))
510 return weechat
.WEECHAT_RC_OK_EAT
511 return weechat
.WEECHAT_RC_OK
514 def go_command_run_buffer(data
, buf
, command
):
515 """Function called when a command "/buffer xxx" is run."""
516 return weechat
.WEECHAT_RC_OK_EAT
519 def go_command_run_window(data
, buf
, command
):
520 """Function called when a command "/window xxx" is run."""
521 return weechat
.WEECHAT_RC_OK_EAT
524 def go_unload_script():
525 """Function called when script is unloaded."""
527 return weechat
.WEECHAT_RC_OK
532 if not weechat
.register(SCRIPT_NAME
, SCRIPT_AUTHOR
, SCRIPT_VERSION
,
533 SCRIPT_LICENSE
, SCRIPT_DESC
,
534 'go_unload_script', ''):
536 weechat
.hook_command(
538 'Quick jump to buffers', '[name]',
539 'name: directly jump to buffer by name (without argument, list is '
541 'You can bind command to a key, for example:\n'
542 ' /key bind meta-g /go\n\n'
543 'You can use completion key (commonly Tab and shift-Tab) to select '
544 'next/previous buffer in list.',
548 # set default settings
549 version
= weechat
.info_get('version_number', '') or 0
550 for option
, value
in SETTINGS
.items():
551 if not weechat
.config_is_set_plugin(option
):
552 weechat
.config_set_plugin(option
, value
[0])
553 if int(version
) >= 0x00030500:
554 weechat
.config_set_desc_plugin(
555 option
, '%s (default: "%s")' % (value
[1], value
[0]))
556 weechat
.hook_info('go_running',
557 'Return "1" if go is running, otherwise "0"',
559 'go_info_running', '')
562 if __name__
== "__main__" and IMPORT_OK
: