From fdd8d07eba9e483baf618b6d8fd928fefb8465f3 Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Thu, 15 Oct 2009 00:08:45 +0800 Subject: Added multi-instance managing to event_manager.py 1. Moved plugin directory from './examples/data/scripts/plugins' to './examples/data/'. 2. Broke up the plugin manager class into two small functions. 3. Removed the handler objects ability to have non-callable handlers given that there is a perfectly good on_event.py plugin which can do exactly the same. 4. Gave event_manager daemon abilities similar to the cookie_daemon. 5. Using pid to track the event manager daemons running status. 6. Added the ability to load plugins from multiple locations. 7. Removed all outgoing message queues as this work-around is no longer required after the newly added --connect-socket uzbl-core ability. 8. Removed native stdin/fifo reading ability. Use socat if required. 9. Updated uzbl-browser script to load example cookie_daemon if cookie_daemon is not in $XDG_DATA_HOME/uzbl/scripts/ 10. Added a new event_manager.py launcher uzbl-daemon. 11. Updated make test-dev-browser target to test uzbl-daemon also. 12. Added init like {start|stop|restart} to the event manager. 13. Added a fourth 'list' option to {start|stop|..} to list the plugins and dirs of each plugin that would be loaded by the event manager. --- examples/data/uzbl/plugins/bind.py | 400 +++++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 examples/data/uzbl/plugins/bind.py (limited to 'examples/data/uzbl/plugins/bind.py') diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py new file mode 100644 index 0000000..d62872f --- /dev/null +++ b/examples/data/uzbl/plugins/bind.py @@ -0,0 +1,400 @@ +'''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 +from event_manager import config, counter, iscallable, isiterable + +# Export these variables/functions to uzbl. +__export__ = ['bind', 'del_bind', 'del_bind_by_glob', 'get_binds'] + +# Hold the bind lists per uzbl instance. +UZBLS = {} + +# Commonly used regular expressions. +starts_with_mod = re.compile('^<([A-Z][A-Za-z0-9-_]+)>') +find_prompts = re.compile('<([^:>]*):>').split + +# For accessing a bind glob stack. +MOD_CMD, ON_EXEC, HAS_ARGS, GLOB = range(4) + + +class BindParseError(Exception): + pass + + +def echo(msg): + if config['verbose']: + print 'bind plugin:', msg + + +def error(msg): + sys.stderr.write('bind plugin: error: %s\n' % msg) + + +def ismodbind(glob): + '''Return True if the glob specifies a modbind.''' + + return bool(starts_with_mod.match(glob)) + + +def sort_mods(glob): + '''Mods are sorted in the keylet.to_string() result so make sure that + bind commands also have their mod keys sorted.''' + + mods = [] + while True: + match = starts_with_mod.match(glob) + if not match: + break + + end = match.span()[1] + mods.append(glob[:end]) + glob = glob[end:] + + return '%s%s' % (''.join(sorted(mods)), glob) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = {'binds': [], 'depth': 0, 'filter': [], + 'args': [], 'last_mode': ''} + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_bind_dict(uzbl): + '''Return the bind dict for the uzbl instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_binds(uzbl): + '''Return the bind list for the uzbl instance.''' + + return get_bind_dict(uzbl)['binds'] + + +def get_stack_depth(uzbl): + '''Return the stack for the uzbl instance.''' + + return get_bind_dict(uzbl)['depth'] + + +def get_filtered_binds(uzbl): + '''Return the bind list for the uzbl instance or return the filtered + bind list thats on the current stack.''' + + bind_dict = get_bind_dict(uzbl) + if bind_dict['depth']: + return list(bind_dict['filter']) + + return list(bind_dict['binds']) + + +def del_bind(uzbl, bind): + '''Delete bind object if bind in the uzbl binds.''' + + binds = get_binds(uzbl) + if bind in binds: + binds.remove(bind) + uzbl.event('DELETED_BIND', bind) + return True + + return False + + +def del_bind_by_glob(uzbl, glob): + '''Delete bind by glob if bind in the uzbl binds.''' + + binds = get_binds(uzbl) + for bind in list(binds): + if bind.glob == glob: + binds.remove(bind) + uzbl.event('DELETED_BIND', bind) + return True + + return False + + +class Bind(object): + + nextbid = counter().next + + def __init__(self, glob, handler, *args, **kargs): + self.callable = iscallable(handler) + + if not glob: + raise ArgumentError('glob cannot be blank') + + if self.callable: + self.function = handler + self.args = args + self.kargs = kargs + + elif kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + elif isiterable(handler): + self.commands = handler + + else: + self.commands = [handler,] + list(args) + + self.glob = glob + self.bid = self.nextbid() + + self.split = split = find_prompts(glob) + self.prompts = split[1::2] + + # Check that there is nothing like: fl** + for glob in split[:-1:2]: + if glob.endswith('*'): + msg = "token '*' not at the end of a prompt bind: %r" % split + raise BindParseError(msg) + + # Check that there is nothing like: fl_ + for glob in split[2::2]: + if not glob: + msg = 'found null segment after first prompt: %r' % split + raise BindParseError(msg) + + self.stack = [] + + for glob in split[::2]: + # Is the binding a MODCMD or KEYCMD: + mod_cmd = ismodbind(glob) + + # Execute the command on UPDATES or EXEC's: + on_exec = True if glob.endswith('_') else False + + # Does the command store arguments: + has_args = True if glob[-1] in ['*', '_'] else False + glob = glob[:-1] if has_args else glob + + self.stack.append((mod_cmd, on_exec, has_args, glob)) + + + def __repr__(self): + args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] + + if self.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)) + + return '' % ', '.join(args) + + +def exec_bind(uzbl, bind, *args, **kargs): + '''Execute bind objects.''' + + if bind.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 = [] + + for cmd in bind.commands: + if '%s' in cmd: + if len(args) > 1: + for arg in args: + cmd = cmd.replace('%s', arg, 1) + + elif len(args) == 1: + cmd = cmd.replace('%s', args[0]) + + 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. + glob = sort_mods(glob) + + del_bind_by_glob(uzbl, glob) + binds = get_binds(uzbl) + + bind = Bind(glob, handler, *args, **kargs) + binds.append(bind) + + print bind + uzbl.event('ADDED_BIND', bind) + + +def parse_bind_event(uzbl, args): + '''Break "event BIND fl* = js follownums.js" into (glob, command).''' + + if not args: + return error('missing bind arguments') + + split = map(unicode.strip, args.split('=', 1)) + if len(split) != 2: + return error('missing "=" in bind definition: %r' % args) + + glob, command = split + bind(uzbl, glob, command) + + +def set_stack_mode(uzbl, prompt): + if uzbl.get_mode() != 'stack': + uzbl.set_mode('stack') + + if prompt: + prompt = "%s: " % prompt + + uzbl.set('keycmd_prompt', prompt) + + +def clear_stack(uzbl, mode): + bind_dict = get_bind_dict(uzbl) + if mode != "stack" and bind_dict['last_mode'] == "stack": + uzbl.set('keycmd_prompt', '') + + if mode != "stack": + bind_dict = get_bind_dict(uzbl) + bind_dict['filter'] = [] + bind_dict['depth'] = 0 + bind_dict['args'] = [] + + bind_dict['last_mode'] = mode + + +def filter_bind(uzbl, bind_dict, bind): + '''Remove a bind from the stack filter list.''' + + if bind in bind_dict['filter']: + bind_dict['filter'].remove(bind) + + if not bind_dict['filter']: + uzbl.set_mode() + + +def match_and_exec(uzbl, bind, depth, keycmd): + bind_dict = get_bind_dict(uzbl) + mode_cmd, on_exec, has_args, glob = bind.stack[depth] + + if has_args: + if not keycmd.startswith(glob): + filter_bind(uzbl, bind_dict, bind) + return False + + args = [keycmd[len(glob):],] + + elif keycmd != glob: + filter_bind(uzbl, bind_dict, bind) + return False + + else: + args = [] + + execindex = len(bind.stack)-1 + if execindex == depth == 0: + exec_bind(uzbl, bind, *args) + if not has_args: + uzbl.clear_keycmd() + + return True + + elif depth != execindex: + if bind_dict['depth'] == depth: + bind_dict['filter'] = [bind,] + bind_dict['args'] += args + bind_dict['depth'] = depth + 1 + + else: + if bind not in bind_dict['filter']: + bind_dict['filter'].append(bind) + + set_stack_mode(uzbl, bind.prompts[depth]) + return False + + args = bind_dict['args'] + args + exec_bind(uzbl, bind, *args) + if on_exec: + uzbl.set_mode() + + return True + + +def keycmd_update(uzbl, keylet): + depth = get_stack_depth(uzbl) + keycmd = keylet.to_string() + for bind in get_filtered_binds(uzbl): + t = bind.stack[depth] + if t[MOD_CMD] or t[ON_EXEC]: + continue + + match_and_exec(uzbl, bind, depth, keycmd) + + +def keycmd_exec(uzbl, keylet): + depth = get_stack_depth(uzbl) + keycmd = keylet.to_string() + for bind in get_filtered_binds(uzbl): + t = bind.stack[depth] + if t[MOD_CMD] or not t[ON_EXEC]: + continue + + match_and_exec(uzbl, bind, depth, keycmd) + + +def modcmd_update(uzbl, keylet): + depth = get_stack_depth(uzbl) + keycmd = keylet.to_string() + for bind in get_filtered_binds(uzbl): + t = bind.stack[depth] + if not t[MOD_CMD] or t[ON_EXEC]: + continue + + match_and_exec(uzbl, bind, depth, keycmd) + + +def modcmd_exec(uzbl, keylet): + depth = get_stack_depth(uzbl) + keycmd = keylet.to_string() + for bind in get_filtered_binds(uzbl): + t = bind.stack[depth] + if not t[MOD_CMD] or not t[ON_EXEC]: + continue + + match_and_exec(uzbl, bind, depth, keycmd) + + +def init(uzbl): + connects = {'BIND': parse_bind_event, + 'KEYCMD_UPDATE': keycmd_update, + 'MODCMD_UPDATE': modcmd_update, + 'KEYCMD_EXEC': keycmd_exec, + 'MODCMD_EXEC': modcmd_exec, + 'MODE_CHANGED': clear_stack} + + uzbl.connect_dict(connects) -- cgit v1.2.3 From 828dcf0378e461c746f6f95d33256a47a963fb64 Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Sun, 25 Oct 2009 02:41:53 +0800 Subject: bind.py and keycmd.py plugins refactor & cleanup after keis merge. --- examples/config/uzbl/config | 35 +++-- examples/data/uzbl/plugins/bind.py | 89 ++++++----- examples/data/uzbl/plugins/keycmd.py | 292 +++++++++++++++++++---------------- 3 files changed, 232 insertions(+), 184 deletions(-) (limited to 'examples/data/uzbl/plugins/bind.py') diff --git a/examples/config/uzbl/config b/examples/config/uzbl/config index b518914..01ea3ef 100644 --- a/examples/config/uzbl/config +++ b/examples/config/uzbl/config @@ -62,12 +62,13 @@ set show_status = 1 set status_top = 0 set status_background = #303030 -set keycmd_style = weight="bold" foreground="red" +set modcmd_style = weight="bold" foreground="red" +set keycmd_style = weight="light" foreground="red" set prompt_style = foreground="grey" set cursor_style = underline="single" set mode_section = [\@[\@mode_indicator]\@] -set keycmd_section = [\@[\@keycmd_prompt]\@\@keycmd] +set keycmd_section = [\@[\@keycmd_prompt]\@\@modcmd\@keycmd] set progress_section = \@[\@progress_format]\@ set uri_section = \@[\@uri]\@ set name_section = \@[\@NAME]\@ @@ -189,30 +190,32 @@ set formfiller = spawn @scripts_dir/formfiller # Prints tab separated "uri title keyword tags" to the bookmarks file. # TODO: Improve bookmarks script to handle this format & include date in bookmark format. -@bind __ = sh 'echo -e "$6 $7 %s %s" >> $XDG_DATA_HOME/uzbl/bookmarks' +@bind b__ = sh 'echo -e "$6 $7 %s %s" >> $XDG_DATA_HOME/uzbl/bookmarks' # Multi-stage bindings with blank prompts (similar behaviour to emacs M-c M-s bindings?) -@bind <:> = exit -@bind <:> = uri http://uzbl.org/ +@bind a<:>q = exit +@bind a<:>h = uri http://uzbl.org/ # === command editing configuration ========================================== # you'll want this at the very least -@bind = request KEYCMD_EXEC_CURRENT +@bind = event KEYCMD_EXEC_CURRENT +@bind = event @set_mode # basic searching -@bind = request SET_CURSOR_POS 0 -@bind = request SET_CURSOR_POS -1 -@bind = request SET_CURSOR_POS - -@bind = request SET_CURSOR_POS + -@bind = request KEYCMD_BACKSPACE +@bind = event SET_CURSOR_POS 0 +@bind = event SET_CURSOR_POS -1 +@bind = event SET_CURSOR_POS - +@bind = event SET_CURSOR_POS + +@bind = event KEYCMD_BACKSPACE +@bind = event KEYCMD_DELETE # readline-ish bindings -@bind = request KEYCMD_STRIP_WORD -@bind = request SET_KEYCMD -@bind = request SET_CURSOR_POS 0 -@bind = request SET_CURSOR_POS -1 +@bind w = event KEYCMD_STRIP_WORD +@bind u = event SET_KEYCMD +@bind a = event SET_CURSOR_POS 0 +@bind e = event SET_CURSOR_POS -1 # === Context menu items ===================================================== @@ -235,7 +238,7 @@ set insert = @mode_config insert set stack = @mode_config stack # Command mode config. -@command keycmd_style = weight="bold" foreground="red" +@command keycmd_style = foreground="red" @command status_background = #202020 @command mode_indicator = Cmd diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 7a2d1cc..fe017eb 100644 --- a/examples/data/uzbl/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -135,12 +135,13 @@ class Bind(object): nextbid = counter().next def __init__(self, glob, handler, *args, **kargs): - self.callable = iscallable(handler) + self.is_callable = iscallable(handler) + self._repr_cache = None if not glob: raise ArgumentError('glob cannot be blank') - if self.callable: + if self.is_callable: self.function = handler self.args = args self.kargs = kargs @@ -172,9 +173,8 @@ class Bind(object): msg = 'found null segment after first prompt: %r' % split raise BindParseError(msg) - self._stack = [] - - for glob in split[::2]: + stack = [] + for (index, glob) in enumerate(reversed(split[::2])): # Is the binding a MODCMD or KEYCMD: mod_cmd = ismodbind(glob) @@ -185,22 +185,28 @@ class Bind(object): has_args = True if glob[-1] in ['*', '_'] else False glob = glob[:-1] if has_args else glob - self._stack.append((mod_cmd, on_exec, has_args, glob)) + stack.append((mod_cmd, on_exec, has_args, glob, index)) + + self.stack = list(reversed(stack)) + self.is_global = len(self.stack) == 1 - def is_global(self): - ''' Returns True if this Bind should be applied in all depths ''' - return 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], False - return self._stack[depth], depth - len(self._stack) + 1 + '''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.callable: + if self.is_callable: args.append('function=%r' % self.function) if self.args: args.append('args=%r' % self.args) @@ -213,13 +219,16 @@ class Bind(object): cmds = self.commands[0] if cmdlen == 1 else self.commands args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) - return '' % ', '.join(args) + self._repr_cache = '' % ', '.join(args) + return self._repr_cache def exec_bind(uzbl, bind, *args, **kargs): '''Execute bind objects.''' - if bind.callable: + 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) @@ -229,7 +238,6 @@ def exec_bind(uzbl, bind, *args, **kargs): raise ArgumentError('cannot supply kargs for uzbl commands') commands = [] - for cmd in bind.commands: if '%s' in cmd: if len(args) > 1: @@ -309,12 +317,13 @@ def filter_bind(uzbl, bind_dict, bind): def match_and_exec(uzbl, bind, depth, keycmd): bind_dict = get_bind_dict(uzbl) - (mod_cmd, on_exec, has_args, glob), more = bind[depth] + (mod_cmd, on_exec, has_args, glob, more) = bind[depth] if has_args: if not keycmd.startswith(glob): if not mod_cmd: filter_bind(uzbl, bind_dict, bind) + return False args = [keycmd[len(glob):],] @@ -322,28 +331,28 @@ def match_and_exec(uzbl, bind, depth, keycmd): elif keycmd != glob: if not mod_cmd: filter_bind(uzbl, bind_dict, bind) + return False else: args = [] - if bind.is_global() or (not more and depth == 0): + if bind.is_global or (not more and depth == 0): exec_bind(uzbl, bind, *args) - if not has_args and not mod_cmd: - uzbl.clear_keycmd() + if not has_args: + uzbl.clear_current() return True elif more: if bind_dict['depth'] == depth: - globalcmds = [cmd for cmd in bind_dict['binds'] if cmd.is_global()] + globalcmds = [cmd for cmd in bind_dict['binds'] if cmd.is_global] bind_dict['filter'] = [bind,] + globalcmds bind_dict['args'] += args bind_dict['depth'] = depth + 1 - else: - if bind not in bind_dict['filter']: - bind_dict['filter'].append(bind) + elif bind not in bind_dict['filter']: + bind_dict['filter'].append(bind) set_stack_mode(uzbl, bind.prompts[depth]) return False @@ -351,52 +360,58 @@ def match_and_exec(uzbl, bind, depth, keycmd): args = bind_dict['args'] + args exec_bind(uzbl, bind, *args) uzbl.set_mode() + if not has_args: + uzbl.clear_current() return True def keycmd_update(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.key_cmd() + keycmd = keylet.get_keycmd() for bind in get_filtered_binds(uzbl): - (t,more) = bind[depth] + t = bind[depth] if t[MOD_CMD] or t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return def keycmd_exec(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.key_cmd() + keycmd = keylet.get_keycmd() for bind in get_filtered_binds(uzbl): - (t,more) = bind[depth] + t = bind[depth] if t[MOD_CMD] or not t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return uzbl.clear_keycmd() def modcmd_update(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.mod_cmd() + keycmd = keylet.get_modcmd() for bind in get_filtered_binds(uzbl): - (t,more) = bind[depth] + t = bind[depth] if not t[MOD_CMD] or t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return def modcmd_exec(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.mod_cmd() + keycmd = keylet.get_modcmd() for bind in get_filtered_binds(uzbl): - (t,more) = bind[depth] + t = bind[depth] if not t[MOD_CMD] or not t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return uzbl.clear_modcmd() def init(uzbl): diff --git a/examples/data/uzbl/plugins/keycmd.py b/examples/data/uzbl/plugins/keycmd.py index 79a96e4..109bb38 100644 --- a/examples/data/uzbl/plugins/keycmd.py +++ b/examples/data/uzbl/plugins/keycmd.py @@ -1,16 +1,14 @@ import re # Map these functions/variables in the plugins namespace to the uzbl object. -__export__ = ['clear_keycmd', 'set_keycmd', 'set_cursor_pos', 'get_keylet'] - -# Regular expression compile cache. -_RE_CACHE = {} +__export__ = ['clear_keycmd', 'set_keycmd', 'set_cursor_pos', 'get_keylet', + 'clear_current', 'clear_modcmd'] # Hold the keylets. UZBLS = {} # Simple key names map. -_SIMPLEKEYS = { +SIMPLEKEYS = { 'Control': 'Ctrl', 'ISO_Left_Tab': 'Shift-Tab', 'space':'Space', @@ -20,7 +18,7 @@ _SIMPLEKEYS = { KEYCMD_FORMAT = "%s%s%s" -def escape(str): +def uzbl_escape(str): '''Prevent outgoing keycmd values from expanding inside the status_format.''' @@ -34,61 +32,57 @@ def escape(str): return "@[%s]@" % str -def get_regex(regex): - '''Compiling regular expressions is a very time consuming so return a - pre-compiled regex match object if possible.''' - - if regex not in _RE_CACHE: - _RE_CACHE[regex] = re.compile(regex).match - - return _RE_CACHE[regex] - - class Keylet(object): '''Small per-instance object that tracks all the keys held and characters typed.''' def __init__(self): - self.cmd = '' - self.cursor = 0 + # Modcmd tracking self.held = [] + self.modcmd = '' + self.is_modcmd = False + + # Keycmd tracking + self.keycmd = '' + self.cursor = 0 - # to_string() string building cache. - self._to_string = None + # Keylet string repr cache. + self._repr_cache = None - self.modcmd = False - def mod_held(self): - ''' returns true if any modkey is held. ''' - return any([len(x) != 1 for x in self.held]) + def get_keycmd(self): + '''Get the keycmd-part of the keylet.''' - def key_cmd(self): - ''' get the keycmd-part of the keylet. ''' - return self.cmd + return self.keycmd + + + def get_modcmd(self): + '''Get the modcmd-part of the keylet.''' + + if not self.is_modcmd: + return '' + + return ''.join(['<%s>' % key for key in self.held]) + self.modcmd - def mod_cmd(self): - ''' get the modcmd-part of the keylet. ''' - return ''.join(['<%s>' % key for key in self.held]) def __repr__(self): - return '' % self.to_string() + '''Return a string representation of the keylet.''' - def to_string(self): - '''Return a string representation of the keys held and pressed that - have been recorded.''' + if self._repr_cache: + return self._repr_cache - if self._to_string is not None: - # Return cached keycmd string. - return self._to_string - - self._to_string = self.mod_cmd() + self.key_cmd() - return self._to_string + l = [] + if self.is_modcmd: + l.append('modcmd=%r' % self.get_modcmd()) + elif self.held: + l.append('held=%r' % ''.join(['<%s>'%key for key in self.held])) - def match(self, regex): - '''See if the keycmd string matches the given regex.''' + if self.keycmd: + l.append('keycmd=%r' % self.get_keycmd()) - return bool(get_regex(regex)(self.to_string())) + self._repr_cache = '' % ', '.join(l) + return self._repr_cache def make_simple(key): @@ -98,8 +92,8 @@ def make_simple(key): if key.endswith('_L') or key.endswith('_R'): key = key[:-2] - if key in _SIMPLEKEYS: - key = _SIMPLEKEYS[key] + if key in SIMPLEKEYS: + key = SIMPLEKEYS[key] return key @@ -126,7 +120,7 @@ def get_keylet(uzbl): add_instance(uzbl) keylet = UZBLS[uzbl] - keylet._to_string = None + keylet._repr_cache = False return keylet @@ -134,55 +128,92 @@ def clear_keycmd(uzbl): '''Clear the keycmd for this uzbl instance.''' k = get_keylet(uzbl) - k.cmd = '' + k.keycmd = '' k.cursor = 0 - k._to_string = None - - k.modcmd = k.mod_held() + k._repr_cache = False config = uzbl.get_config() if 'keycmd' not in config or config['keycmd'] != '': - config['keycmd'] = '' + uzbl.set('keycmd', '') + uzbl.send('update_gui') uzbl.event('KEYCMD_CLEAR') +def clear_modcmd(uzbl, clear_held=False): + '''Clear the modcmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.modcmd = '' + k.is_modcmd = False + k._repr_cache = False + if clear_held: + k.held = [] + + config = uzbl.get_config() + if 'modcmd' not in config or config['modcmd'] != '': + uzbl.set('modcmd', '') + uzbl.send('update_gui') + + uzbl.event('MODCMD_CLEAR') + + +def clear_current(uzbl): + '''Clear the modcmd if is_modcmd else clear keycmd.''' + + k = get_keylet(uzbl) + if k.is_modcmd: + clear_modcmd(uzbl) + + else: + clear_keycmd(uzbl) + + +def focus_changed(uzbl, *args): + '''Focus to the uzbl instance has now been lost which means all currently + held keys in the held list will not get a KEY_RELEASE event so clear the + entire held list.''' + + clear_modcmd(uzbl, clear_held=True) + + def update_event(uzbl, k, execute=True): '''Raise keycmd & modcmd update events.''' config = uzbl.get_config() - if k.modcmd: - keycmd = k.to_string() - if execute: - uzbl.event('MODCMD_UPDATE', k) - if keycmd != k.to_string(): - return + keycmd, modcmd = k.get_keycmd(), k.get_modcmd() - if 'modcmd_updates' in config and config['modcmd_updates'] != '1': - return + if k.is_modcmd: + uzbl.event('MODCMD_UPDATE', k) - return uzbl.set('keycmd', escape(keycmd)) + else: + uzbl.event('KEYCMD_UPDATE', k) + + if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': + new_modcmd = k.get_modcmd() + if not new_modcmd or new_modcmd == modcmd: + uzbl.set('modcmd', uzbl_escape(new_modcmd)) if 'keycmd_events' in config and config['keycmd_events'] != '1': - return + return uzbl.send('update_gui') - keycmd = k.cmd - if execute: - uzbl.event('KEYCMD_UPDATE', k) - if keycmd != k.cmd: - return + new_keycmd = k.get_keycmd() + if not new_keycmd or new_keycmd != keycmd: + uzbl.set('keycmd', '') + return uzbl.send('update_gui') - if not k.cmd: - return uzbl.set('keycmd', '') # Generate the pango markup for the cursor in the keycmd. - if k.cursor < len(k.cmd): - cursor = k.cmd[k.cursor] + curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' + chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] + uzbl.set('keycmd', KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks))) + uzbl.send('update_gui') - else: - cursor = ' ' - chunks = map(escape, [k.cmd[:k.cursor], cursor, k.cmd[k.cursor+1:]]) - uzbl.set('keycmd', KEYCMD_FORMAT % tuple(chunks)) +def inject_char(str, index, char): + '''Inject character into string at at given index.''' + + assert len(char) == 1 + return "%s%s%s" % (str[:index], char, str[index:]) def key_press(uzbl, key): @@ -202,41 +233,34 @@ def key_press(uzbl, key): key = make_simple(key) k = get_keylet(uzbl) - cmdmod = False - - if k.cmd and not k.modcmd and key == 'Space': - k.cmd = "%s %s" % (k.cmd[:k.cursor], k.cmd[k.cursor:]) + if key == 'Space' and not k.held and k.keycmd: + k.keycmd = inject_char(k.keycmd, k.cursor, ' ') k.cursor += 1 - cmdmod = True - elif len(key) > 1: - k.modcmd = True - cmdmod = True - - elif not k.modcmd: + elif not k.held and len(key) == 1: config = uzbl.get_config() if 'keycmd_events' not in config or config['keycmd_events'] == '1': - if len(key) == 1: - cmdmod = True - k.cmd = "%s%s%s" % (k.cmd[:k.cursor], key, k.cmd[k.cursor:]) - k.cursor += 1 - - elif k.cmd: - cmdmod = True - k.cmd = '' + k.keycmd = inject_char(k.keycmd, k.cursor, key) + k.cursor += 1 + + elif k.keycmd: + k.keycmd = '' k.cursor = 0 - if k.modcmd: + elif len(key) > 1: + k.is_modcmd = True if key == 'Shift-Tab' and 'Tab' in k.held: k.held.remove('Tab') if key not in k.held: k.held.append(key) k.held.sort() - cmdmod = True - if cmdmod: - update_event(uzbl, k) + else: + k.is_modcmd = True + k.modcmd += key + + update_event(uzbl, k) def key_release(uzbl, key): @@ -251,8 +275,6 @@ def key_release(uzbl, key): key = make_simple(key) k = get_keylet(uzbl) - - cmdmod = False if key in ['Shift', 'Tab'] and 'Shift-Tab' in k.held: key = 'Shift-Tab' @@ -260,65 +282,69 @@ def key_release(uzbl, key): key = 'Meta' if key in k.held: - cmdmod = True - k.held.remove(key) - k.modcmd = k.mod_held() - if k.modcmd: + if k.is_modcmd: uzbl.event('MODCMD_EXEC', k) - if cmdmod: - update_event(uzbl, k) + k.held.remove(key) + #k.is_modcmd = False + #k.modcmd = '' + #update_event(uzbl, k) + clear_modcmd(uzbl) def set_keycmd(uzbl, keycmd): '''Allow setting of the keycmd externally.''' k = get_keylet(uzbl) - k.modcmd = False - k._to_string = None - k.cmd = keycmd + k.keycmd = keycmd + k._repr_cache = None k.cursor = len(keycmd) - update_event(uzbl, k, False) def keycmd_strip_word(uzbl, sep): ''' Removes the last word from the keycmd, similar to readline ^W ''' + sep = sep or ' ' k = get_keylet(uzbl) - if not k.cmd: + if not k.keycmd: return - cmd = k.cmd[:k.cursor] - tail = len(k.cmd) - k.cursor + head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] + rfind = head.rfind(sep) + head = head[:rfind] if rfind + 1 else '' + k.keycmd = head + tail + k.cursor = len(head) + update_event(uzbl, k, False) - if sep in cmd: - tmp = cmd.rstrip(sep).rsplit(sep, 1) - else: - tmp = ('',) - k.cmd = tmp[0] + (sep if len(tmp) == 2 else '') + k.cmd[k.cursor:] - k.cursor = len(tmp[0]) + (len(tmp) - 1) +def keycmd_backspace(uzbl, *args): + '''Removes the character at the cursor position in the keycmd.''' - assert len(k.cmd) - k.cursor == tail, "tail size changed (%s) (%s - %s)" % (tail, len(k.cmd), k.cursor) + k = get_keylet(uzbl) + if not k.keycmd: + return + k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] + k.cursor -= 1 update_event(uzbl, k, False) -def keycmd_backspace(uzbl, _foo): - ''' Removes the last char of the keycmd ''' +def keycmd_delete(uzbl, *args): + '''Removes the character after the cursor position in the keycmd.''' + k = get_keylet(uzbl) - if not k.cmd: + if not k.keycmd: return - k.cmd = k.cmd[:k.cursor-1] + k.cmd[k.cursor:] - k.cursor -= 1 - + k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] update_event(uzbl, k, False) -def keycmd_exec_current(uzbl, _foo): - ''' Raise a KEYCMD_EXEC with the current keylet and then clear the keycmd ''' +def keycmd_exec_current(uzbl, *args): + '''Raise a KEYCMD_EXEC with the current keylet and then clear the + keycmd.''' + k = get_keylet(uzbl) uzbl.event('KEYCMD_EXEC', k) clear_keycmd(uzbl) @@ -329,21 +355,22 @@ def set_cursor_pos(uzbl, index): indexing and relative stepping with '+' and '-'.''' k = get_keylet(uzbl) - if index == '-': cursor = k.cursor - 1 + elif index == '+': cursor = k.cursor + 1 + else: cursor = int(index.strip()) if cursor < 0: - cursor = len(k.cmd) + cursor + 1 + cursor = len(k.keycmd) + cursor + 1 if cursor < 0: cursor = 0 - if cursor > len(k.cmd): - cursor = len(k.cmd) + if cursor > len(k.keycmd): + cursor = len(k.keycmd) k.cursor = cursor update_event(uzbl, k, False) @@ -359,7 +386,10 @@ def init(uzbl): 'SET_KEYCMD': set_keycmd, 'KEYCMD_STRIP_WORD': keycmd_strip_word, 'KEYCMD_BACKSPACE': keycmd_backspace, + 'KEYCMD_DELETE': keycmd_delete, 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, - 'SET_CURSOR_POS': set_cursor_pos} + 'SET_CURSOR_POS': set_cursor_pos, + 'FOCUS_LOST': focus_changed, + 'FOCUS_GAINED': focus_changed} uzbl.connect_dict(connects) -- cgit v1.2.3