From 8a342efb325d682fdfbb4a2ba07e98dfd53c1f2f Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Sat, 12 Dec 2009 17:34:28 +0800 Subject: Initial commit of mode binding support. Summary of changes: 1. Use an object to track the per instance bind status/state instead of a dict. 2. Added support for stack syntax. 3. Stack finding regex now supports dumb-quoting of prompt also. 4. Mode bind event syntax is "MODE_BIND = " 4. Added legacy support for BIND event and bind function. 5. Mode binds can be bound to multiple modes at once. 6. Mode exclusion supported (i.e. "MODE_BIND global,-insert ..."). 7. Fixed keycmd ghosting after entering stack mode. 8. Added examples to bind functions. --- examples/data/uzbl/plugins/bind.py | 381 ++++++++++++++++++++++--------------- 1 file changed, 228 insertions(+), 153 deletions(-) (limited to 'examples') diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 3dedf16..6d08555 100644 --- a/examples/data/uzbl/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -11,52 +11,145 @@ And it is also possible to execute a function on activation: import sys import re +import pprint # Export these functions to uzbl. -__export__ = ['bind', 'del_bind', 'del_bind_by_glob', 'get_binds'] +__export__ = ['bind', 'mode_bind', 'get_bindlet'] # Hold the bind dicts for each uzbl instance. UZBLS = {} -DEFAULTS = {'binds': [], 'depth': 0, 'stack': [], 'args': [], - 'last_mode': '', 'after': None} # Commonly used regular expressions. -starts_with_mod = re.compile('^<([A-Z][A-Za-z0-9-_]*)>') -find_prompts = re.compile('<([^:>]*):(\"[^\"]*\"|\'[^\']*\'|[^>]*)>').split +MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match +# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... +PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' +FIND_PROMPTS = re.compile(PROMPTS).split +VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match # For accessing a bind glob stack. ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) -class ArgumentError(Exception): - pass +# Custom errors. +class ArgumentError(Exception): pass -def ismodbind(glob): - '''Return True if the glob specifies a modbind.''' +class Bindlet(object): + '''Per-instance bind status/state tracker.''' - return bool(starts_with_mod.match(glob)) + def __init__(self, uzbl): + self.binds = {'global': {}} + self.uzbl = uzbl + self.depth = 0 + self.args = [] + self.last_mode = None + self.after_cmds = None + self.stack_binds = [] + # A subset of the global mode binds containing non-stack and modkey + # activiated binds for use in the stack mode. + self.globals = [] -def split_glob(glob): - '''Take a string of the form "cmd _" and return a list of the - modkeys in the glob and the command.''' - mods = set() - while True: - match = starts_with_mod.match(glob) - if not match: - break + def __getitem__(self, key): + return self.get_binds(key) - end = match.span()[1] - mods.add(glob[:end]) - glob = glob[end:] - return (mods, glob) + def reset(self): + '''Reset the tracker state and return to last mode.''' + + self.depth = 0 + self.args = [] + self.after_cmds = None + self.stack_binds = [] + + if self.last_mode: + mode, self.last_mode = self.last_mode, None + self.uzbl.set_mode(mode) + + self.uzbl.set('keycmd_prompt') + + + def stack(self, bind, args, depth): + '''Enter or add new bind in the next stack level.''' + + if self.depth != depth: + if bind not in self.stack_binds: + self.stack_binds.append(bind) + + return + + current_mode = self.uzbl.get_mode() + if current_mode != 'stack': + self.last_mode = current_mode + self.uzbl.set_mode('stack') + + self.stack_binds = [bind,] + self.args += args + self.depth += 1 + self.after_cmds = bind.prompts[depth] + + + def after(self): + '''If a stack was triggered then set the prompt and default value.''' + + if self.after_cmds is None: + return + + (prompt, cmd, set), self.after_cmds = self.after_cmds, None + + self.uzbl.clear_keycmd() + if prompt: + self.uzbl.set('keycmd_prompt', prompt) + + if set and cmd: + self.uzbl.send(cmd) + + elif set and not cmd: + self.uzbl.send('event SET_KEYCMD %s' % set) + + + def get_binds(self, mode=None): + '''Return the mode binds + globals. If we are stacked then return + the filtered stack list and modkey & non-stack globals.''' + + if mode is None: + mode = self.uzbl.get_mode() + + if not mode: + mode = 'global' + + if self.depth: + return self.stack_binds + self.globals + + globals = self.binds['global'] + if mode not in self.binds or mode == 'global': + return filter(None, globals.values()) + + binds = dict(globals.items() + self.binds[mode].items()) + return filter(None, binds.values()) + + + def add_bind(self, mode, glob, bind=None): + '''Insert (or override) a bind into the mode bind dict.''' + + if mode not in self.binds: + self.binds[mode] = {glob: bind} + return + + binds = self.binds[mode] + binds[glob] = bind + + if mode == 'global': + # Regen the global-globals list. + self.globals = [] + for bind in binds.values(): + if bind is not None and bind.is_global: + self.globals.append(bind) def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) + UZBLS[uzbl] = Bindlet(uzbl) def del_instance(uzbl, *args): @@ -64,8 +157,8 @@ def del_instance(uzbl, *args): del UZBLS[uzbl] -def get_bind_dict(uzbl): - '''Return the bind dict for the uzbl instance.''' +def get_bindlet(uzbl): + '''Return the bind tracklet for the given uzbl instance.''' if uzbl not in UZBLS: add_instance(uzbl) @@ -73,42 +166,36 @@ def get_bind_dict(uzbl): return UZBLS[uzbl] -def get_binds(uzbl): - '''Return the bind list for the uzbl instance.''' - - return get_bind_dict(uzbl)['binds'] - +def ismodbind(glob): + '''Return True if the glob specifies a modbind.''' -def get_filtered_binds(uzbl, bd): - '''Return the bind list for the uzbl instance or return the filtered - bind list thats on the current stack.''' + return bool(MOD_START(glob)) - return bd['stack'] if bd['depth'] else bd['binds'] +def split_glob(glob): + '''Take a string of the form "cmd _" and return a list of the + modkeys in the glob and the command.''' -def del_bind(uzbl, bind): - '''Delete bind object if bind in the uzbl binds.''' + mods = set() + while True: + match = MOD_START(glob) + if not match: + break - binds = get_binds(uzbl) - if bind in binds: - binds.remove(bind) - uzbl.event('DELETED_BIND', bind) - return True + end = match.span()[1] + mods.add(glob[:end]) + glob = glob[end:] - return False + return (mods, glob) -def del_bind_by_glob(uzbl, glob): - '''Delete bind by glob if bind in the uzbl binds.''' +def unquote(str): + '''Remove quotation marks around string.''' - binds = get_binds(uzbl) - for bind in list(binds): - if bind.glob == glob: - binds.remove(bind) - uzbl.event('DELETED_BIND', bind) - return True + if str and str[0] == str[-1] and str[0] in ['"', "'"]: + str = str[1:-1] - return False + return str class Bind(object): @@ -143,29 +230,30 @@ class Bind(object): self.counter[0] += 1 self.bid = self.counter[0] - self.split = split = find_prompts(glob) + self.split = split = FIND_PROMPTS(glob) self.prompts = [] - for (prompt, set) in zip(split[1::3], split[2::3]): - if set and set[0] == set[-1] and set[0] in ['"', "'"]: - # Remove quotes around set. - set = set[1:-1] + for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): + prompt, set = map(unquote, [prompt, set]) + cmd = True if cmd == '!' else False + if prompt and prompt[-1] != ":": + prompt = "%s:" % prompt - self.prompts.append((prompt, set)) + self.prompts.append((prompt, cmd, set)) # Check that there is nothing like: fl** - for glob in split[:-1:3]: + for glob in split[:-1:4]: if glob.endswith('*'): msg = "token '*' not at the end of a prompt bind: %r" % split raise SyntaxError(msg) # Check that there is nothing like: fl_ - for glob in split[3::3]: + for glob in split[4::4]: if not glob: msg = 'found null segment after first prompt: %r' % split raise SyntaxError(msg) stack = [] - for (index, glob) in enumerate(reversed(split[::3])): + for (index, glob) in enumerate(reversed(split[::4])): # Is the binding a MODCMD or KEYCMD: mod_cmd = ismodbind(glob) @@ -249,103 +337,89 @@ def exec_bind(uzbl, bind, *args, **kargs): uzbl.send(cmd) -def bind(uzbl, glob, handler, *args, **kargs): - '''Add a bind handler object.''' - - # Mods come from the keycmd sorted so make sure the modkeys in the bind - # command are sorted too. - - del_bind_by_glob(uzbl, glob) - binds = get_binds(uzbl) +def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): + '''Add a mode bind.''' - bind = Bind(glob, handler, *args, **kargs) - binds.append(bind) + bindlet = get_bindlet(uzbl) - uzbl.event('ADDED_BIND', bind) + if not hasattr(modes, '__iter__'): + modes = unicode(modes).split(',') + # Sort and filter binds. + modes = filter(None, map(unicode.strip, modes)) -def parse_bind_event(uzbl, args): - '''Break "event BIND fl* = js follownums.js" into (glob, command).''' + if callable(handler) or (handler is not None and handler.strip()): + bind = Bind(glob, handler, *args, **kargs) - if not args: - raise ArgumentError('missing bind arguments') - - split = map(unicode.strip, args.split('=', 1)) - if len(split) != 2: - raise ArgumentError('missing delimiter in bind: %r' % args) - - glob, command = split - bind(uzbl, glob, command) - - -def mode_changed(uzbl, mode): - '''Clear the stack on all non-stack mode changes.''' + else: + bind = None - if mode != 'stack': - clear_stack(uzbl) + for mode in modes: + if not VALID_MODE(mode): + raise NameError('invalid mode name: %r' % mode) + for mode in modes: + if mode[0] == '-': + mode, bind = mode[1:], None -def clear_stack(uzbl, bd=None): - '''Clear everything related to stacked binds.''' + bindlet.add_bind(mode, glob, bind) + uzbl.event('ADDED_MODE_BIND', mode, glob, bind) - if bd is None: - bd = get_bind_dict(uzbl) - bd['stack'] = [] - bd['depth'] = 0 - bd['args'] = [] - bd['after'] = None - if bd['last_mode']: - mode, bd['last_mode'] = bd['last_mode'], '' - uzbl.set_mode(mode) +def bind(uzbl, glob, handler, *args, **kargs): + '''Legacy bind function.''' - uzbl.set('keycmd_prompt') + mode_bind(uzbl, 'global', glob, handler, *args, **kargs) -def stack_bind(uzbl, bind, args, depth, bd): - '''Increment the stack depth in the bind dict, generate filtered bind - list for stack mode and set keycmd prompt.''' +def parse_mode_bind(uzbl, args): + '''Parser for the MODE_BIND event. - if bd['depth'] != depth: - if bind not in bd['stack']: - bd['stack'].append(bind) + Example events: + MODE_BIND = + MODE_BIND command o_ = uri %s + MODE_BIND insert,command = ... + MODE_BIND global ... = ... + MODE_BIND global,-insert ... = ... + ''' - return + if not args: + raise ArgumentError('missing bind arguments') - if uzbl.get_mode() != 'stack': - bd['last_mode'] = uzbl.get_mode() - uzbl.set_mode('stack') + split = map(unicode.strip, args.split(' ', 1)) + if len(split) != 2: + raise ArgumentError('missing mode or bind section: %r' % args) - globalcmds = [cmd for cmd in bd['binds'] if cmd.is_global] - bd['stack'] = [bind,] + globalcmds - bd['args'] += args - bd['depth'] = depth + 1 - bd['after'] = bind.prompts[depth] + modes, args = split[0].split(','), split[1] + split = map(unicode.strip, args.split('=', 1)) + if len(split) != 2: + raise ArgumentError('missing delimiter in bind section: %r' % args) + glob, command = split + mode_bind(uzbl, modes, glob, command) -def after_bind(uzbl, bd): - '''Check if there are afte-actions to perform.''' - if bd['after'] is None: - return +def parse_bind(uzbl, args): + '''Legacy parsing of the BIND event and conversion to the new format. - (prompt, set), bd['after'] = bd['after'], None - if prompt: - uzbl.set('keycmd_prompt', '%s:' % prompt) + Example events: + request BIND = + request BIND o_ = uri %s + request BIND = ... + request BIND ... = ... + ''' - else: - uzbl.set('keycmd_prompt') + parse_mode_bind(uzbl, "global %s" % args) - if set: - uzbl.send('event SET_KEYCMD %s' % set) - else: - uzbl.clear_keycmd() +def mode_changed(uzbl, mode): + '''Clear the stack on all non-stack mode changes.''' - uzbl.send('event BIND_STACK_LEVEL %d' % bd['depth']) + if mode != 'stack': + get_bindlet(uzbl).reset() -def match_and_exec(uzbl, bind, depth, keylet, bd): +def match_and_exec(uzbl, bind, depth, keylet, bindlet): (on_exec, has_args, mod_cmd, glob, more) = bind[depth] @@ -375,10 +449,10 @@ def match_and_exec(uzbl, bind, depth, keylet, bd): return True elif more: - stack_bind(uzbl, bind, args, depth, bd) + bindlet.stack(bind, args, depth) return False - args = bd['args'] + args + args = bindlet.args + args exec_bind(uzbl, bind, *args) uzbl.set_mode() if not has_args: @@ -389,63 +463,64 @@ def match_and_exec(uzbl, bind, depth, keylet, bd): def keycmd_update(uzbl, keylet): - bd = get_bind_dict(uzbl) - depth = bd['depth'] - for bind in get_filtered_binds(uzbl, bd): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): t = bind[depth] if t[MOD_CMD] or t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet, bd): + if match_and_exec(uzbl, bind, depth, keylet, bindlet): return - after_bind(uzbl, bd) + bindlet.after() def keycmd_exec(uzbl, keylet): - bd = get_bind_dict(uzbl) - depth = bd['depth'] - for bind in get_filtered_binds(uzbl, bd): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): t = bind[depth] if t[MOD_CMD] or not t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet, bd): + if match_and_exec(uzbl, bind, depth, keylet, bindlet): return uzbl.clear_keycmd() - after_bind(uzbl, bd) + bindlet.after() def modcmd_update(uzbl, keylet): - bd = get_bind_dict(uzbl) - depth = bd['depth'] - for bind in get_filtered_binds(uzbl, bd): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): t = bind[depth] if not t[MOD_CMD] or t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet, bd): + if match_and_exec(uzbl, bind, depth, keylet, bindlet): return - after_bind(uzbl, bd) + bindlet.after() def modcmd_exec(uzbl, keylet): - bd = get_bind_dict(uzbl) - depth = bd['depth'] - for bind in get_filtered_binds(uzbl, bd): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): t = bind[depth] if not t[MOD_CMD] or not t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet, bd): + if match_and_exec(uzbl, bind, depth, keylet, bindlet): return uzbl.clear_modcmd() - after_bind(uzbl, bd) + bindlet.after() def init(uzbl): - connects = {'BIND': parse_bind_event, + connects = {'BIND': parse_bind, + 'MODE_BIND': parse_mode_bind, 'KEYCMD_UPDATE': keycmd_update, 'MODCMD_UPDATE': modcmd_update, 'KEYCMD_EXEC': keycmd_exec, -- cgit v1.2.3 From b64ddd7bce761b53839f981949fce6fd6daa855c Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Sat, 12 Dec 2009 17:57:56 +0800 Subject: Unbreak stack binds. --- examples/data/uzbl/plugins/bind.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 6d08555..0f0d0ce 100644 --- a/examples/data/uzbl/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -96,16 +96,16 @@ class Bindlet(object): if self.after_cmds is None: return - (prompt, cmd, set), self.after_cmds = self.after_cmds, None + (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None self.uzbl.clear_keycmd() if prompt: self.uzbl.set('keycmd_prompt', prompt) - if set and cmd: - self.uzbl.send(cmd) + if set and is_cmd: + self.uzbl.send(set) - elif set and not cmd: + elif set and not is_cmd: self.uzbl.send('event SET_KEYCMD %s' % set) -- cgit v1.2.3 From f75a2b19017778a8dace0a8004d2a7cf10323bfd Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Sun, 13 Dec 2009 04:32:38 +0800 Subject: Removing call to deprecated function. --- examples/data/uzbl/plugins/bind.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 0f0d0ce..8d9bc91 100644 --- a/examples/data/uzbl/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -456,7 +456,7 @@ def match_and_exec(uzbl, bind, depth, keylet, bindlet): exec_bind(uzbl, bind, *args) uzbl.set_mode() if not has_args: - clear_stack(uzbl, bd) + bindlet.reset() uzbl.clear_current() return True -- cgit v1.2.3