'''Keycmd completion.''' # A list of functions this plugin exports to be used via uzbl object. __export__ = ['start_completion', 'get_completion_dict'] import re # Holds the per-instance completion dicts. UZBLS = {} # Completion level NONE, ONCE, LIST, COMPLETE = range(4) # Default instance dict. DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} # The reverse keyword finding re. FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall # Formats LIST_FORMAT = " %s " ITEM_FORMAT = "%s%s" def escape(str): return str.replace("@", "\@") def add_instance(uzbl, *args): UZBLS[uzbl] = dict(DEFAULTS) # Make sure the config keys for all possible completions are known. uzbl.send('dump_config_as_events') def del_instance(uzbl, *args): if uzbl in UZBLS: del UZBLS[uzbl] def get_completion_dict(uzbl): '''Get data stored for an instance.''' if uzbl not in UZBLS: add_instance(uzbl) return UZBLS[uzbl] def get_incomplete_keyword(uzbl): '''Gets the segment of the keycmd leading up to the cursor position and uses a regular expression to search backwards finding parially completed keywords or @variables. Returns a null string if the correct completion conditions aren't met.''' keylet = uzbl.get_keylet() left_segment = keylet.keycmd[:keylet.cursor] partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() if partial.startswith('set '): return ('@%s' % partial[4:].lstrip(), True) return (partial, False) def stop_completion(uzbl, *args): '''Stop command completion and return the level to NONE.''' d = get_completion_dict(uzbl) d['level'] = NONE uzbl.set('completion_list') def complete_completion(uzbl, partial, hint, set_completion=False): '''Inject the remaining porition of the keyword into the keycmd then stop the completioning.''' if set_completion: remainder = "%s = " % hint[len(partial):] else: remainder = "%s " % hint[len(partial):] uzbl.inject_keycmd(remainder) stop_completion(uzbl) def partial_completion(uzbl, partial, hint): '''Inject a common portion of the hints into the keycmd.''' remainder = hint[len(partial):] uzbl.inject_keycmd(remainder) def update_completion_list(uzbl, *args): '''Checks if the user still has a partially completed keyword under his cursor then update the completion hints list.''' partial = get_incomplete_keyword(uzbl)[0] if not partial: return stop_completion(uzbl) d = get_completion_dict(uzbl) if d['level'] < LIST: return hints = [h for h in d['completions'] if h.startswith(partial)] if not hints: return uzbl.set('completion_list') j = len(partial) l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) def start_completion(uzbl, *args): d = get_completion_dict(uzbl) if d['lock']: return (partial, set_completion) = get_incomplete_keyword(uzbl) if not partial: return stop_completion(uzbl) if d['level'] < COMPLETE: d['level'] += 1 hints = [h for h in d['completions'] if h.startswith(partial)] if not hints: return elif len(hints) == 1: d['lock'] = True complete_completion(uzbl, partial, hints[0], set_completion) d['lock'] = False return elif partial in hints and d['level'] == COMPLETE: d['lock'] = True complete_completion(uzbl, partial, partial, set_completion) d['lock'] = False return smalllen, smallest = sorted([(len(h), h) for h in hints])[0] common = '' for i in range(len(partial), smalllen): char, same = smallest[i], True for hint in hints: if hint[i] != char: same = False break if not same: break common += char if common: d['lock'] = True partial_completion(uzbl, partial, partial+common) d['lock'] = False update_completion_list(uzbl) def add_builtins(uzbl, args): '''Pump the space delimited list of builtin commands into the builtin list.''' completions = get_completion_dict(uzbl)['completions'] builtins = filter(None, map(unicode.strip, args.split(" "))) for builtin in builtins: if builtin not in completions: completions.append(builtin) def add_config_key(uzbl, key, value): '''Listen on the CONFIG_CHANGED event and add config keys to the variable list for @var like expansion support.''' completions = get_completion_dict(uzbl)['completions'] key = "@%s" % key if key not in completions: completions.append(key) def init(uzbl): # Event handling hooks. uzbl.connect_dict({ 'BUILTINS': add_builtins, 'CONFIG_CHANGED': add_config_key, 'INSTANCE_EXIT': del_instance, 'INSTANCE_START': add_instance, 'KEYCMD_CLEARED': stop_completion, 'KEYCMD_EXEC': stop_completion, 'KEYCMD_UPDATE': update_completion_list, 'START_COMPLETION': start_completion, 'STOP_COMPLETION': stop_completion, }) # Function exports to the uzbl object, `function(uzbl, *args, ..)` # becomes `uzbl.function(*args, ..)`. uzbl.export_dict({ 'get_completion_dict': get_completion_dict, 'start_completion': start_completion, })