]> git.rmz.io Git - dotfiles.git/blob - weechat/python/go.py
bin: support ipv4 and ipv6 in whatsmyip
[dotfiles.git] / weechat / python / go.py
1 # -*- coding: utf-8 -*-
2 #
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>
6 #
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.
11 #
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.
16 #
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/>.
19 #
20
21 #
22 # History:
23 #
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
47 # instead of number)
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
72 # pointer)
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
86 #
87
88 """
89 Quick jump to buffers.
90 (this script requires WeeChat 0.3.0 or newer)
91 """
92
93 from __future__ import print_function
94
95 SCRIPT_NAME = 'go'
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'
100
101 SCRIPT_COMMAND = 'go'
102
103 IMPORT_OK = True
104
105 try:
106 import weechat
107 except ImportError:
108 print('This script must be run under WeeChat.')
109 print('Get WeeChat now at: http://www.weechat.org/')
110 IMPORT_OK = False
111
112 import re
113
114 # script options
115 SETTINGS = {
116 'color_number': (
117 'yellow,magenta',
118 'color for buffer number (not selected)'),
119 'color_number_selected': (
120 'yellow,red',
121 'color for selected buffer number'),
122 'color_name': (
123 'black,cyan',
124 'color for buffer name (not selected)'),
125 'color_name_selected': (
126 'black,brown',
127 'color for a selected buffer name'),
128 'color_name_highlight': (
129 'red,cyan',
130 'color for highlight in buffer name (not selected)'),
131 'color_name_highlight_selected': (
132 'red,brown',
133 'color for highlight in a selected buffer name'),
134 'message': (
135 'Go to: ',
136 'message to display before list of buffers'),
137 'short_name': (
138 'off',
139 'display and search in short names instead of buffer name'),
140 'sort': (
141 'number,beginning',
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': (
150 'off',
151 'use name "core" instead of "weechat" for core buffer'),
152 'auto_jump': (
153 'off',
154 'automatically jump to buffer when it is uniquely selected'),
155 'fuzzy_search': (
156 'off',
157 'search buffer matches using approximation'),
158 'buffer_number': (
159 'on',
160 'display buffer number'),
161 }
162
163 # hooks management
164 HOOK_COMMAND_RUN = {
165 'input': ('/input *', 'go_command_run_input'),
166 'buffer': ('/buffer *', 'go_command_run_buffer'),
167 'window': ('/window *', 'go_command_run_window'),
168 }
169 hooks = {}
170
171 # input before command /go (we'll restore it later)
172 saved_input = ''
173 saved_input_pos = 0
174
175 # last user input (if changed, we'll update list of matching buffers)
176 old_input = None
177
178 # matching buffers
179 buffers = []
180 buffers_pos = 0
181
182
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))
186
187
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'
191
192
193 def go_unhook_one(hook):
194 """Unhook something hooked by this script."""
195 global hooks
196 if hook in hooks:
197 weechat.unhook(hooks[hook])
198 del hooks[hook]
199
200
201 def go_unhook_all():
202 """Unhook all."""
203 go_unhook_one('modifier')
204 for hook in HOOK_COMMAND_RUN:
205 go_unhook_one(hook)
206
207
208 def go_hook_all():
209 """Hook command_run and modifier."""
210 global hooks
211 priority = ''
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:
216 priority = '2000|'
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]),
221 value[1], '')
222 if 'modifier' not in hooks:
223 hooks['modifier'] = weechat.hook_modifier(
224 'input_text_display_with_cursor', 'go_input_modifier', '')
225
226
227 def go_start(buf):
228 """Start go on buffer."""
229 global saved_input, saved_input_pos, old_input, buffers_pos
230 go_hook_all()
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', '')
234 old_input = None
235 buffers_pos = 0
236
237
238 def go_end(buf):
239 """End go on buffer."""
240 global saved_input, saved_input_pos, old_input
241 go_unhook_all()
242 weechat.buffer_set(buf, 'input', saved_input)
243 weechat.buffer_set(buf, 'input_pos', str(saved_input_pos))
244 old_input = None
245
246
247 def go_match_beginning(buf, string):
248 """Check if a string matches the beginning of buffer name/short name."""
249 if not string:
250 return False
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']):
254 return True
255 return False
256
257
258 def go_match_fuzzy(name, string):
259 """Check if string matches name using approximation."""
260 if not string:
261 return False
262
263 name_len = len(name)
264 string_len = len(string)
265
266 if string_len > name_len:
267 return False
268 if name_len == string_len:
269 return name == string
270
271 # Attempt to match all chars somewhere in name
272 prev_index = -1
273 for i, char in enumerate(string):
274 index = name.find(char, prev_index+1)
275 if index == -1:
276 return False
277 prev_index = index
278 return True
279
280
281 def go_now(buf, args):
282 """Go to buffer specified by args."""
283 listbuf = go_matching_buffers(args)
284 if not listbuf:
285 return
286
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):
291 weechat.command(buf,
292 '/buffer ' + str(listbuf[index]['full_name']))
293 return
294
295 # jump to first buffer in matching buffers by default
296 weechat.command(buf, '/buffer ' + str(listbuf[0]['full_name']))
297
298
299 def go_cmd(data, buf, args):
300 """Command "/go": just hook what we need."""
301 global hooks
302 if args:
303 go_now(buf, args)
304 elif 'modifier' in hooks:
305 go_end(buf)
306 else:
307 go_start(buf)
308 return weechat.WEECHAT_RC_OK
309
310
311 def go_matching_buffers(strinput):
312 """Return a list with buffers matching user input."""
313 global buffers_pos
314 listbuf = []
315 if len(strinput) == 0:
316 buffers_pos = 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')
323 else:
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':
328 name = 'core'
329 number = weechat.infolist_integer(infolist, 'number')
330 full_name = weechat.infolist_string(infolist, 'full_name')
331 if not 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:
344 listbuf.append({
345 'number': number,
346 'short_name': short_name,
347 'name': name,
348 'full_name': full_name,
349 'pointer': pointer,
350 })
351 weechat.infolist_free(infolist)
352
353 # sort buffers
354 hotlist = []
355 infolist = weechat.infolist_get('hotlist', '', '')
356 while weechat.infolist_next(infolist):
357 hotlist.append(
358 weechat.infolist_pointer(infolist, 'buffer_pointer'))
359 weechat.infolist_free(infolist)
360 last_index_hotlist = len(hotlist)
361
362 def _sort_name(buf):
363 """Sort buffers by name (or short name)."""
364 return buf['name']
365
366 def _sort_hotlist(buf):
367 """Sort buffers by hotlist order."""
368 try:
369 return hotlist.index(buf['pointer'])
370 except ValueError:
371 # not in hotlist, always last.
372 return last_index_hotlist
373
374 def _sort_match_number(buf):
375 """Sort buffers by match on number."""
376 return 0 if str(buf['number']) == strinput else 1
377
378 def _sort_match_beginning(buf):
379 """Sort buffers by match at beginning."""
380 return 0 if go_match_beginning(buf, strinput) else 1
381
382 funcs = {
383 'name': _sort_name,
384 'hotlist': _sort_hotlist,
385 'number': _sort_match_number,
386 'beginning': _sort_match_beginning,
387 }
388
389 for key in weechat.config_get_plugin('sort').split(','):
390 if key in funcs:
391 listbuf = sorted(listbuf, key=funcs[key])
392
393 if not strinput:
394 index = [i for i, buf in enumerate(listbuf)
395 if buf['pointer'] == weechat.current_buffer()]
396 if index:
397 buffers_pos = index[0]
398
399 return listbuf
400
401
402 def go_buffers_to_string(listbuf, pos, strinput):
403 """Return string built with list of buffers found (matching user input)."""
404 string = ''
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)
410 if index >= 0:
411 index2 = index + len(strinput)
412 name = '%s%s%s%s%s' % (
413 buffer_name[:index],
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):
422 name = ""
423 prev_index = -1
424 for char in strinput.lower():
425 index = buffer_name.lower().find(char, prev_index+1)
426 if prev_index < 0:
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]
437 prev_index = index
438
439 name += weechat.color(weechat.config_get_plugin(
440 'color_name' + selected))
441 name += buffer_name[prev_index+1:]
442 else:
443 name = buffer_name
444 string += ' '
445 if go_option_enabled('buffer_number'):
446 string += '%s%s' % (
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)),
453 name,
454 weechat.color('reset'))
455 return ' ' + string if string else ''
456
457
458 def go_input_modifier(data, modifier, modifier_data, string):
459 """This modifier is called when input text item is built by WeeChat.
460
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).
463 """
464 global old_input, buffers, buffers_pos
465 if modifier_data != weechat.current_buffer():
466 return ''
467 names = ''
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')
476 buffers_pos = 0
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
480
481
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
490 buffers_pos += 1
491 if buffers_pos >= len(buffers):
492 buffers_pos = 0
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
498 buffers_pos -= 1
499 if buffers_pos < 0:
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)
506 go_end(buf)
507 if len(buffers) > 0:
508 weechat.command(
509 buf, '/buffer ' + str(buffers[buffers_pos]['full_name']))
510 return weechat.WEECHAT_RC_OK_EAT
511 return weechat.WEECHAT_RC_OK
512
513
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
517
518
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
522
523
524 def go_unload_script():
525 """Function called when script is unloaded."""
526 go_unhook_all()
527 return weechat.WEECHAT_RC_OK
528
529
530 def go_main():
531 """Entry point."""
532 if not weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
533 SCRIPT_LICENSE, SCRIPT_DESC,
534 'go_unload_script', ''):
535 return
536 weechat.hook_command(
537 SCRIPT_COMMAND,
538 'Quick jump to buffers', '[name]',
539 'name: directly jump to buffer by name (without argument, list is '
540 'displayed)\n\n'
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.',
545 '%(buffers_names)',
546 'go_cmd', '')
547
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"',
558 '',
559 'go_info_running', '')
560
561
562 if __name__ == "__main__" and IMPORT_OK:
563 go_main()