PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/examples/data/plugins/bind.py

https://github.com/nesaro/uzbl
Python | 462 lines | 447 code | 7 blank | 8 comment | 10 complexity | ddc9d6366e0ff65447ce5f1f9fffa69f MD5 | raw file
  1. '''Plugin provides support for binds in uzbl.
  2. For example:
  3. event BIND ZZ = exit -> bind('ZZ', 'exit')
  4. event BIND o _ = uri %s -> bind('o _', 'uri %s')
  5. event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
  6. And it is also possible to execute a function on activation:
  7. bind('DD', myhandler)
  8. '''
  9. import sys
  10. import re
  11. # Commonly used regular expressions.
  12. MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match
  13. # Matches <x:y>, <'x':y>, <:'y'>, <x!y>, <'x'!y>, ...
  14. PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>'
  15. FIND_PROMPTS = re.compile(PROMPTS).split
  16. VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match
  17. # For accessing a bind glob stack.
  18. ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5)
  19. # Custom errors.
  20. class ArgumentError(Exception): pass
  21. class Bindlet(object):
  22. '''Per-instance bind status/state tracker.'''
  23. def __init__(self, uzbl):
  24. self.binds = {'global': {}}
  25. self.uzbl = uzbl
  26. self.depth = 0
  27. self.args = []
  28. self.last_mode = None
  29. self.after_cmds = None
  30. self.stack_binds = []
  31. # A subset of the global mode binds containing non-stack and modkey
  32. # activiated binds for use in the stack mode.
  33. self.globals = []
  34. def __getitem__(self, key):
  35. return self.get_binds(key)
  36. def reset(self):
  37. '''Reset the tracker state and return to last mode.'''
  38. self.depth = 0
  39. self.args = []
  40. self.after_cmds = None
  41. self.stack_binds = []
  42. if self.last_mode:
  43. mode, self.last_mode = self.last_mode, None
  44. self.uzbl.config['mode'] = mode
  45. del self.uzbl.config['keycmd_prompt']
  46. def stack(self, bind, args, depth):
  47. '''Enter or add new bind in the next stack level.'''
  48. if self.depth != depth:
  49. if bind not in self.stack_binds:
  50. self.stack_binds.append(bind)
  51. return
  52. mode = self.uzbl.config.get('mode', None)
  53. if mode != 'stack':
  54. self.last_mode = mode
  55. self.uzbl.config['mode'] = 'stack'
  56. self.stack_binds = [bind,]
  57. self.args += args
  58. self.depth += 1
  59. self.after_cmds = bind.prompts[depth]
  60. def after(self):
  61. '''If a stack was triggered then set the prompt and default value.'''
  62. if self.after_cmds is None:
  63. return
  64. (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None
  65. self.uzbl.clear_keycmd()
  66. if prompt:
  67. self.uzbl.config['keycmd_prompt'] = prompt
  68. if set and is_cmd:
  69. self.uzbl.send(set)
  70. elif set and not is_cmd:
  71. self.uzbl.send('event SET_KEYCMD %s' % set)
  72. def get_binds(self, mode=None):
  73. '''Return the mode binds + globals. If we are stacked then return
  74. the filtered stack list and modkey & non-stack globals.'''
  75. if mode is None:
  76. mode = self.uzbl.config.get('mode', None)
  77. if not mode:
  78. mode = 'global'
  79. if self.depth:
  80. return self.stack_binds + self.globals
  81. globals = self.binds['global']
  82. if mode not in self.binds or mode == 'global':
  83. return filter(None, globals.values())
  84. binds = dict(globals.items() + self.binds[mode].items())
  85. return filter(None, binds.values())
  86. def add_bind(self, mode, glob, bind=None):
  87. '''Insert (or override) a bind into the mode bind dict.'''
  88. if mode not in self.binds:
  89. self.binds[mode] = {glob: bind}
  90. return
  91. binds = self.binds[mode]
  92. binds[glob] = bind
  93. if mode == 'global':
  94. # Regen the global-globals list.
  95. self.globals = []
  96. for bind in binds.values():
  97. if bind is not None and bind.is_global:
  98. self.globals.append(bind)
  99. def ismodbind(glob):
  100. '''Return True if the glob specifies a modbind.'''
  101. return bool(MOD_START(glob))
  102. def split_glob(glob):
  103. '''Take a string of the form "<Mod1><Mod2>cmd _" and return a list of the
  104. modkeys in the glob and the command.'''
  105. mods = set()
  106. while True:
  107. match = MOD_START(glob)
  108. if not match:
  109. break
  110. end = match.span()[1]
  111. mods.add(glob[:end])
  112. glob = glob[end:]
  113. return (mods, glob)
  114. class Bind(object):
  115. # Class attribute to hold the number of Bind classes created.
  116. counter = [0,]
  117. def __init__(self, glob, handler, *args, **kargs):
  118. self.is_callable = callable(handler)
  119. self._repr_cache = None
  120. if not glob:
  121. raise ArgumentError('glob cannot be blank')
  122. if self.is_callable:
  123. self.function = handler
  124. self.args = args
  125. self.kargs = kargs
  126. elif kargs:
  127. raise ArgumentError('cannot supply kargs for uzbl commands')
  128. elif hasattr(handler, '__iter__'):
  129. self.commands = handler
  130. else:
  131. self.commands = [handler,] + list(args)
  132. self.glob = glob
  133. # Assign unique id.
  134. self.counter[0] += 1
  135. self.bid = self.counter[0]
  136. self.split = split = FIND_PROMPTS(glob)
  137. self.prompts = []
  138. for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]):
  139. prompt, set = map(unquote, [prompt, set])
  140. cmd = True if cmd == '!' else False
  141. if prompt and prompt[-1] != ":":
  142. prompt = "%s:" % prompt
  143. self.prompts.append((prompt, cmd, set))
  144. # Check that there is nothing like: fl*<int:>*
  145. for glob in split[:-1:4]:
  146. if glob.endswith('*'):
  147. msg = "token '*' not at the end of a prompt bind: %r" % split
  148. raise SyntaxError(msg)
  149. # Check that there is nothing like: fl<prompt1:><prompt2:>_
  150. for glob in split[4::4]:
  151. if not glob:
  152. msg = 'found null segment after first prompt: %r' % split
  153. raise SyntaxError(msg)
  154. stack = []
  155. for (index, glob) in enumerate(reversed(split[::4])):
  156. # Is the binding a MODCMD or KEYCMD:
  157. mod_cmd = ismodbind(glob)
  158. # Do we execute on UPDATES or EXEC events?
  159. on_exec = True if glob[-1] in ['!', '_'] else False
  160. # Does the command take arguments?
  161. has_args = True if glob[-1] in ['*', '_'] else False
  162. glob = glob[:-1] if has_args or on_exec else glob
  163. mods, glob = split_glob(glob)
  164. stack.append((on_exec, has_args, mods, glob, index))
  165. self.stack = list(reversed(stack))
  166. self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
  167. def __getitem__(self, depth):
  168. '''Get bind info at a depth.'''
  169. if self.is_global:
  170. return self.stack[0]
  171. return self.stack[depth]
  172. def __repr__(self):
  173. if self._repr_cache:
  174. return self._repr_cache
  175. args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
  176. if self.is_callable:
  177. args.append('function=%r' % self.function)
  178. if self.args:
  179. args.append('args=%r' % self.args)
  180. if self.kargs:
  181. args.append('kargs=%r' % self.kargs)
  182. else:
  183. cmdlen = len(self.commands)
  184. cmds = self.commands[0] if cmdlen == 1 else self.commands
  185. args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds))
  186. self._repr_cache = '<Bind(%s)>' % ', '.join(args)
  187. return self._repr_cache
  188. def exec_bind(uzbl, bind, *args, **kargs):
  189. '''Execute bind objects.'''
  190. uzbl.event("EXEC_BIND", bind, args, kargs)
  191. if bind.is_callable:
  192. args += bind.args
  193. kargs = dict(bind.kargs.items()+kargs.items())
  194. bind.function(uzbl, *args, **kargs)
  195. return
  196. if kargs:
  197. raise ArgumentError('cannot supply kargs for uzbl commands')
  198. commands = []
  199. cmd_expand = uzbl.cmd_expand
  200. for cmd in bind.commands:
  201. cmd = cmd_expand(cmd, args)
  202. uzbl.send(cmd)
  203. def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs):
  204. '''Add a mode bind.'''
  205. bindlet = uzbl.bindlet
  206. if not hasattr(modes, '__iter__'):
  207. modes = unicode(modes).split(',')
  208. # Sort and filter binds.
  209. modes = filter(None, map(unicode.strip, modes))
  210. if callable(handler) or (handler is not None and handler.strip()):
  211. bind = Bind(glob, handler, *args, **kargs)
  212. else:
  213. bind = None
  214. for mode in modes:
  215. if not VALID_MODE(mode):
  216. raise NameError('invalid mode name: %r' % mode)
  217. for mode in modes:
  218. if mode[0] == '-':
  219. mode, bind = mode[1:], None
  220. bindlet.add_bind(mode, glob, bind)
  221. uzbl.event('ADDED_MODE_BIND', mode, glob, bind)
  222. def bind(uzbl, glob, handler, *args, **kargs):
  223. '''Legacy bind function.'''
  224. mode_bind(uzbl, 'global', glob, handler, *args, **kargs)
  225. def parse_mode_bind(uzbl, args):
  226. '''Parser for the MODE_BIND event.
  227. Example events:
  228. MODE_BIND <mode> <bind> = <command>
  229. MODE_BIND command o<location:>_ = uri %s
  230. MODE_BIND insert,command <BackSpace> = ...
  231. MODE_BIND global ... = ...
  232. MODE_BIND global,-insert ... = ...
  233. '''
  234. if not args:
  235. raise ArgumentError('missing bind arguments')
  236. split = map(unicode.strip, args.split(' ', 1))
  237. if len(split) != 2:
  238. raise ArgumentError('missing mode or bind section: %r' % args)
  239. modes, args = split[0].split(','), split[1]
  240. split = map(unicode.strip, args.split('=', 1))
  241. if len(split) != 2:
  242. raise ArgumentError('missing delimiter in bind section: %r' % args)
  243. glob, command = split
  244. mode_bind(uzbl, modes, glob, command)
  245. def parse_bind(uzbl, args):
  246. '''Legacy parsing of the BIND event and conversion to the new format.
  247. Example events:
  248. request BIND <bind> = <command>
  249. request BIND o<location:>_ = uri %s
  250. request BIND <BackSpace> = ...
  251. request BIND ... = ...
  252. '''
  253. parse_mode_bind(uzbl, "global %s" % args)
  254. def mode_changed(uzbl, mode):
  255. '''Clear the stack on all non-stack mode changes.'''
  256. if mode != 'stack':
  257. uzbl.bindlet.reset()
  258. def match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet):
  259. (on_exec, has_args, mod_cmd, glob, more) = bind[depth]
  260. cmd = keylet.modcmd if mod_cmd else keylet.keycmd
  261. if mod_cmd and modstate != mod_cmd:
  262. return False
  263. if has_args:
  264. if not cmd.startswith(glob):
  265. return False
  266. args = [cmd[len(glob):],]
  267. elif cmd != glob:
  268. return False
  269. else:
  270. args = []
  271. if bind.is_global or (not more and depth == 0):
  272. exec_bind(uzbl, bind, *args)
  273. if not has_args:
  274. uzbl.clear_current()
  275. return True
  276. elif more:
  277. bindlet.stack(bind, args, depth)
  278. (on_exec, has_args, mod_cmd, glob, more) = bind[depth+1]
  279. if not on_exec and has_args and not glob and not more:
  280. exec_bind(uzbl, bind, *(args+['',]))
  281. return False
  282. args = bindlet.args + args
  283. exec_bind(uzbl, bind, *args)
  284. if not has_args or on_exec:
  285. del uzbl.config['mode']
  286. bindlet.reset()
  287. return True
  288. def key_event(uzbl, modstate, keylet, mod_cmd=False, on_exec=False):
  289. bindlet = uzbl.bindlet
  290. depth = bindlet.depth
  291. for bind in bindlet.get_binds():
  292. t = bind[depth]
  293. if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec):
  294. continue
  295. if match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet):
  296. return
  297. bindlet.after()
  298. # Return to the previous mode if the KEYCMD_EXEC keycmd doesn't match any
  299. # binds in the stack mode.
  300. if on_exec and not mod_cmd and depth and depth == bindlet.depth:
  301. del uzbl.config['mode']
  302. # plugin init hook
  303. def init(uzbl):
  304. '''Export functions and connect handlers to events.'''
  305. connect_dict(uzbl, {
  306. 'BIND': parse_bind,
  307. 'MODE_BIND': parse_mode_bind,
  308. 'MODE_CHANGED': mode_changed,
  309. })
  310. # Connect key related events to the key_event function.
  311. events = [['KEYCMD_UPDATE', 'KEYCMD_EXEC'],
  312. ['MODCMD_UPDATE', 'MODCMD_EXEC']]
  313. for mod_cmd in range(2):
  314. for on_exec in range(2):
  315. event = events[mod_cmd][on_exec]
  316. connect(uzbl, event, key_event, bool(mod_cmd), bool(on_exec))
  317. export_dict(uzbl, {
  318. 'bind': bind,
  319. 'mode_bind': mode_bind,
  320. 'bindlet': Bindlet(uzbl),
  321. })
  322. # vi: set et ts=4: