# -*- coding: utf-8 -*- # # Copyright (C) 2011-2018 Sébastien Helleu # Copyright (C) 2012 ArZa # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # # Contextual command line help for WeeChat. # (this script requires WeeChat 0.3.5 or newer) # # History: # # 2018-04-10, Sébastien Helleu : # version 0.5: fix infolist_time for WeeChat >= 2.2 (WeeChat returns a long # integer instead of a string), fix PEP8 errors # 2012-01-04, ArZa : # version 0.4: settings for right align and space before help # 2012-01-03, Sébastien Helleu : # version 0.3: make script compatible with Python 3.x # 2011-05-18, Sébastien Helleu : # version 0.2: add options for aliases, start on load, list of commands to # ignore; add default value in help of script options # 2011-05-15, Sébastien Helleu : # version 0.1: initial release # SCRIPT_NAME = 'cmd_help' SCRIPT_AUTHOR = 'Sébastien Helleu ' SCRIPT_VERSION = '0.5' SCRIPT_LICENSE = 'GPL3' SCRIPT_DESC = 'Contextual command line help' SCRIPT_COMMAND = 'cmd_help' import_ok = True try: import weechat except ImportError: print('This script must be run under WeeChat.') print('Get WeeChat now at: http://www.weechat.org/') import_ok = False try: import re import time except ImportError as message: print('Missing package(s) for %s: %s' % (SCRIPT_NAME, message)) import_ok = False cmdhelp_hooks = { 'modifier': '', 'timer': '', 'command_run': '', } cmdhelp_option_infolist = '' cmdhelp_option_infolist_fields = {} # script options cmdhelp_settings_default = { 'display_no_help': [ 'on', 'display "No help" when command is not found', ], 'start_on_load': [ 'off', 'auto start help when script is loaded', ], 'stop_on_enter': [ 'on', 'enter key stop help', ], 'timer': [ '0', ('number of seconds help is displayed (0 = display until help is ' 'toggled)'), ], 'prefix': [ '[', 'string displayed before help', ], 'suffix': [ ']', 'string displayed after help', ], 'format_option': [ '(${white:type}) ${description_nls}', ('format of help for options: free text with identifiers using ' 'format: ${name} or ${color:name}: color is a WeeChat ' 'color (optional), name is a field of infolist "option"'), ], 'max_options': [ '5', 'max number of options displayed in list', ], 'ignore_commands': [ 'map,me,die,restart', 'comma-separated list of commands (without leading "/") to ignore', ], 'color_alias': [ 'white', 'color for text "Alias"', ], 'color_alias_name': [ 'green', 'color for alias name', ], 'color_alias_value': [ 'green', 'color for alias value', ], 'color_delimiters': [ 'lightgreen', 'color for delimiters', ], 'color_no_help': [ 'red', 'color for text "No help"', ], 'color_list_count': [ 'white', 'color for number of commands/options in list found', ], 'color_list': [ 'green', 'color for list of commands/options', ], 'color_arguments': [ 'cyan', 'color for command arguments', ], 'color_option_name': [ 'yellow', 'color for name of option found (by adding "*" to option name)', ], 'color_option_help': [ 'brown', 'color for help on option', ], 'right_align': [ 'off', 'align help to right', ], 'right_padding': [ '15', 'padding to right when aligned to right', ], 'space': [ '2', 'minimum space before help', ], } cmdhelp_settings = {} def unhook(hooks): """Unhook something hooked by this script.""" global cmdhelp_hooks for hook in hooks: if cmdhelp_hooks[hook]: weechat.unhook(cmdhelp_hooks[hook]) cmdhelp_hooks[hook] = '' def config_cb(data, option, value): """Called when a script option is changed.""" global cmdhelp_settings, cmdhelp_hooks pos = option.rfind('.') if pos > 0: name = option[pos+1:] if name in cmdhelp_settings: cmdhelp_settings[name] = value if name == 'stop_on_enter': if value == 'on' and not cmdhelp_hooks['command_run']: cmdhelp_hooks['command_run'] = weechat.hook_command_run( '/input return', 'command_run_cb', '') elif value != 'on' and cmdhelp_hooks['command_run']: unhook(('command_run',)) return weechat.WEECHAT_RC_OK def command_run_cb(data, buffer, command): """Callback for "command_run" hook.""" global cmdhelp_hooks, cmdhelp_settings if cmdhelp_hooks['modifier'] and cmdhelp_settings['stop_on_enter'] == 'on': unhook(('timer', 'modifier')) return weechat.WEECHAT_RC_OK def format_option(match): """Replace ${xxx} by its value in option format.""" global cmdhelp_settings, cmdhelp_option_infolist global cmdhelp_option_infolist_fields string = match.group() end = string.find('}') if end < 0: return string field = string[2:end] color1 = '' color2 = '' pos = field.find(':') if pos: color1 = field[0:pos] field = field[pos+1:] if color1: color1 = weechat.color(color1) color2 = weechat.color(cmdhelp_settings['color_option_help']) fieldtype = cmdhelp_option_infolist_fields.get(field, '') if fieldtype == 'i': string = str(weechat.infolist_integer(cmdhelp_option_infolist, field)) elif fieldtype == 's': string = weechat.infolist_string(cmdhelp_option_infolist, field) elif fieldtype == 'p': string = weechat.infolist_pointer(cmdhelp_option_infolist, field) elif fieldtype == 't': date = weechat.infolist_time(cmdhelp_option_infolist, field) # since WeeChat 2.2, infolist_time returns a long integer instead of # a string if not isinstance(date, str): date = time.strftime('%F %T', time.localtime(int(date))) string = date return '%s%s%s' % (color1, string, color2) def get_option_list_and_desc(option, displayname): """Get list of options and description for option(s).""" global cmdhelp_settings, cmdhelp_option_infolist global cmdhelp_option_infolist_fields options = [] description = '' cmdhelp_option_infolist = weechat.infolist_get('option', '', option) if cmdhelp_option_infolist: cmdhelp_option_infolist_fields = {} while weechat.infolist_next(cmdhelp_option_infolist): options.append(weechat.infolist_string(cmdhelp_option_infolist, 'full_name')) if not description: fields = weechat.infolist_fields(cmdhelp_option_infolist) for field in fields.split(','): items = field.split(':', 1) if len(items) == 2: cmdhelp_option_infolist_fields[items[1]] = items[0] description = re.compile(r'\$\{[^\}]+\}').sub( format_option, cmdhelp_settings['format_option']) if displayname: description = '%s%s%s: %s' % ( weechat.color(cmdhelp_settings['color_option_name']), weechat.infolist_string(cmdhelp_option_infolist, 'full_name'), weechat.color(cmdhelp_settings['color_option_help']), description) weechat.infolist_free(cmdhelp_option_infolist) cmdhelp_option_infolist = '' cmdhelp_option_infolist_fields = {} return options, description def get_help_option(input_args): """Get help about option or values authorized for option.""" global cmdhelp_settings, cmdhelp_option_infolist global cmdhelp_option_infolist_fields pos = input_args.find(' ') if pos > 0: option = input_args[0:pos] else: option = input_args options, description = get_option_list_and_desc(option, False) if not options and not description: options, description = get_option_list_and_desc('%s*' % option, True) if len(options) > 1: try: max_options = int(cmdhelp_settings['max_options']) except ValueError: max_options = 5 if len(options) > max_options: text = '%s...' % ', '.join(options[0:max_options]) else: text = ', '.join(options) return '%s%d options: %s%s' % ( weechat.color(cmdhelp_settings['color_list_count']), len(options), weechat.color(cmdhelp_settings['color_list']), text) if description: return '%s%s' % (weechat.color(cmdhelp_settings['color_option_help']), description) return '%sNo help for option %s' % ( weechat.color(cmdhelp_settings['color_no_help']), option) def get_command_arguments(input_args, cmd_args): """Get command arguments according to command arguments given in input.""" partial = '' input_firstarg = input_args.split(' ', 1)[0].lower() items = cmd_args.split('||') for item in items: item = item.strip() firstword = item.split(' ')[0] items2 = firstword.split('|') for item2 in items2: item2 = item2.strip().lower() if item2 == input_firstarg: return item if not partial and item2.startswith(input_firstarg): partial = item if partial: return partial return cmd_args def get_help_command(plugin, input_cmd, input_args): """Get help for command in input.""" global cmdhelp_settings if input_cmd == 'set' and input_args: return get_help_option(input_args) infolist = weechat.infolist_get('hook', '', 'command,%s' % input_cmd) cmd_plugin_name = '' cmd_command = '' cmd_args = '' cmd_desc = '' while weechat.infolist_next(infolist): cmd_plugin_name = (weechat.infolist_string(infolist, 'plugin_name') or 'core') cmd_command = weechat.infolist_string(infolist, 'command') cmd_args = weechat.infolist_string(infolist, 'args_nls') cmd_desc = weechat.infolist_string(infolist, 'description') if weechat.infolist_pointer(infolist, 'plugin') == plugin: break weechat.infolist_free(infolist) if cmd_plugin_name == 'alias': return '%sAlias %s%s%s => %s%s' % ( weechat.color(cmdhelp_settings['color_alias']), weechat.color(cmdhelp_settings['color_alias_name']), cmd_command, weechat.color(cmdhelp_settings['color_alias']), weechat.color(cmdhelp_settings['color_alias_value']), cmd_desc, ) if input_args: cmd_args = get_command_arguments(input_args, cmd_args) if not cmd_args: return None return '%s%s' % (weechat.color(cmdhelp_settings['color_arguments']), cmd_args) def get_list_commands(plugin, input_cmd, input_args): """Get list of commands (beginning with current input).""" global cmdhelp_settings infolist = weechat.infolist_get('hook', '', 'command,%s*' % input_cmd) commands = [] plugin_names = [] while weechat.infolist_next(infolist): commands.append(weechat.infolist_string(infolist, 'command')) plugin_names.append( weechat.infolist_string(infolist, 'plugin_name') or 'core') weechat.infolist_free(infolist) if commands: if len(commands) > 1 or commands[0].lower() != input_cmd.lower(): commands2 = [] for index, command in enumerate(commands): if commands.count(command) > 1: commands2.append('%s(%s)' % (command, plugin_names[index])) else: commands2.append(command) return '%s%d commands: %s%s' % ( weechat.color(cmdhelp_settings['color_list_count']), len(commands2), weechat.color(cmdhelp_settings['color_list']), ', '.join(commands2)) return None def input_modifier_cb(data, modifier, modifier_data, string): """Modifier that will add help on command line (for display only).""" global cmdhelp_settings line = weechat.string_remove_color(string, '') if line == '': return string command = '' arguments = '' if weechat.string_input_for_buffer(line) != '': return string items = line.split(' ', 1) if len(items) > 0: command = items[0] if len(command) < 2: return string if len(items) > 1: arguments = items[1] if command[1:].lower() in cmdhelp_settings['ignore_commands'].split(','): return string current_buffer = weechat.current_buffer() current_window = weechat.current_window() plugin = weechat.buffer_get_pointer(current_buffer, 'plugin') msg_help = (get_help_command(plugin, command[1:], arguments) or get_list_commands(plugin, command[1:], arguments)) if not msg_help: if cmdhelp_settings['display_no_help'] != 'on': return string msg_help = weechat.color(cmdhelp_settings['color_no_help']) if command: msg_help += 'No help for command %s' % command else: msg_help += 'No help' if cmdhelp_settings['right_align'] == 'on': win_width = weechat.window_get_integer(current_window, 'win_width') input_length = weechat.buffer_get_integer(current_buffer, 'input_length') help_length = len(weechat.string_remove_color(msg_help, "")) min_space = int(cmdhelp_settings['space']) padding = int(cmdhelp_settings['right_padding']) space = win_width - input_length - help_length - padding if space < min_space: space = min_space else: space = int(cmdhelp_settings['space']) color_delimiters = cmdhelp_settings['color_delimiters'] return '%s%s%s%s%s%s%s' % (string, space * ' ', weechat.color(color_delimiters), cmdhelp_settings['prefix'], msg_help, weechat.color(color_delimiters), cmdhelp_settings['suffix']) def timer_cb(data, remaining_calls): """Timer callback.""" global cmdhelp_hooks if cmdhelp_hooks['modifier']: unhook(('modifier',)) weechat.bar_item_update('input_text') return weechat.WEECHAT_RC_OK def cmd_help_toggle(): """Toggle help on/off.""" global cmdhelp_hooks, cmdhelp_settings if cmdhelp_hooks['modifier']: unhook(('timer', 'modifier')) else: cmdhelp_hooks['modifier'] = weechat.hook_modifier( 'input_text_display_with_cursor', 'input_modifier_cb', '') timer = cmdhelp_settings['timer'] if timer and timer != '0': try: value = float(timer) if value > 0: weechat.hook_timer(value * 1000, 0, 1, 'timer_cb', '') except ValueError: pass weechat.bar_item_update('input_text') def cmd_help_cb(data, buffer, args): """Callback for /cmd_help command.""" cmd_help_toggle() return weechat.WEECHAT_RC_OK if __name__ == '__main__' and import_ok: if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''): # set allowed fields in option "format_option" fields = [] infolist = weechat.infolist_get('option', '', 'weechat.plugin.*') if infolist: if weechat.infolist_next(infolist): strfields = weechat.infolist_fields(infolist) for field in strfields.split(','): items = field.split(':', 1) if len(items) == 2: fields.append(items[1]) weechat.infolist_free(infolist) if fields: cmdhelp_settings_default['format_option'][1] += ( ': %s' % ', '.join(fields)) # set default settings version = weechat.info_get("version_number", "") or 0 for option, value in cmdhelp_settings_default.items(): if weechat.config_is_set_plugin(option): cmdhelp_settings[option] = weechat.config_get_plugin(option) else: weechat.config_set_plugin(option, value[0]) cmdhelp_settings[option] = value[0] if int(version) >= 0x00030500: weechat.config_set_desc_plugin( option, '%s (default: "%s")' % (value[1], value[0])) # detect config changes weechat.hook_config('plugins.var.python.%s.*' % SCRIPT_NAME, 'config_cb', '') # add hook to catch "enter" key if cmdhelp_settings['stop_on_enter'] == 'on': cmdhelp_hooks['command_run'] = weechat.hook_command_run( '/input return', 'command_run_cb', '') # add command weechat.hook_command( SCRIPT_COMMAND, 'Contextual command line help.', '', 'This comand toggles help on command line.\n\n' 'It is recommended to bind this command on a key, for example ' 'F1:\n' ' /key bind /cmd_help\n' 'which will give, according to your terminal something like:\n' ' /key bind meta-OP /cmd_help\n' ' or:\n' ' /key bind meta2-11~ /cmd_help\n\n' 'To try: type "/server" (without pressing enter) and press F1 ' '(then you can add arguments and enjoy dynamic help!)', '', 'cmd_help_cb', '') # auto start help if cmdhelp_settings['start_on_load'] == 'on': cmd_help_toggle()