# -*- coding: utf-8 -*- # # Copyright (c) 2009 by xt # # Based on go.py by FlashCode # # 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 . # # # Set screen title # (this script requires WeeChat 0.3.0 or newer) # # History: # # 2023-01-08, Sébastien Helleu # version 0.7: send buffer pointer with signal "input_text_changed" # 2019-10-31, Simmo Saan # version 0.6: fix hook_command_run hooks not being unhooked # 2019-07-11, Sébastien Helleu # version 0.5: make script compatible with Python 3 # 2019-07-11, Simmo Saan # version 0.4: fix detection of "/input search_text_here" # 2016-02-20, Simmo Saan # version 0.3: add option to only display selected command # 2010-01-19, xt # version 0.2: return max 9 commands # 2009-06-10, xt # version 0.1: initial release import weechat as w import re weechat = w SCRIPT_NAME = "histsearch" SCRIPT_AUTHOR = "xt " SCRIPT_VERSION = "0.7" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Quick search in command history (think ctrl-r in bash)" SCRIPT_COMMAND = 'histsearch' # script options settings = { "color_number" : "yellow,magenta", "color_number_selected" : "yellow,red", "color_name" : "black,cyan", "color_name_selected" : "black,brown", "color_name_highlight" : "red,cyan", "color_name_highlight_selected": "red,brown", "message" : "Command: ", "display_selected_only" : "false", } # hooks management hook_command_run = { "input" : ("/input *", "command_run_input"), "buffer": ("/buffer *", "command_run_buffer"), "window": ("/window *", "command_run_window"), } hooks = {} # input before command (we'll restore it later) saved_input = "" # last user input (if changed, we'll update list of matching commands) old_input = None # matching buffers commands = [] command_pos = 0 if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): weechat.hook_command(SCRIPT_COMMAND, "Quick search in command history", "", "You can bind command to a key, for example:\n /key bind meta-e /histsearch\n\n" + "You can use completion key (commonly Tab and shift-Tab) to select " + "next/previous command in list.", "", "histsearch_cmd", "") for option, default_value in settings.items(): if w.config_get_plugin(option) == "": w.config_set_plugin(option, default_value) def unhook_one(hook): """ Unhook something hooked by this script """ global hooks if hook in hooks: w.unhook(hooks[hook]) del hooks[hook] def unhook_all(): """ Unhook all """ global hook_command_run unhook_one("modifier") for hook in hook_command_run.keys(): unhook_one(hook) return w.WEECHAT_RC_OK def hook_all(): """ Hook command_run and modifier """ global hook_command_run, hooks for hook, value in hook_command_run.items(): if hook not in hooks: hooks[hook] = w.hook_command_run(value[0], value[1], "") if "modifier" not in hooks: hooks["modifier"] = w.hook_modifier( "input_text_display_with_cursor", "input_modifier", "") def histsearch_start(buffer): """ Start histsearch on buffer """ global saved_input, old_input, commands_pos hook_all() saved_input = w.buffer_get_string(buffer, "input") w.buffer_set(buffer, "input", "") old_input = None commands_pos = 0 def histsearch_end(buffer): """ End histsearch on buffer """ global saved_input, old_input unhook_all() w.buffer_set(buffer, "input", saved_input) old_input = None def get_matching_commands(input): """ Return list with commands matching user input """ global commands_pos clist = [] if len(input) == 0: commands_pos = 0 return [] input = input.lower() infolist = w.infolist_get("history", "", "") while w.infolist_next(infolist): text = w.infolist_string(infolist, "text") matching = input in text #if not matching and input.isdigit(): # matching = str(number).startswith(input) if len(clist) > 9: # Return max 10 commands break if len(input) == 0 or matching: if not text in clist: clist.append(text) w.infolist_free(infolist) return clist def get_command_string(commands, pos, input): ''' Build the string that is displayed on input bar ''' global settings colors = {} returnstr = '' for option in settings: colors[option] = weechat.config_get_plugin(option) for i, command in enumerate(commands): selected = '' if i == pos: selected = "_selected" if weechat.config_string_to_boolean(weechat.config_get_plugin("display_selected_only")): if not selected: continue index = command.find(input) index2 = index + len(input) returnstr += ' ' + \ weechat.color(colors["color_number" + selected]) + str(i) + \ weechat.color(colors["color_name" + selected]) + command[:index] + \ weechat.color(colors["color_name_highlight" + selected]) + command[index:index2] + \ weechat.color(colors["color_name" + selected]) + command[index2:] + \ weechat.color("reset") if i > 10: # Display max 10 commands break return returnstr def input_modifier(data, modifier, modifier_data, string): """ This modifier is called when input text item is built by WeeChat (commonly after changes in input or cursor move), it builds new input with prefix ("Commands:"), and suffix (list of commands found) """ global old_input, commands, commands_pos if modifier_data != w.current_buffer(): return "" input = w.string_remove_color(string, "") input = input.strip() if old_input == None or input != old_input: old_commands = commands commands = get_matching_commands(input) if commands != old_commands and len(input) > 0: commands_pos = 0 old_input = input commandstr = get_command_string(commands, commands_pos, input) return w.config_get_plugin("message") + string + commandstr def command_run_input(data, buffer, command): """ Function called when a command "/input xxxx" is run """ global commands, commands_pos if command.startswith('/input search_text') or command.startswith('/input jump'): # search text or jump to another buffer is forbidden now return w.WEECHAT_RC_OK_EAT elif command == "/input complete_next": # choose next buffer in list commands_pos += 1 if commands_pos >= len(commands): commands_pos = 0 w.hook_signal_send("input_text_changed", w.WEECHAT_HOOK_SIGNAL_POINTER, buffer) return w.WEECHAT_RC_OK_EAT elif command == "/input complete_previous": # choose previous buffer in list commands_pos -= 1 if commands_pos < 0: commands_pos = len(commands) - 1 w.hook_signal_send("input_text_changed", w.WEECHAT_HOOK_SIGNAL_POINTER, buffer) return w.WEECHAT_RC_OK_EAT elif command == "/input return": # As in enter was pressed. # Put the current command on the input bar histsearch_end(buffer) if len(commands) > 0: w.command(buffer, "/input insert " + commands[commands_pos]) return w.WEECHAT_RC_OK_EAT return w.WEECHAT_RC_OK def command_run_buffer(data, buffer, command): """ Function called when a command "/buffer xxxx" is run """ return w.WEECHAT_RC_OK_EAT def command_run_window(data, buffer, command): """ Function called when a command "/buffer xxxx" is run """ return w.WEECHAT_RC_OK_EAT def histsearch_cmd(data, buffer, args): """ Command "/histsearch": just hook what we need """ global hooks if "modifier" in hooks: histsearch_end(buffer) else: histsearch_start(buffer) return w.WEECHAT_RC_OK def histsearch_unload_script(): """ Function called when script is unloaded """ unhook_all() return w.WEECHAT_RC_OK