aboutsummaryrefslogtreecommitdiffhomepage
path: root/examples/data/plugins/bind.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/data/plugins/bind.py')
-rw-r--r--examples/data/plugins/bind.py521
1 files changed, 521 insertions, 0 deletions
diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py
new file mode 100644
index 0000000..9e09337
--- /dev/null
+++ b/examples/data/plugins/bind.py
@@ -0,0 +1,521 @@
+'''Plugin provides support for binds in uzbl.
+
+For example:
+ event BIND ZZ = exit -> bind('ZZ', 'exit')
+ event BIND o _ = uri %s -> bind('o _', 'uri %s')
+ event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
+
+And it is also possible to execute a function on activation:
+ bind('DD', myhandler)
+'''
+
+import sys
+import re
+import pprint
+
+# Hold the bind dicts for each uzbl instance.
+UZBLS = {}
+
+# Commonly used regular expressions.
+MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match
+# Matches <x:y>, <'x':y>, <:'y'>, <x!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)
+
+
+# Custom errors.
+class ArgumentError(Exception): pass
+
+
+class Bindlet(object):
+ '''Per-instance bind status/state tracker.'''
+
+ 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 __getitem__(self, key):
+ return self.get_binds(key)
+
+
+ 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, 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 is_cmd:
+ self.uzbl.send(set)
+
+ elif set and not is_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] = Bindlet(uzbl)
+
+
+def del_instance(uzbl, *args):
+ if uzbl in UZBLS:
+ del UZBLS[uzbl]
+
+
+def get_bindlet(uzbl):
+ '''Return the bind tracklet for the given uzbl instance.'''
+
+ if uzbl not in UZBLS:
+ add_instance(uzbl)
+
+ return UZBLS[uzbl]
+
+
+def ismodbind(glob):
+ '''Return True if the glob specifies a modbind.'''
+
+ return bool(MOD_START(glob))
+
+
+def split_glob(glob):
+ '''Take a string of the form "<Mod1><Mod2>cmd _" and return a list of the
+ modkeys in the glob and the command.'''
+
+ mods = set()
+ while True:
+ match = MOD_START(glob)
+ if not match:
+ break
+
+ end = match.span()[1]
+ mods.add(glob[:end])
+ glob = glob[end:]
+
+ return (mods, glob)
+
+
+def unquote(str):
+ '''Remove quotation marks around string.'''
+
+ if str and str[0] == str[-1] and str[0] in ['"', "'"]:
+ str = str[1:-1]
+
+ return str
+
+
+class Bind(object):
+
+ # Class attribute to hold the number of Bind classes created.
+ counter = [0,]
+
+ def __init__(self, glob, handler, *args, **kargs):
+ self.is_callable = callable(handler)
+ self._repr_cache = None
+
+ if not glob:
+ raise ArgumentError('glob cannot be blank')
+
+ if self.is_callable:
+ self.function = handler
+ self.args = args
+ self.kargs = kargs
+
+ elif kargs:
+ raise ArgumentError('cannot supply kargs for uzbl commands')
+
+ elif hasattr(handler, '__iter__'):
+ self.commands = handler
+
+ else:
+ self.commands = [handler,] + list(args)
+
+ self.glob = glob
+
+ # Assign unique id.
+ self.counter[0] += 1
+ self.bid = self.counter[0]
+
+ self.split = split = FIND_PROMPTS(glob)
+ self.prompts = []
+ 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, cmd, set))
+
+ # Check that there is nothing like: fl*<int:>*
+ 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<prompt1:><prompt2:>_
+ 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[::4])):
+ # Is the binding a MODCMD or KEYCMD:
+ mod_cmd = ismodbind(glob)
+
+ # Do we execute on UPDATES or EXEC events?
+ on_exec = True if glob[-1] in ['!', '_'] else False
+
+ # Does the command take arguments?
+ has_args = True if glob[-1] in ['*', '_'] else False
+
+ glob = glob[:-1] if has_args or on_exec else glob
+ mods, glob = split_glob(glob)
+ stack.append((on_exec, has_args, mods, glob, index))
+
+ self.stack = list(reversed(stack))
+ self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
+
+
+ def __getitem__(self, depth):
+ '''Get bind info at a depth.'''
+
+ if self.is_global:
+ return self.stack[0]
+
+ return self.stack[depth]
+
+
+ def __repr__(self):
+ if self._repr_cache:
+ return self._repr_cache
+
+ args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
+
+ if self.is_callable:
+ args.append('function=%r' % self.function)
+ if self.args:
+ args.append('args=%r' % self.args)
+
+ if self.kargs:
+ args.append('kargs=%r' % self.kargs)
+
+ else:
+ cmdlen = len(self.commands)
+ cmds = self.commands[0] if cmdlen == 1 else self.commands
+ args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds))
+
+ self._repr_cache = '<Bind(%s)>' % ', '.join(args)
+ return self._repr_cache
+
+
+def exec_bind(uzbl, bind, *args, **kargs):
+ '''Execute bind objects.'''
+
+ uzbl.event("EXEC_BIND", bind, args, kargs)
+
+ if bind.is_callable:
+ args += bind.args
+ kargs = dict(bind.kargs.items()+kargs.items())
+ bind.function(uzbl, *args, **kargs)
+ return
+
+ if kargs:
+ raise ArgumentError('cannot supply kargs for uzbl commands')
+
+ commands = []
+ cmd_expand = uzbl.cmd_expand
+ for cmd in bind.commands:
+ cmd = cmd_expand(cmd, args)
+ uzbl.send(cmd)
+
+
+def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs):
+ '''Add a mode bind.'''
+
+ bindlet = get_bindlet(uzbl)
+
+ if not hasattr(modes, '__iter__'):
+ modes = unicode(modes).split(',')
+
+ # Sort and filter binds.
+ modes = filter(None, map(unicode.strip, modes))
+
+ if callable(handler) or (handler is not None and handler.strip()):
+ bind = Bind(glob, handler, *args, **kargs)
+
+ else:
+ bind = None
+
+ 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
+
+ bindlet.add_bind(mode, glob, bind)
+ uzbl.event('ADDED_MODE_BIND', mode, glob, bind)
+
+
+def bind(uzbl, glob, handler, *args, **kargs):
+ '''Legacy bind function.'''
+
+ mode_bind(uzbl, 'global', glob, handler, *args, **kargs)
+
+
+def parse_mode_bind(uzbl, args):
+ '''Parser for the MODE_BIND event.
+
+ Example events:
+ MODE_BIND <mode> <bind> = <command>
+ MODE_BIND command o<location:>_ = uri %s
+ MODE_BIND insert,command <BackSpace> = ...
+ MODE_BIND global ... = ...
+ MODE_BIND global,-insert ... = ...
+ '''
+
+ if not args:
+ raise ArgumentError('missing bind arguments')
+
+ split = map(unicode.strip, args.split(' ', 1))
+ if len(split) != 2:
+ raise ArgumentError('missing mode or bind section: %r' % args)
+
+ 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 parse_bind(uzbl, args):
+ '''Legacy parsing of the BIND event and conversion to the new format.
+
+ Example events:
+ request BIND <bind> = <command>
+ request BIND o<location:>_ = uri %s
+ request BIND <BackSpace> = ...
+ request BIND ... = ...
+ '''
+
+ parse_mode_bind(uzbl, "global %s" % args)
+
+
+def mode_changed(uzbl, mode):
+ '''Clear the stack on all non-stack mode changes.'''
+
+ if mode != 'stack':
+ get_bindlet(uzbl).reset()
+
+
+def match_and_exec(uzbl, bind, depth, keylet, bindlet):
+
+ (on_exec, has_args, mod_cmd, glob, more) = bind[depth]
+ cmd = keylet.modcmd if mod_cmd else keylet.keycmd
+
+ if mod_cmd and keylet.held != mod_cmd:
+ return False
+
+ if has_args:
+ if not cmd.startswith(glob):
+ return False
+
+ args = [cmd[len(glob):],]
+
+ elif cmd != glob:
+ return False
+
+ else:
+ args = []
+
+ if bind.is_global or (not more and depth == 0):
+ exec_bind(uzbl, bind, *args)
+ if not has_args:
+ uzbl.clear_current()
+
+ return True
+
+ elif more:
+ bindlet.stack(bind, args, depth)
+ return False
+
+ args = bindlet.args + args
+ exec_bind(uzbl, bind, *args)
+ uzbl.set_mode()
+ if not has_args:
+ bindlet.reset()
+ uzbl.clear_current()
+
+ return True
+
+
+def keycmd_update(uzbl, keylet):
+ 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, bindlet):
+ return
+
+ bindlet.after()
+
+
+def keycmd_exec(uzbl, keylet):
+ 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, bindlet):
+ return uzbl.clear_keycmd()
+
+ bindlet.after()
+
+
+def modcmd_update(uzbl, keylet):
+ 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, bindlet):
+ return
+
+ bindlet.after()
+
+
+def modcmd_exec(uzbl, keylet):
+ 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, bindlet):
+ return uzbl.clear_modcmd()
+
+ bindlet.after()
+
+
+def init(uzbl):
+ # Event handling hooks.
+ uzbl.connect_dict({
+ 'BIND': parse_bind,
+ 'KEYCMD_EXEC': keycmd_exec,
+ 'KEYCMD_UPDATE': keycmd_update,
+ 'MODCMD_EXEC': modcmd_exec,
+ 'MODCMD_UPDATE': modcmd_update,
+ 'MODE_BIND': parse_mode_bind,
+ 'MODE_CHANGED': mode_changed,
+ })
+
+ # Function exports to the uzbl object, `function(uzbl, *args, ..)`
+ # becomes `uzbl.function(*args, ..)`.
+ uzbl.export_dict({
+ 'bind': bind,
+ 'mode_bind': mode_bind,
+ 'get_bindlet': get_bindlet,
+ })