Merge remote-tracking branch 'zyx-i/doc-lint' into develop

This commit is contained in:
Kim Silkebækken 2013-04-20 16:56:43 +02:00
commit badc2cf891
15 changed files with 477 additions and 160 deletions

View File

@ -377,14 +377,13 @@ Themes
can be aligned with the ``align`` property. can be aligned with the ``align`` property.
``priority`` ``priority``
Optional segment priority. Segments with priority ``-1`` (the Optional segment priority. Segments with priority ``None`` (the default
default priority) will always be included, regardless of the width priority, represented by ``null`` in json) will always be included,
of the prompt/statusline. regardless of the width of the prompt/statusline.
If the priority is ``0`` or more, the segment may be removed if the If the priority is any number, the segment may be removed if the
prompt/statusline width is too small for all the segments to be prompt/statusline width is too small for all the segments to be
rendered. A lower number means that the segment has a higher rendered. A lower number means that the segment has a higher priority.
priority.
Segments are removed according to their priority, with low priority Segments are removed according to their priority, with low priority
segments being removed first. segments being removed first.

View File

@ -91,12 +91,12 @@ Usage
Vim statusline Vim statusline
-------------- --------------
Add the following line to your :file:`vimrc`, where ``{path}`` is the Add the following line to your :file:`vimrc`, where ``{repository_root}`` is the
absolute path to your Powerline installation directory: absolute path to your Powerline installation directory:
.. code-block:: vim .. code-block:: vim
set rtp+={path}/powerline/bindings/vim set rtp+={repository_root}/powerline/bindings/vim
If you're using Vundle or Pathogen and don't want Powerline functionality in If you're using Vundle or Pathogen and don't want Powerline functionality in
any other applications, simply add Powerline as a bundle and point the path any other applications, simply add Powerline as a bundle and point the path
@ -115,22 +115,22 @@ Shell prompts
Bash prompt Bash prompt
^^^^^^^^^^^ ^^^^^^^^^^^
Add the following line to your :file:`bashrc`, where ``{path}`` is the Add the following line to your :file:`bashrc`, where ``{repository_root}`` is
absolute path to your Powerline installation directory: the absolute path to your Powerline installation directory:
.. code-block:: bash .. code-block:: bash
. {path}/powerline/bindings/bash/powerline.sh . {repository_root}/powerline/bindings/bash/powerline.sh
Zsh prompt Zsh prompt
^^^^^^^^^^ ^^^^^^^^^^
Add the following line to your :file:`zshrc`, where ``{path}`` is the Add the following line to your :file:`zshrc`, where ``{repository_root}`` is the
absolute path to your Powerline installation directory: absolute path to your Powerline installation directory:
.. code-block:: bash .. code-block:: bash
. {path}/powerline/bindings/zsh/powerline.zsh . {repository_root}/powerline/bindings/zsh/powerline.zsh
If you are not satisfied with powerline speed in this case, compile zpython If you are not satisfied with powerline speed in this case, compile zpython
branch from https://bitbucket.org/ZyX_I/zsh. branch from https://bitbucket.org/ZyX_I/zsh.
@ -138,10 +138,10 @@ branch from https://bitbucket.org/ZyX_I/zsh.
Tmux statusline Tmux statusline
--------------- ---------------
Add the following line to your :file:`tmux.conf`, where ``{path}`` is the Add the following line to your :file:`tmux.conf`, where ``{repository_root}`` is
absolute path to your Powerline installation directory:: the absolute path to your Powerline installation directory::
source '{path}/powerline/bindings/tmux/powerline.conf' source '{repository_root}/powerline/bindings/tmux/powerline.conf'
IPython prompt IPython prompt
-------------- --------------
@ -174,12 +174,12 @@ Awesome widget
.. note:: The Powerline widget will spawn a shell script that runs in the .. note:: The Powerline widget will spawn a shell script that runs in the
background and updates the statusline with ``awesome-client``. background and updates the statusline with ``awesome-client``.
Add the following to your :file:`rc.lua`, where ``{path}`` is the absolute Add the following to your :file:`rc.lua`, where ``{repository_root}`` is the
path to your Powerline installation directory: absolute path to your Powerline installation directory:
.. code-block:: lua .. code-block:: lua
package.path = package.path .. ';{path}/powerline/bindings/awesome/?.lua' package.path = package.path .. ';{repository_root}/powerline/bindings/awesome/?.lua'
require('powerline') require('powerline')
Then add the ``powerline_widget`` to your ``wibox``: Then add the ``powerline_widget`` to your ``wibox``:

View File

@ -1,8 +1,8 @@
# vim:fileencoding=utf-8:noet
from sphinx.ext import autodoc from sphinx.ext import autodoc
from sphinx.util.inspect import getargspec from inspect import formatargspec
from inspect import ArgSpec, formatargspec from powerline.lint.inspect import getconfigargspec
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment from powerline.lib.threaded import ThreadedSegment
from itertools import count
try: try:
from __builtin__ import unicode from __builtin__ import unicode
@ -25,55 +25,7 @@ class ThreadedDocumenter(autodoc.FunctionDocumenter):
super(ThreadedDocumenter, cls).can_document_member(member, membername, isattr, parent)) super(ThreadedDocumenter, cls).can_document_member(member, membername, isattr, parent))
def format_args(self): def format_args(self):
if isinstance(self.object, ThreadedSegment): argspec = getconfigargspec(self.object)
args = ['interval']
defaults = [getattr(self.object, 'interval', 1)]
if self.object.update_first:
args.append('update_first')
defaults.append(True)
methods = ['render', 'set_state']
if isinstance(self.object, KwThreadedSegment):
methods += ['key', 'render_one']
for method in methods:
if hasattr(self.object, method):
# Note: on <python-2.6 it may return simple tuple, not
# ArgSpec instance.
argspec = getargspec(getattr(self.object, method))
for i, arg in zip(count(-1, -1), reversed(argspec.args)):
if (arg == 'self' or
(arg == 'segment_info' and
getattr(self.object, 'powerline_requires_segment_info', None)) or
(method == 'render_one' and -i == len(argspec.args)) or
arg in args):
continue
if argspec.defaults and len(argspec.defaults) >= -i:
default = argspec.defaults[i]
defaults.append(default)
args.append(arg)
else:
args.insert(0, arg)
argspec = ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults))
else:
if hasattr(self.object, 'powerline_origin'):
obj = self.object.powerline_origin
else:
obj = self.object
argspec = getargspec(obj)
args = []
defaults = []
for i, arg in zip(count(-1, -1), reversed(argspec.args)):
if (arg == 'segment_info' and getattr(self.object, 'powerline_requires_segment_info', None)):
continue
if argspec.defaults and len(argspec.defaults) >= -i:
default = argspec.defaults[i]
defaults.append(default)
args.append(arg)
else:
args.insert(0, arg)
argspec = ArgSpec(args=args, varargs=argspec.varargs, keywords=argspec.keywords, defaults=tuple(defaults))
return formatargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\') return formatargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\')

View File

@ -23,7 +23,7 @@ def find_config_file(search_paths, config_file):
raise IOError('Config file not found in search path: {0}'.format(config_file)) raise IOError('Config file not found in search path: {0}'.format(config_file))
class PowerlineState(object): class PowerlineLogger(object):
def __init__(self, use_daemon_threads, logger, ext): def __init__(self, use_daemon_threads, logger, ext):
self.logger = logger self.logger = logger
self.ext = ext self.ext = ext
@ -175,7 +175,7 @@ class Powerline(object):
self.logger.addHandler(handler) self.logger.addHandler(handler)
if not self.pl: if not self.pl:
self.pl = PowerlineState(self.use_daemon_threads, self.logger, self.ext) self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext)
if not self.config_loader.pl: if not self.config_loader.pl:
self.config_loader.pl = self.pl self.config_loader.pl = self.pl

View File

@ -3,6 +3,9 @@ from powerline import find_config_file, Powerline
from powerline.lib.config import load_json_config from powerline.lib.config import load_json_config
from powerline.lint.markedjson.error import echoerr, MarkedError from powerline.lint.markedjson.error import echoerr, MarkedError
from powerline.segments.vim import vim_modes from powerline.segments.vim import vim_modes
from powerline.lint.inspect import getconfigargspec
from powerline.lint.markedjson.markedvalue import gen_marked_value
from powerline.lib.threaded import ThreadedSegment
import itertools import itertools
import sys import sys
import os import os
@ -25,8 +28,33 @@ def open_file(path):
EMPTYTUPLE = tuple() EMPTYTUPLE = tuple()
class JStr(unicode):
def join(self, iterable):
return super(JStr, self).join((unicode(item) for item in iterable))
key_sep = JStr('/')
list_sep = JStr(', ')
def context_key(context): def context_key(context):
return '/'.join((unicode(c[0]) for c in context)) return key_sep.join((c[0] for c in context))
class DelayedEchoErr(object):
def __init__(self, echoerr):
self.echoerr = echoerr
self.errs = []
def __call__(self, *args, **kwargs):
self.errs.append((args, kwargs))
def echo_all(self):
for args, kwargs in self.errs:
self.echoerr(*args, **kwargs)
def __nonzero__(self):
return not not self.errs
class Spec(object): class Spec(object):
@ -80,7 +108,7 @@ class Spec(object):
context_mark=context_mark, context_mark=context_mark,
problem='{0!r} must be a {1} instance, not {2}'.format( problem='{0!r} must be a {1} instance, not {2}'.format(
value, value,
', '.join((t.__name__ for t in types)), list_sep.join((t.__name__ for t in types)),
type(value.value).__name__ type(value.value).__name__
), ),
problem_mark=value.mark) problem_mark=value.mark)
@ -118,10 +146,7 @@ class Spec(object):
return True, hadproblem return True, hadproblem
def check_either(self, value, context_mark, data, context, echoerr, start, end): def check_either(self, value, context_mark, data, context, echoerr, start, end):
errs = [] new_echoerr = DelayedEchoErr(echoerr)
def new_echoerr(*args, **kwargs):
errs.append((args, kwargs))
hadproblem = False hadproblem = False
for spec in self.specs[start:end]: for spec in self.specs[start:end]:
@ -131,8 +156,7 @@ class Spec(object):
if not hadproblem: if not hadproblem:
return True, False return True, False
for args, kwargs in errs: new_echoerr.echo_all()
echoerr(*args, **kwargs)
return False, hadproblem return False, hadproblem
@ -392,6 +416,7 @@ def check_config(d, theme, data, context, echoerr):
return True, False, True return True, False, True
return True, False, False return True, False, False
divider_spec = Spec().type(unicode).len('le', 3, divider_spec = Spec().type(unicode).len('le', 3,
lambda value: 'Divider {0!r} is too large!'.format(value)).copy lambda value: 'Divider {0!r} is too large!'.format(value)).copy
divside_spec = Spec( divside_spec = Spec(
@ -427,7 +452,7 @@ main_spec = (Spec(
theme=theme_spec(), theme=theme_spec(),
local_themes=Spec() local_themes=Spec()
.unknown_spec(lambda *args: check_matcher_func('vim', *args), theme_spec()) .unknown_spec(lambda *args: check_matcher_func('vim', *args), theme_spec())
), ).optional(),
ipython=Spec( ipython=Spec(
colorscheme=colorscheme_spec(), colorscheme=colorscheme_spec(),
theme=theme_spec(), theme=theme_spec(),
@ -436,7 +461,7 @@ main_spec = (Spec(
out=theme_spec(), out=theme_spec(),
rewrite=theme_spec(), rewrite=theme_spec(),
), ),
), ).optional(),
).unknown_spec(check_ext, ).unknown_spec(check_ext,
Spec( Spec(
colorscheme=colorscheme_spec(), colorscheme=colorscheme_spec(),
@ -527,10 +552,11 @@ type_keys = {
} }
required_keys = { required_keys = {
'function': set(), 'function': set(),
'string': set(('contents', 'highlight_group')), 'string': set(('contents',)),
'filler': set(('highlight_group',)), 'filler': set(),
} }
function_keys = set(('args', 'module')) function_keys = set(('args', 'module'))
highlight_keys = set(('highlight_group', 'name'))
def check_key_compatibility(segment, data, context, echoerr): def check_key_compatibility(segment, data, context, echoerr):
@ -542,25 +568,33 @@ def check_key_compatibility(segment, data, context, echoerr):
problem_mark=segment_type.mark) problem_mark=segment_type.mark)
return False, False, True return False, False, True
hadproblem = False
keys = set(segment) keys = set(segment)
if not ((keys - generic_keys) < type_keys[segment_type]): if not ((keys - generic_keys) < type_keys[segment_type]):
unknown_keys = keys - generic_keys - type_keys[segment_type] unknown_keys = keys - generic_keys - type_keys[segment_type]
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
context_mark=context[-1][1].mark, context_mark=context[-1][1].mark,
problem='found keys not used with the current segment type: {0}'.format( problem='found keys not used with the current segment type: {0}'.format(
', '.join((unicode(key) for key in unknown_keys))), list_sep.join(unknown_keys)),
problem_mark=list(unknown_keys)[0].mark) problem_mark=list(unknown_keys)[0].mark)
return True, False, True hadproblem = True
if not (keys > required_keys[segment_type]): if not (keys > required_keys[segment_type]):
missing_keys = required_keys[segment_type] - keys missing_keys = required_keys[segment_type] - keys
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
context_mark=context[-1][1].mark, context_mark=context[-1][1].mark,
problem='found missing required keys: {0}'.format( problem='found missing required keys: {0}'.format(
', '.join((unicode(key) for key in missing_keys)))) list_sep.join(missing_keys)))
return True, False, True hadproblem = True
return True, False, False if not (segment_type == 'function' or (keys & highlight_keys)):
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
context_mark=context[-1][1].mark,
problem='found missing keys required to determine highlight group. Either highlight_group or name key must be present')
hadproblem = True
return True, False, hadproblem
def check_segment_module(module, data, context, echoerr): def check_segment_module(module, data, context, echoerr):
@ -610,28 +644,39 @@ def check_full_segment_data(segment, data, context, echoerr):
return check_key_compatibility(segment_copy, data, context, echoerr) return check_key_compatibility(segment_copy, data, context, echoerr)
def import_segment(name, data, context, echoerr, module=None):
if not module:
module = context[-2][1].get('module', context[0][1].get('default_module', 'powerline.segments.' + data['ext']))
with WithPath(data['import_paths']):
try:
func = getattr(__import__(unicode(module), fromlist=[unicode(name)]), unicode(name))
except ImportError:
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='failed to import module {0}'.format(module),
problem_mark=module.mark)
return None
except AttributeError:
echoerr(context='Error while loading segment function (key {key})'.format(key=context_key(context)),
problem='failed to load function {0} from module {1}'.format(name, module),
problem_mark=name.mark)
return None
if not callable(func):
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='imported "function" {0} from module {1} is not callable'.format(name, module),
problem_mark=module.mark)
return None
return func
def check_segment_name(name, data, context, echoerr): def check_segment_name(name, data, context, echoerr):
ext = data['ext'] ext = data['ext']
if context[-2][1].get('type', 'function') == 'function': if context[-2][1].get('type', 'function') == 'function':
module = context[-2][1].get('module', context[0][1].get('default_module', 'powerline.segments.' + ext)) func = import_segment(name, data, context, echoerr)
with WithPath(data['import_paths']):
try:
func = getattr(__import__(unicode(module), fromlist=[unicode(name)]), unicode(name))
except ImportError:
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='failed to import module {0}'.format(module),
problem_mark=module.mark)
return True, False, True
except AttributeError:
echoerr(context='Error while loading segment function (key {key})'.format(key=context_key(context)),
problem='failed to load function {0} from module {1}'.format(name, module),
problem_mark=name.mark)
return True, False, True
if not callable(func): if not func:
echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)),
problem='imported "function" {0} from module {1} is not callable'.format(name, module),
problem_mark=module.mark)
return True, False, True return True, False, True
hl_groups = [] hl_groups = []
@ -653,32 +698,32 @@ def check_segment_name(name, data, context, echoerr):
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight group {0} not defined in the following colorschemes: {1}\n(Group name was obtained from function documentation.)'.format( problem='found highlight group {0} not defined in the following colorschemes: {1}\n(Group name was obtained from function documentation.)'.format(
divider_hl_group, ', '.join(r)), divider_hl_group, list_sep.join(r)),
problem_mark=name.mark) problem_mark=name.mark)
hadproblem = True hadproblem = True
if hl_groups: if hl_groups:
greg = re.compile(r'``([^`]+)``( \(gradient\))?') greg = re.compile(r'``([^`]+)``( \(gradient\))?')
hl_groups = [[greg.match(subs).groups() for subs in s.split(' or ')] for s in (', '.join(hl_groups)).split(', ')] hl_groups = [[greg.match(subs).groups() for subs in s.split(' or ')] for s in (list_sep.join(hl_groups)).split(', ')]
for required_pack in hl_groups: for required_pack in hl_groups:
rs = [hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False)) rs = [hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False))
for hl_group, gradient in required_pack] for hl_group, gradient in required_pack]
if all(rs): if all(rs):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes\n(Group names were taken from function documentation.)'.format( problem='found highlight groups list ({0}) with all groups not defined in some colorschemes\n(Group names were taken from function documentation.)'.format(
', '.join((unicode(h[0]) for h in required_pack))), list_sep.join((h[0] for h in required_pack))),
problem_mark=name.mark) problem_mark=name.mark)
for r, h in zip(rs, required_pack): for r, h in zip(rs, required_pack):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format( problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
h[0], ', '.join(r))) h[0], list_sep.join(r)))
hadproblem = True hadproblem = True
else: else:
r = hl_exists(name, data, context, echoerr, allow_gradients=True) r = hl_exists(name, data, context, echoerr, allow_gradients=True)
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight group {0} not defined in the following colorschemes: {1}\n(If not specified otherwise in documentation, highlight group for function segments\nis the same as the function name.)'.format( problem='found highlight group {0} not defined in the following colorschemes: {1}\n(If not specified otherwise in documentation, highlight group for function segments\nis the same as the function name.)'.format(
name, ', '.join(r)), name, list_sep.join(r)),
problem_mark=name.mark) problem_mark=name.mark)
hadproblem = True hadproblem = True
@ -744,7 +789,7 @@ def check_highlight_group(hl_group, data, context, echoerr):
if r: if r:
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format( problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
hl_group, ', '.join(r)), hl_group, list_sep.join(r)),
problem_mark=hl_group.mark) problem_mark=hl_group.mark)
return True, False, True return True, False, True
return True, False, False return True, False, False
@ -755,12 +800,12 @@ def check_highlight_groups(hl_groups, data, context, echoerr):
if all(rs): if all(rs):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format( problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format(
', '.join((unicode(h) for h in hl_groups))), list_sep.join((unicode(h) for h in hl_groups))),
problem_mark=hl_groups.mark) problem_mark=hl_groups.mark)
for r, hl_group in zip(rs, hl_groups): for r, hl_group in zip(rs, hl_groups):
echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)), echoerr(context='Error while checking theme (key {key})'.format(key=context_key(context)),
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format( problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
hl_group, ', '.join(r)), hl_group, list_sep.join(r)),
problem_mark=hl_group.mark) problem_mark=hl_group.mark)
return True, False, True return True, False, True
return True, False, False return True, False, False
@ -798,11 +843,94 @@ def check_segment_data_key(key, data, context, echoerr):
return True, False, False return True, False, False
# FIXME More checks, limit existing to ThreadedSegment instances only threaded_args_specs = {
'interval': Spec().cmp('gt', 0.0),
'update_first': Spec().type(bool),
'shutdown_event': Spec().error('Shutdown event must be set by powerline'),
}
def check_args_variant(segment, args, data, context, echoerr):
argspec = getconfigargspec(segment)
present_args = set(args)
all_args = set(argspec.args)
required_args = set(argspec.args[:-len(argspec.defaults)])
hadproblem = False
if required_args - present_args:
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context_key(context)),
context_mark=args.mark,
problem='some of the required keys are missing: {0}'.format(list_sep.join(required_args - present_args)))
hadproblem = True
if not all_args >= present_args:
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context_key(context)),
context_mark=args.mark,
problem='found unknown keys: {0}'.format(list_sep.join(present_args - all_args)),
problem_mark=next(iter(present_args - all_args)).mark)
hadproblem = True
if isinstance(segment, ThreadedSegment):
for key in set(threaded_args_specs) & present_args:
proceed, khadproblem = threaded_args_specs[key].match(args[key], args.mark, data, context + ((key, args[key]),), echoerr)
if khadproblem:
hadproblem = True
if not proceed:
return hadproblem
return hadproblem
def check_args(get_segment_variants, args, data, context, echoerr):
new_echoerr = DelayedEchoErr(echoerr)
count = 0
hadproblem = False
for segment in get_segment_variants(data, context, new_echoerr):
count += 1
shadproblem = check_args_variant(segment, args, data, context, echoerr)
if shadproblem:
hadproblem = True
if not count:
hadproblem = True
if new_echoerr:
new_echoerr.echo_all()
else:
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context_key(context)),
context_mark=context[-2][1].mark,
problem='no suitable segments found')
return True, False, hadproblem
def get_one_segment_variant(data, context, echoerr):
name = context[-2][1].get('name')
if name:
func = import_segment(name, data, context, echoerr)
if func:
yield func
def get_all_possible_segments(data, context, echoerr):
name = context[-2][0]
module, name = name.rpartition('.')[::2]
if module:
func = import_segment(name, data, context, echoerr, module=module)
if func:
yield func
else:
for theme_config in data['ext_theme_configs'].values():
for segments in theme_config.get('segments', {}).values():
for segment in segments:
if segment.get('type', 'function') == 'function':
module = segment.get('module', context[0][1].get('default_module', 'powerline.segments.' + data['ext']))
func = import_segment(name, data, context, echoerr, module=module)
if func:
yield func
args_spec = Spec( args_spec = Spec(
interval=Spec().cmp('gt', 0.0).optional(),
update_first=Spec().type(bool).optional(),
shutdown_event=Spec().error('Shutdown event must be set by powerline').optional(),
pl=Spec().error('pl object must be set by powerline').optional(), pl=Spec().error('pl object must be set by powerline').optional(),
segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(), segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(),
).unknown_spec(Spec(), Spec()).optional().copy ).unknown_spec(Spec(), Spec()).optional().copy
@ -818,12 +946,12 @@ segments_spec = Spec().optional().list(
draw_soft_divider=Spec().type(bool).optional(), draw_soft_divider=Spec().type(bool).optional(),
draw_inner_divider=Spec().type(bool).optional(), draw_inner_divider=Spec().type(bool).optional(),
module=segment_module_spec(), module=segment_module_spec(),
priority=Spec().either(Spec().cmp('eq', -1), Spec().cmp('ge', 0.0)).optional(), priority=Spec().type(int, float, type(None)).optional(),
after=Spec().type(unicode).optional(), after=Spec().type(unicode).optional(),
before=Spec().type(unicode).optional(), before=Spec().type(unicode).optional(),
width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(), width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(),
align=Spec().oneof(set('lr')).optional(), align=Spec().oneof(set('lr')).optional(),
args=args_spec(), args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_variant, *args, **kwargs)),
contents=Spec().type(unicode).optional(), contents=Spec().type(unicode).optional(),
highlight_group=Spec().list( highlight_group=Spec().list(
highlight_group_spec().re('^(?:(?!:divider$).)+$', highlight_group_spec().re('^(?:(?!:divider$).)+$',
@ -840,7 +968,7 @@ theme_spec = (Spec(
Spec( Spec(
after=Spec().type(unicode).optional(), after=Spec().type(unicode).optional(),
before=Spec().type(unicode).optional(), before=Spec().type(unicode).optional(),
args=args_spec(), args=args_spec().func(lambda *args, **kwargs: check_args(get_all_possible_segments, *args, **kwargs)),
contents=Spec().type(unicode).optional(), contents=Spec().type(unicode).optional(),
), ),
).optional().context_message('Error while loading segment data (key {key})'), ).optional().context_message('Error while loading segment data (key {key})'),

59
powerline/lint/inspect.py Normal file
View File

@ -0,0 +1,59 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import
from inspect import ArgSpec, getargspec
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
from itertools import count
def getconfigargspec(obj):
if isinstance(obj, ThreadedSegment):
args = ['interval']
defaults = [getattr(obj, 'interval', 1)]
if obj.update_first:
args.append('update_first')
defaults.append(True)
methods = ['render', 'set_state']
if isinstance(obj, KwThreadedSegment):
methods += ['key', 'render_one']
for method in methods:
if hasattr(obj, method):
# Note: on <python-2.6 it may return simple tuple, not
# ArgSpec instance.
argspec = getargspec(getattr(obj, method))
for i, arg in zip(count(1), reversed(argspec.args)):
if (arg == 'self' or
(arg == 'segment_info' and
getattr(obj, 'powerline_requires_segment_info', None)) or
(arg == 'pl') or
(method.startswith('render') and (1 if argspec.args[0] == 'self' else 0) + i == len(argspec.args)) or
arg in args):
continue
if argspec.defaults and len(argspec.defaults) >= i:
default = argspec.defaults[-i]
defaults.append(default)
args.append(arg)
else:
args.insert(0, arg)
argspec = ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults))
else:
if hasattr(obj, 'powerline_origin'):
obj = obj.powerline_origin
else:
obj = obj
argspec = getargspec(obj)
args = []
defaults = []
for i, arg in zip(count(1), reversed(argspec.args)):
if ((arg == 'segment_info' and getattr(obj, 'powerline_requires_segment_info', None)) or
arg == 'pl'):
continue
if argspec.defaults and len(argspec.defaults) >= i:
default = argspec.defaults[-i]
defaults.append(default)
args.append(arg)
else:
args.insert(0, arg)
argspec = ArgSpec(args=args, varargs=argspec.varargs, keywords=argspec.keywords, defaults=tuple(defaults))
return argspec

View File

@ -29,6 +29,9 @@ class Mark:
self.buffer = buffer self.buffer = buffer
self.pointer = pointer self.pointer = pointer
def copy(self):
return Mark(self.name, self.line, self.column, self.buffer, self.pointer)
def get_snippet(self, indent=4, max_length=75): def get_snippet(self, indent=4, max_length=75):
if self.buffer is None: if self.buffer is None:
return None return None

View File

@ -1,17 +1,71 @@
__all__ = ['gen_marked_value', 'MarkedValue'] __all__ = ['gen_marked_value', 'MarkedValue']
try:
from __builtin__ import unicode
except ImportError:
unicode = str
def gen_new(cls):
def __new__(arg_cls, value, mark):
r = super(arg_cls, arg_cls).__new__(arg_cls, value)
r.mark = mark
r.value = value
return r
return __new__
class MarkedUnicode(unicode):
__new__ = gen_new(unicode)
def _proc_partition(self, part_result):
pointdiff = 1
r = []
for s in part_result:
mark = self.mark.copy()
# XXX Does not work properly with escaped strings, but this requires
# saving much more information in mark.
mark.column += pointdiff
mark.pointer += pointdiff
r.append(MarkedUnicode(s, mark))
pointdiff += len(s)
return tuple(r)
def rpartition(self, sep):
return self._proc_partition(super(MarkedUnicode, self).rpartition(sep))
def partition(self, sep):
return self._proc_partition(super(MarkedUnicode, self).partition(sep))
class MarkedInt(int):
__new__ = gen_new(int)
class MarkedFloat(float):
__new__ = gen_new(float)
class MarkedValue: class MarkedValue:
def __init__(self, value, mark): def __init__(self, value, mark):
self.mark = mark self.mark = mark
self.value = value self.value = value
specialclasses = {
unicode: MarkedUnicode,
int: MarkedInt,
float: MarkedFloat,
}
classcache = {} classcache = {}
def gen_marked_value(value, mark): def gen_marked_value(value, mark, use_special_classes=True):
if value.__class__ in classcache: if use_special_classes and value.__class__ in specialclasses:
Marked = specialclasses[value.__class__]
elif value.__class__ in classcache:
Marked = classcache[value.__class__] Marked = classcache[value.__class__]
else: else:
class Marked(MarkedValue): class Marked(MarkedValue):

View File

@ -19,11 +19,51 @@ def construct_returned_value(rendered_highlighted, segments, output_raw):
class Renderer(object): class Renderer(object):
'''Object that is responsible for generating the highlighted string.
:param dict theme_config:
Main theme configuration.
:param local_themes:
Local themes. Is to be used by subclasses from ``.get_theme()`` method,
base class only records this parameter to a ``.local_themes`` attribute.
:param dict theme_kwargs:
Keyword arguments for ``Theme`` class constructor.
:param Colorscheme colorscheme:
Colorscheme object that holds colors configuration.
:param PowerlineLogger pl:
Object used for logging.
:param int ambiwidth:
Width of the characters with east asian width unicode attribute equal to
``A`` (Ambigious).
:param dict options:
Various options. Are normally not used by base renderer, but all options
are recorded as attributes.
'''
segment_info = { segment_info = {
'environ': os.environ, 'environ': os.environ,
'getcwd': getattr(os, 'getcwdu', os.getcwd), 'getcwd': getattr(os, 'getcwdu', os.getcwd),
'home': os.environ.get('HOME'), 'home': os.environ.get('HOME'),
} }
'''Basic segment info. Is merged with local segment information by
``.get_segment_info()`` method. Keys:
``environ``
Object containing environment variables. Must define at least the
following methods: ``.__getitem__(var)`` that raises ``KeyError`` in
case requested environment variable is not present, ``.get(var,
default=None)`` that works like ``dict.get`` and be able to be passed to
``Popen``.
``getcwd``
Function that returns current working directory. Will be called without
any arguments, should return ``unicode`` or (in python-2) regular
string.
``home``
String containing path to home directory. Should be ``unicode`` or (in
python-2) regular string or ``None``.
'''
def __init__(self, def __init__(self,
theme_config, theme_config,
@ -31,6 +71,7 @@ class Renderer(object):
theme_kwargs, theme_kwargs,
colorscheme, colorscheme,
pl, pl,
ambiwidth=1,
**options): **options):
self.__dict__.update(options) self.__dict__.update(options)
self.theme_config = theme_config self.theme_config = theme_config
@ -41,24 +82,48 @@ class Renderer(object):
self.theme_kwargs = theme_kwargs self.theme_kwargs = theme_kwargs
self.colorscheme = colorscheme self.colorscheme = colorscheme
self.width_data = { self.width_data = {
'N': 1, # Neutral 'N': 1, # Neutral
'Na': 1, # Narrow 'Na': 1, # Narrow
'A': getattr(self, 'ambiwidth', 1), # Ambigious 'A': ambiwidth, # Ambigious
'H': 1, # Half-width 'H': 1, # Half-width
'W': 2, # Wide 'W': 2, # Wide
'F': 2, # Fullwidth 'F': 2, # Fullwidth
} }
def strwidth(self, string): def strwidth(self, string):
'''Function that returns string width.
Is used to calculate the place given string occupies when handling
``width`` argument to ``.render()`` method. Must take east asian width
into account.
:param unicode string:
String whose width will be calculated.
:return: unsigned integer.
'''
return sum((0 if combining(symbol) else self.width_data[east_asian_width(symbol)] for symbol in string)) return sum((0 if combining(symbol) else self.width_data[east_asian_width(symbol)] for symbol in string))
def get_theme(self, matcher_info): def get_theme(self, matcher_info):
'''Get Theme object.
Is to be overridden by subclasses to support local themes, this variant
only returns ``.theme`` attribute.
:param matcher_info:
Parameter ``matcher_info`` that ``.render()`` method received.
Unused.
'''
return self.theme return self.theme
def shutdown(self): def shutdown(self):
'''Prepare for interpreter shutdown. The only job it is supposed to do
is calling ``.shutdown()`` method for all theme objects. Should be
overridden by subclasses in case they support local themes.
'''
self.theme.shutdown() self.theme.shutdown()
def get_highlighting(self, segment, mode): def _get_highlighting(self, segment, mode):
segment['highlight'] = self.colorscheme.get_highlighting(segment['highlight_group'], mode, segment.get('gradient_level')) segment['highlight'] = self.colorscheme.get_highlighting(segment['highlight_group'], mode, segment.get('gradient_level'))
if segment['divider_highlight_group']: if segment['divider_highlight_group']:
segment['divider_highlight'] = self.colorscheme.get_highlighting(segment['divider_highlight_group'], mode) segment['divider_highlight'] = self.colorscheme.get_highlighting(segment['divider_highlight_group'], mode)
@ -67,6 +132,21 @@ class Renderer(object):
return segment return segment
def get_segment_info(self, segment_info): def get_segment_info(self, segment_info):
'''Get segment information.
Must return a dictionary containing at least ``home``, ``environ`` and
``getcwd`` keys (see documentation for ``segment_info`` attribute). This
implementation merges ``segment_info`` dictionary passed to
``.render()`` method with ``.segment_info`` attribute, preferring keys
from the former. It also replaces ``getcwd`` key with function returning
``segment_info['environ']['PWD']`` in case ``PWD`` variable is
available.
:param dict segment_info:
Segment information that was passed to ``.render()`` method.
:return: dict with segment information.
'''
r = self.segment_info.copy() r = self.segment_info.copy()
if segment_info: if segment_info:
r.update(segment_info) r.update(segment_info)
@ -82,12 +162,30 @@ class Renderer(object):
with a negative priority are left. If one or more filler segments are with a negative priority are left. If one or more filler segments are
provided they will fill the remaining space until the desired width is provided they will fill the remaining space until the desired width is
reached. reached.
:param str mode:
Mode string. Affects contents (colors and the set of segments) of
rendered string.
:param int width:
Maximum width text can occupy. May be exceeded if there are too much
non-removable segments.
:param str side:
One of ``left``, ``right``. Determines which side will be rendered.
If not present all sides are rendered.
:param bool output_raw:
Changes the output: if this parameter is ``True`` then in place of
one string this method outputs a pair ``(colored_string,
colorless_string)``.
:param dict segment_info:
Segment information. See also ``.get_segment_info()`` method.
:param matcher_info:
Matcher information. Is processed in ``.get_theme()`` method.
''' '''
theme = self.get_theme(matcher_info) theme = self.get_theme(matcher_info)
segments = theme.get_segments(side, self.get_segment_info(segment_info)) segments = theme.get_segments(side, self.get_segment_info(segment_info))
# Handle excluded/included segments for the current mode # Handle excluded/included segments for the current mode
segments = [self.get_highlighting(segment, mode) for segment in segments segments = [self._get_highlighting(segment, mode) for segment in segments
if mode not in segment['exclude_modes'] or (segment['include_modes'] and segment in segment['include_modes'])] if mode not in segment['exclude_modes'] or (segment['include_modes'] and segment in segment['include_modes'])]
segments = [segment for segment in self._render_segments(theme, segments)] segments = [segment for segment in self._render_segments(theme, segments)]
@ -97,7 +195,7 @@ class Renderer(object):
return construct_returned_value(''.join([segment['_rendered_hl'] for segment in segments]) + self.hlstyle(), segments, output_raw) return construct_returned_value(''.join([segment['_rendered_hl'] for segment in segments]) + self.hlstyle(), segments, output_raw)
# Create an ordered list of segments that can be dropped # Create an ordered list of segments that can be dropped
segments_priority = [segment for segment in sorted(segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0] segments_priority = sorted((segment for segment in segments if segment['priority'] is not None), key=lambda segment: segment['priority'], reverse=True)
while sum([segment['_len'] for segment in segments]) > width and len(segments_priority): while sum([segment['_len'] for segment in segments]) > width and len(segments_priority):
segments.remove(segments_priority[0]) segments.remove(segments_priority[0])
segments_priority.pop(0) segments_priority.pop(0)
@ -199,10 +297,23 @@ class Renderer(object):
@staticmethod @staticmethod
def escape(string): def escape(string):
'''Method that escapes segment contents.
'''
return string return string
def hlstyle(fg=None, bg=None, attr=None): def hlstyle(fg=None, bg=None, attr=None):
'''Output highlight style string.
Assuming highlighted string looks like ``{style}{contents}`` this method
should output ``{style}``. If it is called without arguments this method
is supposed to reset style to its default.
'''
raise NotImplementedError raise NotImplementedError
def hl(self, contents, fg=None, bg=None, attr=None): def hl(self, contents, fg=None, bg=None, attr=None):
'''Output highlighted chunk.
This implementation just outputs ``.hlstyle()`` joined with
``contents``.
'''
return self.hlstyle(fg, bg, attr) + (contents or '') return self.hlstyle(fg, bg, attr) + (contents or '')

View File

@ -39,17 +39,17 @@ def get_filler(data, segment):
segment_getters = { segment_getters = {
"function": get_function, "function": get_function,
"string": get_string, "string": get_string,
"filler": get_filler, "filler": get_filler,
} }
def gen_segment_getter(ext, path, theme_configs, default_module=None): def gen_segment_getter(ext, path, theme_configs, default_module=None):
data = { data = {
'default_module': default_module or 'powerline.segments.' + ext, 'default_module': default_module or 'powerline.segments.' + ext,
'path': path, 'path': path,
} }
def get_key(segment, module, key, default=None): def get_key(segment, module, key, default=None):
return get_segment_key(segment, theme_configs, key, module, default) return get_segment_key(segment, theme_configs, key, module, default)
@ -61,8 +61,18 @@ def gen_segment_getter(ext, path, theme_configs, default_module=None):
get_segment_info = segment_getters[segment_type] get_segment_info = segment_getters[segment_type]
except KeyError: except KeyError:
raise TypeError('Unknown segment type: {0}'.format(segment_type)) raise TypeError('Unknown segment type: {0}'.format(segment_type))
contents, contents_func, module = get_segment_info(data, segment)
highlight_group = segment_type != 'function' and segment.get('highlight_group') or segment.get('name') try:
contents, contents_func, module = get_segment_info(data, segment)
except Exception as e:
pl.exception('Failed to generate segment from {0!r}: {1}', segment, str(e), prefix='segment_generator')
return None
if segment_type == 'function':
highlight_group = [module + '.' + segment['name'], segment['name']]
else:
highlight_group = segment.get('highlight_group') or segment.get('name')
return { return {
'name': segment.get('name'), 'name': segment.get('name'),
'type': segment_type, 'type': segment_type,
@ -73,7 +83,7 @@ def gen_segment_getter(ext, path, theme_configs, default_module=None):
'contents_func': contents_func, 'contents_func': contents_func,
'contents': contents, 'contents': contents,
'args': get_key(segment, module, 'args', {}) if segment_type == 'function' else {}, 'args': get_key(segment, module, 'args', {}) if segment_type == 'function' else {},
'priority': segment.get('priority', -1), 'priority': segment.get('priority', None),
'draw_hard_divider': segment.get('draw_hard_divider', True), 'draw_hard_divider': segment.get('draw_hard_divider', True),
'draw_soft_divider': segment.get('draw_soft_divider', True), 'draw_soft_divider': segment.get('draw_soft_divider', True),
'draw_inner_divider': segment.get('draw_inner_divider', False), 'draw_inner_divider': segment.get('draw_inner_divider', False),
@ -89,6 +99,6 @@ def gen_segment_getter(ext, path, theme_configs, default_module=None):
'_len': 0, '_len': 0,
'_space_left': 0, '_space_left': 0,
'_space_right': 0, '_space_right': 0,
} }
return get return get

View File

@ -274,6 +274,7 @@ def col_current(pl, segment_info):
return str(segment_info['window'].cursor[1] + 1) return str(segment_info['window'].cursor[1] + 1)
# TODO Add &textwidth-based gradient
@window_cached @window_cached
def virtcol_current(pl): def virtcol_current(pl):
'''Return current visual column with concealed characters ingored '''Return current visual column with concealed characters ingored

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # vim:fileencoding=utf-8:noet
'''Powerline prompt and statusline script.''' '''Powerline prompt and statusline script.'''
import sys import sys
import os import os

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # vim:fileencoding=utf-8:noet
'''Powerline configuration checker.''' '''Powerline configuration checker.'''
import argparse import argparse
from powerline.lint import check from powerline.lint import check

View File

@ -3,7 +3,7 @@
FAILED=0 FAILED=0
export PYTHONPATH="${PYTHONPATH}:`realpath .`" export PYTHONPATH="${PYTHONPATH}:`realpath .`"
for file in tests/test_*.py ; do for file in tests/test_*.py ; do
if ! ${PYTHON} $file ; then if ! ${PYTHON} $file --verbose --catch ; then
FAILED=1 FAILED=1
fi fi
done done

View File

@ -316,13 +316,13 @@ class _Buffer(object):
self.name = os.path.abspath(name) if name else None self.name = os.path.abspath(name) if name else None
_buf_scopes[bufnr] = {} _buf_scopes[bufnr] = {}
_buf_options[bufnr] = { _buf_options[bufnr] = {
'modified': 0, 'modified': 0,
'readonly': 0, 'readonly': 0,
'fileformat': 'unix', 'fileformat': 'unix',
'filetype': '', 'filetype': '',
'buftype': '', 'buftype': '',
'fileencoding': 'utf-8', 'fileencoding': 'utf-8',
} }
_buf_lines[bufnr] = [''] _buf_lines[bufnr] = ['']
from copy import copy from copy import copy
_undostate[bufnr] = [copy(_buf_lines[bufnr])] _undostate[bufnr] = [copy(_buf_lines[bufnr])]
@ -393,9 +393,9 @@ def _init():
@_vim @_vim
def _get_segment_info(): def _get_segment_info():
mode_translations = { mode_translations = {
chr(ord('V') - 0x40): '^V', chr(ord('V') - 0x40): '^V',
chr(ord('S') - 0x40): '^S', chr(ord('S') - 0x40): '^S',
} }
mode = _mode mode = _mode
mode = mode_translations.get(mode, mode) mode = mode_translations.get(mode, mode)
return { return {