Release 2.4

- Added `short` parameter for `system_load` segment that leaves only one load
  average number out of three.
- Added `powerline.segments.i3wm.scratchpad` segment used to list windows that
  are currently on the scratchpad.
- Added support for multiple batteries in battery segment.
- Added `….i3wm.workspace` segment which describes single i3wm workspace and
  workspaces lister. Old `….i3wm.workspaces` segment was deprecated.
- Added support for multiple monitors in lemonbar bindings.
- Added support for most recent tmux version (2.2).
- Fixed battery status support on some linux systems.
- Fixed MPD bindings: they sometimes were not able to handle names if they did
  not fit ASCII.
- Fixed MPD bindings: they did not correctly get elapsed time.
- Fixed AttributeError on some systems: LC_MESSAGES is not always available.
- Fixed Mac OS-specific variant of spotify player support when Python-3 is
  used.
- Fixed performance of the tabline.
This commit is contained in:
Foo 2016-04-19 02:02:47 +03:00
commit d77cc30a3b
65 changed files with 2155 additions and 316 deletions

View File

@ -24,12 +24,9 @@ matrix:
- python: "3.2" - python: "3.2"
- python: "3.3" - python: "3.3"
- python: "3.4" - python: "3.4"
- python: "3.5"
- python: "pypy" - python: "pypy"
- python: "pypy3" - python: "pypy3"
- python: "2.6"
env: >-
USE_UCS2_PYTHON=1
UCS2_PYTHON_VARIANT="2.6"
- python: "2.7" - python: "2.7"
env: >- env: >-
USE_UCS2_PYTHON=1 USE_UCS2_PYTHON=1

View File

@ -1,6 +1,7 @@
recursive-include powerline *.json *.vim recursive-include powerline *.json *.vim
recursive-include powerline/bindings *.* recursive-include powerline/bindings *.*
recursive-exclude powerline/bindings *.pyc *.pyo recursive-exclude powerline/bindings *.pyc *.pyo
recursive-include powerline/dist *.*
recursive-include client *.* recursive-include client *.*
recursive-include docs/source *.rst *.py recursive-include docs/source *.rst *.py
include docs/Makefile include docs/Makefile

View File

@ -16,8 +16,6 @@ their type and ``segments`` key with a list of segments (a bit more details in
More information in :ref:`Writing listers <dev-listers>` section. More information in :ref:`Writing listers <dev-listers>` section.
Currently only Vim listers are available.
Vim listers Vim listers
----------- -----------
@ -29,3 +27,9 @@ Pdb listers
.. automodule:: powerline.listers.pdb .. automodule:: powerline.listers.pdb
:members: :members:
i3wm listers
----------
.. automodule:: powerline.listers.i3wm
:members:

View File

@ -12,6 +12,8 @@ Vim overrides
Vim configuration can be overridden using the following options: Vim configuration can be overridden using the following options:
.. _local-configuration-overrides-vim-config:
``g:powerline_config_overrides`` ``g:powerline_config_overrides``
Dictionary, recursively merged with contents of Dictionary, recursively merged with contents of
:file:`powerline/config.json`. :file:`powerline/config.json`.
@ -37,6 +39,17 @@ Vim configuration can be overridden using the following options:
was configured in :ref:`log_* options <config-common-log>`. Level is always was configured in :ref:`log_* options <config-common-log>`. Level is always
:ref:`log_level <config-common-log_level>`, same for format. :ref:`log_level <config-common-log_level>`, same for format.
.. warning::
This variable is deprecated. Use :ref:`log_file option
<config-common-log>` in conjunction with
:py:class:`powerline.vim.VimVarHandler` class and :ref:`Vim config
overrides variable <local-configuration-overrides-vim-config>`. Using
this is also the only variant to make saving into the environment
variable the *only* place where log is saved or save into different
variable.
.. autoclass:: powerline.vim.VimVarHandler
.. _local-configuration-overrides-script: .. _local-configuration-overrides-script:
Powerline script overrides Powerline script overrides

View File

@ -94,14 +94,38 @@ Common configuration is a subdictionary that is a value of ``common`` key in
.. _config-common-log: .. _config-common-log:
``log_file`` ``log_file``
Defines path which will hold powerline logs. If not present, logging will be Defines how logs will be handled. There are three variants here:
done to stderr.
#. Absent. In this case logging will be done to stderr: equivalent to
``[["logging.StreamHandler", []]]`` or ``[null]``.
#. Plain string. In this case logging will be done to the given file:
``"/file/name"`` is equivalent to ``[["logging.FileHandler",
[["/file/name"]]]]`` or ``["/file/name"]``. Leading ``~/`` is expanded in
the file name, so using ``"~/.log/foo"`` is permitted. If directory
pointed by the option is absent, it will be created, but not its parent.
#. List of handler definitions. Handler definition may either be ``null``,
a string or a list with two or three elements:
#. Logging class name and module. If module name is absent, it is
equivalent to ``logging.handlers``.
#. Class constructor arguments in a form ``[[args[, kwargs]]]``: accepted
variants are ``[]`` (no arguments), ``[args]`` (e.g.
``[["/file/name"]]``: only positional arguments) or ``[args, kwargs]``
(e.g. ``[[], {"host": "localhost", "port": 6666}]``: positional and
keyword arguments, but no positional arguments in the example).
#. Optional logging level. Overrides :ref:`log_level key
<config-common-log_level>` and has the same format.
#. Optional format string. Partially overrides :ref:`log_format key
<config-common-log_format>` and has the same format. “Partially” here
means that it may only specify more critical level.
.. _config-common-log_level: .. _config-common-log_level:
``log_level`` ``log_level``
String, determines logging level. Defaults to ``WARNING``. String, determines logging level. Defaults to ``WARNING``.
.. _config-common-log_format:
``log_format`` ``log_format``
String, determines format of the log messages. Defaults to String, determines format of the log messages. Defaults to
``'%(asctime)s:%(level)s:%(message)s'``. ``'%(asctime)s:%(level)s:%(message)s'``.
@ -163,6 +187,10 @@ Common configuration is a subdictionary that is a value of ``ext`` key in
``out`` and ``rewrite`` prompts (refer to IPython documentation for more ``out`` and ``rewrite`` prompts (refer to IPython documentation for more
details) while ``in`` prompt is the default. details) while ``in`` prompt is the default.
For wm (:ref:`lemonbar <lemonbar-usage>` only) it is a dictionary
``{output : theme_name}`` that maps the ``xrandr`` output names to the
local themes to use on that output.
``components`` ``components``
Determines which extension components should be enabled. This key is highly Determines which extension components should be enabled. This key is highly
extension-specific, here is the table of extensions and corresponding extension-specific, here is the table of extensions and corresponding

View File

@ -305,7 +305,7 @@ Segment dictionary contains the following keys:
``side`` ``side``
Segment side: ``right`` or ``left``. Segment side: ``right`` or ``left``.
``display_condition``` ``display_condition``
Contains function that takes three position parameters: Contains function that takes three position parameters:
:py:class:`powerline.PowerlineLogger` instance, :ref:`segment_info :py:class:`powerline.PowerlineLogger` instance, :ref:`segment_info
<dev-segments-info>` dictionary and current mode and returns either ``True`` <dev-segments-info>` dictionary and current mode and returns either ``True``
@ -517,6 +517,22 @@ Pdb
Equal to the length of :py:attr:`pdb.Pdb.stack` at the first invocation of Equal to the length of :py:attr:`pdb.Pdb.stack` at the first invocation of
the prompt decremented by one. the prompt decremented by one.
i3wm
----
``mode``
Currently active i3 mode (as a string).
``output``
``xrandr`` output name currently drawing to. Currently only available
in lemonbar bindings.
``workspace``
dictionary containing the workspace name under the key ``"name"`` and
boolean values for the ``"visible"``, ``"urgent"`` and ``"focused"``
keys, indicating the state of the workspace. Currently only provided by
the :py:func:`powerline.listers.i3wm.workspace_lister` lister.
Segment class Segment class
============= =============

View File

@ -30,8 +30,9 @@ Generic requirements
with bazaar repositories. with bazaar repositories.
* ``pyuv`` python package. Required for :ref:`libuv-based watcher * ``pyuv`` python package. Required for :ref:`libuv-based watcher
<config-common-watcher>` to work. <config-common-watcher>` to work.
* ``i3-py``, `available on github <https://github.com/ziberna/i3-py>`_. Required * ``i3-ipc`` python package. Required for i3wm bindings and segments.
for i3wm bindings and segments. * ``xrandr`` program. Required for the multi-monitor lemonbar binding and the
:py:func:`powerline.listers.i3wm.output_lister`.
.. note:: .. note::
Until mercurial and bazaar support Python-3 or PyPy powerline will not Until mercurial and bazaar support Python-3 or PyPy powerline will not

View File

@ -11,12 +11,10 @@ Python package
sudo port select python python27-apple sudo port select python python27-apple
. Homebrew may be used here:: Homebrew may be used here::
brew install python brew install python
.
.. note:: .. note::
In case :file:`powerline.sh` as a client ``socat`` and ``coreutils`` need In case :file:`powerline.sh` as a client ``socat`` and ``coreutils`` need
to be installed. ``coreutils`` may be installed using ``brew install to be installed. ``coreutils`` may be installed using ``brew install
@ -45,7 +43,7 @@ Python package
``powerline-status`` in PyPI. ``powerline-status`` in PyPI.
.. note:: .. note::
Powerline developers should be aware that``pip install --editable`` does Powerline developers should be aware that ``pip install --editable`` does
not currently fully work. Installation performed this way are missing not currently fully work. Installation performed this way are missing
``powerline`` executable that needs to be symlinked. It will be located in ``powerline`` executable that needs to be symlinked. It will be located in
``scripts/powerline``. ``scripts/powerline``.
@ -56,13 +54,13 @@ Vim installation
Any terminal vim version with Python 3.2+ or Python 2.6+ support should work, Any terminal vim version with Python 3.2+ or Python 2.6+ support should work,
but MacVim users need to install it using the following command:: but MacVim users need to install it using the following command::
brew install macvim --env-std --override-system-vim brew install macvim --env-std --with-override-system-vim
Fonts installation Fonts installation
================== ==================
Install downloaded patched font by double-clicking the font file in Finder, then To install patched font double-click the font file in Finder, then click
clicking :guilabel:`Install this font` in the preview window. :guilabel:`Install this font` in the preview window.
After installing the patched font MacVim or terminal emulator (whatever After installing the patched font MacVim or terminal emulator (whatever
application powerline should work with) need to be configured to use the patched application powerline should work with) need to be configured to use the patched

View File

@ -6,6 +6,7 @@ import re
import codecs import codecs
from collections import namedtuple from collections import namedtuple
from argparse import REMAINDER
from functools import reduce from functools import reduce
@ -61,9 +62,9 @@ def parse_argument(*args, **kwargs):
is_option = args[0].startswith('-') is_option = args[0].startswith('-')
is_long_option = args[0].startswith('--') is_long_option = args[0].startswith('--')
is_short_option = is_option and not is_long_option is_short_option = is_option and not is_long_option
action = kwargs.get('action', 'store_true') action = kwargs.get('action', 'store')
multi = kwargs.get('action') in ('append',) multi = kwargs.get('action') in ('append',) or kwargs.get('nargs') is REMAINDER
nargs = kwargs.get('nargs') or (1 if kwargs.get('metavar') or action in ('append',) else 0) nargs = kwargs.get('nargs', (1 if action in ('append', 'store') else 0))
return Argument( return Argument(
names=args, names=args,
help=u(kwargs.get('help', '')), help=u(kwargs.get('help', '')),
@ -165,9 +166,20 @@ def format_usage_arguments(arguments, base_length=None):
# `--` is automatically transformed into &#8211; (EN DASH) # `--` is automatically transformed into &#8211; (EN DASH)
# when parsing into HTML. We do not need this. # when parsing into HTML. We do not need this.
line[-1] += [nodes.Text(char) for char in name] line[-1] += [nodes.Text(char) for char in name]
elif argument.nargs is REMAINDER:
line.append(nodes.Text('['))
line.append(nodes.strong())
line[-1] += [nodes.Text(char) for char in '--']
line.append(nodes.Text('] '))
if argument.nargs: if argument.nargs:
assert(argument.nargs in (1, '?')) assert(argument.nargs in (1, '?', REMAINDER))
with SurroundWith(line, argument.nargs == '?' and argument.is_option): with SurroundWith(
line, (
True
if argument.nargs is REMAINDER
else (argument.nargs == '?' and argument.is_option)
)
):
if argument.is_long_option: if argument.is_long_option:
line.append(nodes.Text('=')) line.append(nodes.Text('='))
line.append(nodes.emphasis(text=argument.metavar)) line.append(nodes.emphasis(text=argument.metavar))
@ -337,15 +349,21 @@ class AutoManParser(object):
class AutoMan(Directive): class AutoMan(Directive):
required_arguments = 1 required_arguments = 1
optional_arguments = 0 optional_arguments = 0
option_spec = dict(prog=unchanged_required) option_spec = dict(prog=unchanged_required, minimal=bool)
has_content = False has_content = False
def run(self): def run(self):
minimal = self.options.get('minimal')
module = self.arguments[0] module = self.arguments[0]
template_args = {} template_args = {}
template_args.update(get_authors()) template_args.update(get_authors())
get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser
parser = get_argparser(AutoManParser) parser = get_argparser(AutoManParser)
if minimal:
container = nodes.container()
container += parser.automan_usage(self.options['prog'])
container += parser.automan_description()
return [container]
synopsis_section = nodes.section( synopsis_section = nodes.section(
'', '',
nodes.title(text='Synopsis'), nodes.title(text='Synopsis'),

View File

@ -7,7 +7,8 @@ I cant see any fancy symbols, whats wrong?
* If youre using iTerm2, please update to `this revision * If youre using iTerm2, please update to `this revision
<https://github.com/gnachman/iTerm2/commit/8e3ad6dabf83c60b8cf4a3e3327c596401744af6>`_ <https://github.com/gnachman/iTerm2/commit/8e3ad6dabf83c60b8cf4a3e3327c596401744af6>`_
or newer. or newer. Also make sure that Preferences>Profiles>Text>Non-ASCII Font is the same as
your main Font.
* You need to set your ``LANG`` and ``LC_*`` environment variables to * You need to set your ``LANG`` and ``LC_*`` environment variables to
a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distros a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distros
documentation for information about setting these variables correctly. documentation for information about setting these variables correctly.

View File

@ -89,8 +89,8 @@ root <repository-root>`)::
.. note:: .. note::
The availability of the ``powerline-config`` command is required for The availability of the ``powerline-config`` command is required for
powerline support. DLlocation of this script may be specified via powerline support. The location of this script may be specified via
``$POWERLINE_CONFIG_COMMAND`` environment variable. the ``$POWERLINE_CONFIG_COMMAND`` environment variable.
.. note:: .. note::
It is advised to run ``powerline-daemon`` before adding the above line to It is advised to run ``powerline-daemon`` before adding the above line to
@ -114,11 +114,12 @@ For IPython<0.11 add the following lines to :file:`.ipython/ipy_user_conf.py`:
# create skeleton ipy_user_conf.py file): # create skeleton ipy_user_conf.py file):
powerline_setup() powerline_setup()
For IPython>=0.11 add the following line to :file:`ipython_config.py` file in For IPython>=0.11 add the following line to
the used profile: :file:`~/.ipython/profile_default/ipython_config.py` file in the used profile:
.. code-block:: Python .. code-block:: Python
c = get_config()
c.InteractiveShellApp.extensions = [ c.InteractiveShellApp.extensions = [
'powerline.bindings.ipython.post_0_11' 'powerline.bindings.ipython.post_0_11'
] ]

View File

@ -50,26 +50,35 @@ Add the following to :file:`~/.config/qtile/config.py`:
), ),
] ]
.. _bar-usage: .. _lemonbar-usage:
bar-aint-recursive lemonbar (formerly bar-aint-recursive)
================== ======================================
To run the bar simply pipe the output of the binding script into ``bar`` and To run the bar simply start the binding script:
specify appropriate options, for example like this::
python /path/to/powerline/bindings/bar/powerline-bar.py | bar python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py
to run with i3, simply ``exec`` this in i3 config file:: You can specify options to be passed to ``lemonbar`` after ``--``, like so:
exec python /path/to/powerline/bindings/bar/powerline-bar.py --i3 | bar python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --height 16 -- -f "Source Code Pro for Powerline-9"
to run with i3, simply ``exec`` this in the i3 config file and set the ``--i3`` switch:
exec python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --i3
Running the binding in i3-mode will require `i3ipc <https://github.com/acrisci/i3ipc-python>`_ Running the binding in i3-mode will require `i3ipc <https://github.com/acrisci/i3ipc-python>`_
(or the outdated `i3-py <https://github.com/ziberna/i3-py>`_). (or the outdated `i3-py <https://github.com/ziberna/i3-py>`_).
See the `bar documentation <https://github.com/LemonBoy/bar>`_ for more See the `lemonbar documentation <https://github.com/LemonBoy/bar>`_ for more
information and options. information and options.
All ``powerline-lemonbar.py`` arguments:
.. automan:: powerline.commands.lemonbar
:prog: powerline-lemonbar.py
:minimal: true
I3 bar I3 bar
====== ======

View File

@ -9,7 +9,7 @@ from threading import Lock, Event
from powerline.colorscheme import Colorscheme from powerline.colorscheme import Colorscheme
from powerline.lib.config import ConfigLoader from powerline.lib.config import ConfigLoader
from powerline.lib.unicode import safe_unicode, FailedUnicode from powerline.lib.unicode import unicode, safe_unicode, FailedUnicode
from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR
from powerline.lib.dict import mergedicts from powerline.lib.dict import mergedicts
from powerline.lib.encoding import get_preferred_output_encoding from powerline.lib.encoding import get_preferred_output_encoding
@ -121,7 +121,7 @@ def get_fallback_logger(stream=None):
handler.setLevel(level) handler.setLevel(level)
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger = logging.getLogger('powerline') logger = logging.Logger('powerline')
logger.setLevel(level) logger.setLevel(level)
logger.addHandler(handler) logger.addHandler(handler)
_fallback_logger = PowerlineLogger(None, logger, '_fallback_') _fallback_logger = PowerlineLogger(None, logger, '_fallback_')
@ -200,40 +200,102 @@ def load_config(cfg_path, find_config_files, config_loader, loader_callback=None
return ret return ret
def _get_log_handler(common_config, stream=None): def _set_log_handlers(common_config, logger, get_module_attr, stream=None):
'''Get log handler. '''Set log handlers
:param dict common_config: :param dict common_config:
Configuration dictionary used to create handler. Configuration dictionary used to create handler.
:param logging.Logger logger:
:return: logging.Handler subclass. Logger to which handlers will be attached.
:param func get_module_attr:
:py:func:`gen_module_attr_getter` output.
:param file stream:
Stream to use by default for :py:class:`logging.StreamHandler` in place
of :py:attr:`sys.stderr`. May be ``None``.
''' '''
log_file = common_config['log_file'] log_targets = common_config['log_file']
if log_file: num_handlers = 0
log_file = os.path.expanduser(log_file) for log_target in log_targets:
log_dir = os.path.dirname(log_file) if log_target is None:
if not os.path.isdir(log_dir): log_target = ['logging.StreamHandler', []]
os.mkdir(log_dir) elif isinstance(log_target, unicode):
return logging.FileHandler(log_file) log_target = os.path.expanduser(log_target)
else: log_dir = os.path.dirname(log_target)
return logging.StreamHandler(stream) if log_dir and not os.path.isdir(log_dir):
os.mkdir(log_dir)
log_target = ['logging.FileHandler', [[log_target]]]
module, handler_class_name = log_target[0].rpartition('.')[::2]
module = module or 'logging.handlers'
try:
handler_class_args = log_target[1][0]
except IndexError:
if module == 'logging' and handler_class_name == 'StreamHandler':
handler_class_args = [stream]
else:
handler_class_args = ()
try:
handler_class_kwargs = log_target[1][1]
except IndexError:
handler_class_kwargs = {}
module = str(module)
handler_class_name = str(handler_class_name)
handler_class = get_module_attr(module, handler_class_name)
if not handler_class:
continue
handler = handler_class(*handler_class_args, **handler_class_kwargs)
try:
handler_level_name = log_target[2]
except IndexError:
handler_level_name = common_config['log_level']
try:
handler_format = log_target[3]
except IndexError:
handler_format = common_config['log_format']
handler.setLevel(getattr(logging, handler_level_name))
handler.setFormatter(logging.Formatter(handler_format))
logger.addHandler(handler)
num_handlers += 1
if num_handlers == 0 and log_targets:
raise ValueError('Failed to set up any handlers')
def create_logger(common_config, stream=None): def create_logger(common_config, use_daemon_threads=True, ext='__unknown__',
import_paths=None, imported_modules=None, stream=None):
'''Create logger according to provided configuration '''Create logger according to provided configuration
:param dict common_config:
Common configuration, from :py:func:`finish_common_config`.
:param bool use_daemon_threads:
Whether daemon threads should be used. Argument to
:py:class:`PowerlineLogger` constructor.
:param str ext:
Used extension. Argument to :py:class:`PowerlineLogger` constructor.
:param set imported_modules:
Set where imported modules are saved. Argument to
:py:func:`gen_module_attr_getter`. May be ``None``, in this case new
empty set is used.
:param file stream:
Stream to use by default for :py:class:`logging.StreamHandler` in place
of :py:attr:`sys.stderr`. May be ``None``.
:return: Three objects:
#. :py:class:`logging.Logger` instance.
#. :py:class:`PowerlineLogger` instance.
#. Function, output of :py:func:`gen_module_attr_getter`.
''' '''
log_format = common_config['log_format'] logger = logging.Logger('powerline')
formatter = logging.Formatter(log_format)
level = getattr(logging, common_config['log_level']) level = getattr(logging, common_config['log_level'])
handler = _get_log_handler(common_config, stream)
handler.setLevel(level)
handler.setFormatter(formatter)
logger = logging.getLogger('powerline')
logger.setLevel(level) logger.setLevel(level)
logger.addHandler(handler)
return logger pl = PowerlineLogger(use_daemon_threads, logger, ext)
get_module_attr = gen_module_attr_getter(
pl, common_config['paths'],
set() if imported_modules is None else imported_modules)
_set_log_handlers(common_config, logger, get_module_attr, stream)
return logger, pl, get_module_attr
def finish_common_config(encoding, common_config): def finish_common_config(encoding, common_config):
@ -264,7 +326,10 @@ def finish_common_config(encoding, common_config):
common_config.setdefault('additional_escapes', None) common_config.setdefault('additional_escapes', None)
common_config.setdefault('reload_config', True) common_config.setdefault('reload_config', True)
common_config.setdefault('interval', None) common_config.setdefault('interval', None)
common_config.setdefault('log_file', None) common_config.setdefault('log_file', [None])
if not isinstance(common_config['log_file'], list):
common_config['log_file'] = [common_config['log_file']]
common_config['paths'] = [ common_config['paths'] = [
os.path.expanduser(path) for path in common_config['paths'] os.path.expanduser(path) for path in common_config['paths']
@ -324,6 +389,26 @@ def gen_module_attr_getter(pl, import_paths, imported_modules):
return get_module_attr return get_module_attr
LOG_KEYS = set(('log_format', 'log_level', 'log_file', 'paths'))
'''List of keys related to logging
'''
def _get_log_keys(common_config):
'''Return a common configuration copy with only log-related config left
:param dict common_config:
Common configuration.
:return:
:py:class:`dict` instance which has only keys from
:py:attr:`powerline.LOG_KEYS` left.
'''
return dict((
(k, v) for k, v in common_config.items() if k in LOG_KEYS
))
class Powerline(object): class Powerline(object):
'''Main powerline class, entrance point for all powerline uses. Sets '''Main powerline class, entrance point for all powerline uses. Sets
powerline up and loads the configuration. powerline up and loads the configuration.
@ -380,6 +465,7 @@ class Powerline(object):
self.ext = ext self.ext = ext
self.run_once = run_once self.run_once = run_once
self.logger = logger self.logger = logger
self.had_logger = bool(self.logger)
self.use_daemon_threads = use_daemon_threads self.use_daemon_threads = use_daemon_threads
if not renderer_module: if not renderer_module:
@ -430,8 +516,20 @@ class Powerline(object):
This function is used to create logger unless it was already specified This function is used to create logger unless it was already specified
at initialization. at initialization.
:return: Three objects:
#. :py:class:`logging.Logger` instance.
#. :py:class:`PowerlineLogger` instance.
#. Function, output of :py:func:`gen_module_attr_getter`.
''' '''
return create_logger(self.common_config, self.default_log_stream) return create_logger(
common_config=self.common_config,
use_daemon_threads=self.use_daemon_threads,
ext=self.ext,
imported_modules=self.imported_modules,
stream=self.default_log_stream,
)
def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False): def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False):
'''(Re)create renderer object. Can be used after Powerline object was '''(Re)create renderer object. Can be used after Powerline object was
@ -465,22 +563,24 @@ class Powerline(object):
or not self.prev_common_config or not self.prev_common_config
or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme']) or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme'])
log_keys_differ = (not self.prev_common_config or (
_get_log_keys(self.prev_common_config) != _get_log_keys(self.common_config)
))
self.prev_common_config = self.common_config self.prev_common_config = self.common_config
self.import_paths = self.common_config['paths'] if log_keys_differ:
if self.had_logger:
if not self.logger: self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext)
self.logger = self.create_logger() self.get_module_attr = gen_module_attr_getter(
self.pl, self.common_config['paths'], self.imported_modules)
if not self.pl: else:
self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext) self.logger, self.pl, self.get_module_attr = self.create_logger()
self.config_loader.pl = self.pl self.config_loader.pl = self.pl
if not self.run_once: if not self.run_once:
self.config_loader.set_watcher(self.common_config['watcher']) self.config_loader.set_watcher(self.common_config['watcher'])
self.get_module_attr = gen_module_attr_getter(self.pl, self.import_paths, self.imported_modules)
mergedicts(self.renderer_options, dict( mergedicts(self.renderer_options, dict(
pl=self.pl, pl=self.pl,
term_truecolor=self.common_config['term_truecolor'], term_truecolor=self.common_config['term_truecolor'],

View File

@ -2,31 +2,27 @@
# vim:fileencoding=utf-8:noet # vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function) from __future__ import (unicode_literals, division, absolute_import, print_function)
import os
import sys import sys
import time import time
from threading import Lock, Timer from threading import Lock, Timer
from argparse import ArgumentParser from argparse import ArgumentParser
from powerline import Powerline from powerline.lemonbar import LemonbarPowerline
from powerline.lib.encoding import get_unicode_writer from powerline.lib.encoding import get_unicode_writer
class BarPowerline(Powerline):
get_encoding = staticmethod(lambda: 'utf-8')
def init(self):
super(BarPowerline, self).init(ext='wm', renderer_module='bar')
if __name__ == '__main__': if __name__ == '__main__':
parser = ArgumentParser(description='Powerline BAR bindings.') parser = ArgumentParser(description='Powerline lemonbar bindings.')
parser.add_argument( parser.add_argument(
'--i3', action='store_true', '--i3', action='store_true',
help='Subscribe for i3 events.' help='Subscribe for i3 events.'
) )
args = parser.parse_args() args = parser.parse_args()
powerline = BarPowerline() powerline = LemonbarPowerline()
powerline.update_renderer()
powerline.pl.warn("The 'bar' bindings are deprecated, please switch to 'lemonbar'")
lock = Lock() lock = Lock()
modes = ['default'] modes = ['default']
write = get_unicode_writer(encoding='utf-8') write = get_unicode_writer(encoding='utf-8')
@ -60,4 +56,4 @@ if __name__ == '__main__':
conn.main() conn.main()
while True: while True:
time.sleep(1e10) time.sleep(1e8)

View File

@ -9,7 +9,7 @@ import shlex
from powerline.config import POWERLINE_ROOT, TMUX_CONFIG_DIRECTORY from powerline.config import POWERLINE_ROOT, TMUX_CONFIG_DIRECTORY
from powerline.lib.config import ConfigLoader from powerline.lib.config import ConfigLoader
from powerline import generate_config_finder, load_config, create_logger, PowerlineLogger, finish_common_config from powerline import generate_config_finder, load_config, create_logger, finish_common_config
from powerline.shell import ShellPowerline from powerline.shell import ShellPowerline
from powerline.lib.shell import which from powerline.lib.shell import which
from powerline.bindings.tmux import (TmuxVersionInfo, run_tmux_command, set_tmux_environment, get_tmux_version, from powerline.bindings.tmux import (TmuxVersionInfo, run_tmux_command, set_tmux_environment, get_tmux_version,
@ -221,8 +221,8 @@ def get_main_config(args):
def create_powerline_logger(args): def create_powerline_logger(args):
config = get_main_config(args) config = get_main_config(args)
common_config = finish_common_config(get_preferred_output_encoding(), config['common']) common_config = finish_common_config(get_preferred_output_encoding(), config['common'])
logger = create_logger(common_config) logger, pl, get_module_attr = create_logger(common_config)
return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config') return pl
def check_command(cmd): def check_command(cmd):

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import time
import re
import subprocess
from threading import Lock, Timer
from powerline.lemonbar import LemonbarPowerline
from powerline.commands.lemonbar import get_argparser
from powerline.bindings.wm import get_connected_xrandr_outputs
if __name__ == '__main__':
parser = get_argparser()
args = parser.parse_args()
powerline = LemonbarPowerline()
powerline.update_renderer()
bars = []
for screen in get_connected_xrandr_outputs(powerline.pl):
command = [args.bar_command, '-g', '{0}x{1}+{2}'.format(screen['width'], args.height, screen['x'])] + args.args[1:]
process = subprocess.Popen(command, stdin=subprocess.PIPE)
bars.append((screen['name'], process, int(screen['width']) / 5))
lock = Lock()
modes = ['default']
def render(reschedule=False):
if reschedule:
Timer(args.interval, render, kwargs={'reschedule': True}).start()
global lock
with lock:
for output, process, width in bars:
process.stdin.write(powerline.render(mode=modes[0], width=width, matcher_info=output).encode('utf-8') + b'\n')
process.stdin.flush()
def update(evt):
modes[0] = evt.change
render()
render(reschedule=True)
if args.i3:
try:
import i3ipc
except ImportError:
import i3
i3.Subscription(lambda evt, data, sub: render(), 'workspace')
else:
conn = i3ipc.Connection()
conn.on('workspace::focus', lambda conn, evt: render())
conn.on('mode', lambda conn, evt: update(evt))
conn.main()
while True:
time.sleep(1e8)

View File

@ -1,5 +1,4 @@
set -g status on set -g status on
set -g status-utf8 on
set -g status-interval 2 set -g status-interval 2
set -g status-left-length 20 set -g status-left-length 20
set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id=\"`tmux display -p "#D"`\")' set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id=\"`tmux display -p "#D"`\")'

View File

@ -273,7 +273,7 @@ def _vim_to_python(value):
if hasattr(vim, 'options'): if hasattr(vim, 'options'):
def vim_getbufoption(info, option): def vim_getbufoption(info, option):
return info['buffer'].options[str(option)] return _vim_to_python(info['buffer'].options[str(option)])
def vim_getoption(option): def vim_getoption(option):
return vim.options[str(option)] return vim.options[str(option)]

View File

@ -0,0 +1,38 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import re
from powerline.theme import requires_segment_info
from powerline.lib.shell import run_cmd
conn = None
def get_i3_connection():
'''Return a valid, cached i3 Connection instance
'''
global conn
if not conn:
try:
import i3ipc
except ImportError:
import i3 as conn
else:
conn = i3ipc.Connection()
return conn
XRANDR_OUTPUT_RE = re.compile(r'^(?P<name>[0-9A-Za-z-]+) connected(?P<primary> primary)? (?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+)', re.MULTILINE)
def get_connected_xrandr_outputs(pl):
'''Iterate over xrandr outputs
Outputs are represented by a dictionary with ``name``, ``width``,
``height``, ``primary``, ``x`` and ``y`` keys.
'''
return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer(
run_cmd(pl, ['xrandr', '-q'])
))

View File

@ -0,0 +1,35 @@
# vim:fileencoding=utf-8:noet
# WARNING: using unicode_literals causes errors in argparse
from __future__ import (division, absolute_import, print_function)
import argparse
def get_argparser(ArgumentParser=argparse.ArgumentParser):
parser = ArgumentParser(
description='Powerline BAR bindings.'
)
parser.add_argument(
'--i3', action='store_true',
help='Subscribe for i3 events.'
)
parser.add_argument(
'--height', default='',
metavar='PIXELS', help='Bar height.'
)
parser.add_argument(
'--interval', '-i',
type=float, default=0.5,
metavar='SECONDS', help='Refresh interval.'
)
parser.add_argument(
'--bar-command', '-C',
default='lemonbar',
metavar='CMD', help='Name of the lemonbar executable to use.'
)
parser.add_argument(
'args', nargs=argparse.REMAINDER,
help='Extra arguments for lemonbar. Should be preceded with ``--`` '
'argument in order not to be confused with script own arguments.'
)
return parser

View File

@ -23,16 +23,25 @@
"csv:column_number": "line_current", "csv:column_number": "line_current",
"csv:column_name": "line_current_symbol", "csv:column_name": "line_current_symbol",
"tab:background": "background",
"tab:divider": "background:divider",
"tab_nc:modified_indicator": "modified_indicator", "tab_nc:modified_indicator": "modified_indicator",
"tab_nc:file_directory": "information:unimportant", "tab_nc:file_directory": "information:unimportant",
"tab_nc:file_name": "tab_nc:file_directory", "tab_nc:file_name": "tab_nc:file_directory",
"tab_nc:tabnr": "tab_nc:file_directory", "tab_nc:tabnr": "tab_nc:file_directory",
"buf_nc:file_directory": "tab_nc:file_directory", "buf_nc:file_directory": "tab_nc:file_directory",
"buf_nc:file_name": "tab_nc:file_name", "buf_nc:file_name": "buf_nc:file_directory",
"buf_nc:bufnr": "tab_nc:tabnr", "buf_nc:bufnr": "buf_nc:file_directory",
"buf_nc:modified_indicator": "tab_nc:modified_indicator", "buf_nc:modified_indicator": "tab_nc:modified_indicator",
"buf_nc_mod:file_directory": "tab_nc:file_directory",
"buf_nc_mod:file_name": "buf_nc_mod:file_directory",
"buf_nc_mod:bufnr": "buf_nc_mod:file_directory",
"buf_nc_mod:modified_indicator": "tab_nc:modified_indicator",
"commandt:label": "file_name", "commandt:label": "file_name",
"commandt:background": "background", "commandt:background": "background",
"commandt:finder": "file_name", "commandt:finder": "file_name",

View File

@ -131,6 +131,15 @@
"args": { "args": {
"text": "+" "text": "+"
} }
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "O",
"changed": "X"
}
}
} }
} }
} }

View File

@ -129,6 +129,15 @@
"args": { "args": {
"text": "+" "text": "+"
} }
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
} }
} }
} }

View File

@ -143,6 +143,15 @@
"args": { "args": {
"text": "🖫⃥" "text": "🖫⃥"
} }
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
} }
} }
} }

View File

@ -129,6 +129,15 @@
"args": { "args": {
"text": "+" "text": "+"
} }
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
} }
} }
} }

View File

@ -129,6 +129,15 @@
"args": { "args": {
"text": "+" "text": "+"
} }
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
} }
} }
} }

View File

@ -130,6 +130,15 @@
"args": { "args": {
"text": "+" "text": "+"
} }
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
} }
} }
} }

View File

@ -67,7 +67,7 @@
}, },
{ {
"type": "string", "type": "string",
"highlight_groups": ["background"], "highlight_groups": ["tab:background"],
"draw_soft_divider": false, "draw_soft_divider": false,
"draw_hard_divider": false, "draw_hard_divider": false,
"width": "auto" "width": "auto"

21
powerline/lemonbar.py Normal file
View File

@ -0,0 +1,21 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline import Powerline
from powerline.lib.dict import mergedicts
class LemonbarPowerline(Powerline):
def init(self):
super(LemonbarPowerline, self).init(ext='wm', renderer_module='lemonbar')
get_encoding = staticmethod(lambda: 'utf-8')
def get_local_themes(self, local_themes):
if not local_themes:
return {}
return dict((
(key, {'config': self.load_theme_config(val)})
for key, val in local_themes.items()
))

View File

@ -78,3 +78,11 @@ def mergedicts_copy(d1, d2):
else: else:
ret[k] = d2[k] ret[k] = d2[k]
return ret return ret
def updated(d, *args, **kwargs):
'''Copy dictionary and update it with provided arguments
'''
d = d.copy()
d.update(*args, **kwargs)
return d

View File

@ -43,9 +43,15 @@ def get_preferred_output_encoding():
Falls back to ASCII, so that output is most likely to be displayed Falls back to ASCII, so that output is most likely to be displayed
correctly. correctly.
''' '''
if hasattr(locale, 'LC_MESSAGES'):
return (
locale.getlocale(locale.LC_MESSAGES)[1]
or locale.getdefaultlocale()[1]
or 'ascii'
)
return ( return (
locale.getlocale(locale.LC_MESSAGES)[1] locale.getdefaultlocale()[1]
or locale.getdefaultlocale()[1]
or 'ascii' or 'ascii'
) )
@ -57,9 +63,15 @@ def get_preferred_input_encoding():
Falls back to latin1 so that function is less likely to throw as decoded Falls back to latin1 so that function is less likely to throw as decoded
output is primary searched for ASCII values. output is primary searched for ASCII values.
''' '''
if hasattr(locale, 'LC_MESSAGES'):
return (
locale.getlocale(locale.LC_MESSAGES)[1]
or locale.getdefaultlocale()[1]
or 'latin1'
)
return ( return (
locale.getlocale(locale.LC_MESSAGES)[1] locale.getdefaultlocale()[1]
or locale.getdefaultlocale()[1]
or 'latin1' or 'latin1'
) )

View File

@ -7,7 +7,7 @@ import os
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from functools import partial from functools import partial
from powerline.lib.encoding import get_preferred_input_encoding from powerline.lib.encoding import get_preferred_input_encoding, get_preferred_output_encoding
if sys.platform.startswith('win32'): if sys.platform.startswith('win32'):
@ -36,7 +36,8 @@ def run_cmd(pl, cmd, stdin=None, strip=True):
pl.exception('Could not execute command ({0}): {1}', e, cmd) pl.exception('Could not execute command ({0}): {1}', e, cmd)
return None return None
else: else:
stdout, err = p.communicate(stdin) stdout, err = p.communicate(
stdin if stdin is None else stdin.encode(get_preferred_output_encoding()))
stdout = stdout.decode(get_preferred_input_encoding()) stdout = stdout.decode(get_preferred_input_encoding())
return stdout.strip() if strip else stdout return stdout.strip() if strip else stdout

View File

@ -22,7 +22,7 @@ from powerline.lint.checks import (check_matcher_func, check_ext, check_config,
check_segment_function, check_args, get_one_segment_function, check_segment_function, check_args, get_one_segment_function,
check_highlight_groups, check_highlight_group, check_full_segment_data, check_highlight_groups, check_highlight_group, check_full_segment_data,
get_all_possible_functions, check_segment_data_key, register_common_name, get_all_possible_functions, check_segment_data_key, register_common_name,
highlight_group_spec) highlight_group_spec, check_log_file_level, check_logging_handler)
from powerline.lint.spec import Spec from powerline.lint.spec import Spec
from powerline.lint.context import Context from powerline.lint.context import Context
@ -56,6 +56,11 @@ ext_spec = Spec(
top_theme=top_theme_spec().optional(), top_theme=top_theme_spec().optional(),
).copy ).copy
gen_components_spec = (lambda *components: Spec().list(Spec().type(unicode).oneof(set(components)))) gen_components_spec = (lambda *components: Spec().list(Spec().type(unicode).oneof(set(components))))
log_level_spec = Spec().re('^[A-Z]+$').func(
(lambda value, *args: (True, True, not hasattr(logging, value))),
(lambda value: 'unknown debugging level {0}'.format(value))
).copy
log_format_spec = Spec().type(unicode).copy
main_spec = (Spec( main_spec = (Spec(
common=Spec( common=Spec(
default_top_theme=top_theme_spec().optional(), default_top_theme=top_theme_spec().optional(),
@ -67,21 +72,32 @@ main_spec = (Spec(
(lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))), (lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))),
(lambda value: 'path does not exist: {0}'.format(value)) (lambda value: 'path does not exist: {0}'.format(value))
).optional(), ).optional(),
log_file=Spec().type(unicode).func( log_file=Spec().either(
( Spec().type(unicode).func(
lambda value, *args: ( (
True, lambda value, *args: (
True, True,
not os.path.isdir(os.path.dirname(os.path.expanduser(value))) True,
) not os.path.isdir(os.path.dirname(os.path.expanduser(value)))
)
),
(lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value)))
), ),
(lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value))) Spec().list(Spec().either(
Spec().type(unicode, type(None)),
Spec().tuple(
Spec().re(function_name_re).func(check_logging_handler),
Spec().tuple(
Spec().type(list).optional(),
Spec().type(dict).optional(),
),
log_level_spec().func(check_log_file_level).optional(),
log_format_spec().optional(),
),
))
).optional(), ).optional(),
log_level=Spec().re('^[A-Z]+$').func( log_level=log_level_spec().optional(),
(lambda value, *args: (True, True, not hasattr(logging, value))), log_format=log_format_spec().optional(),
(lambda value: 'unknown debugging level {0}'.format(value))
).optional(),
log_format=Spec().type(unicode).optional(),
interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(), interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(),
reload_config=Spec().type(bool).optional(), reload_config=Spec().type(bool).optional(),
watcher=Spec().type(unicode).oneof(set(('auto', 'inotify', 'stat'))).optional(), watcher=Spec().type(unicode).oneof(set(('auto', 'inotify', 'stat'))).optional(),
@ -110,6 +126,12 @@ main_spec = (Spec(
select=ext_theme_spec(), select=ext_theme_spec(),
), ),
).optional(), ).optional(),
wm=ext_spec().update(
local_themes=Spec().unknown_spec(
Spec().re('^[0-9A-Za-z-]+$'),
ext_theme_spec()
).optional()
).optional(),
).unknown_spec( ).unknown_spec(
check_ext, check_ext,
ext_spec(), ext_spec(),

View File

@ -800,3 +800,67 @@ def check_exinclude_function(name, data, context, echoerr):
if not func: if not func:
return True, False, True return True, False, True
return True, False, False return True, False, False
def check_log_file_level(this_level, data, context, echoerr):
'''Check handler level specified in :ref:`log_file key <config-common-log>`
This level must be greater or equal to the level in :ref:`log_level key
<config-common-log_level>`.
'''
havemarks(this_level)
hadproblem = False
top_level = context[0][1].get('common', {}).get('log_level', 'WARNING')
top_level_str = top_level
top_level_mark = getattr(top_level, 'mark', None)
if (
not isinstance(top_level, unicode) or not hasattr(logging, top_level)
or not isinstance(this_level, unicode) or not hasattr(logging, this_level)
):
return True, False, hadproblem
top_level = getattr(logging, top_level)
this_level_str = this_level
this_level_mark = this_level.mark
this_level = getattr(logging, this_level)
if this_level < top_level:
echoerr(
context='Error while checking log level index (key {key})'.format(
key=context.key),
context_mark=this_level_mark,
problem='found level that is less critical then top level ({0} < {0})'.format(
this_level_str, top_level_str),
problem_mark=top_level_mark,
)
hadproblem = True
return True, False, hadproblem
def check_logging_handler(handler_name, data, context, echoerr):
havemarks(handler_name)
import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])]
handler_module, separator, handler_class = handler_name.rpartition('.')
if not separator:
handler_module = 'logging.handlers'
handler_class = handler_name
with WithPath(import_paths):
try:
handler = getattr(__import__(str(handler_module), fromlist=[str(handler_class)]), str(handler_class))
except ImportError:
echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
problem='failed to load module {0}'.format(handler_module),
problem_mark=handler_name.mark)
return True, False, True
except AttributeError:
echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
problem='failed to load handler class {0}'.format(handler_class),
problem_mark=handler_name.mark)
return True, False, True
if not issubclass(handler, logging.Handler):
echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
problem='loaded class {0} is not a logging.Handler subclass'.format(handler_class),
problem_mark=handler_name.mark)
return True, False, True
return True, False, False

View File

@ -21,6 +21,12 @@ class WithPath(object):
def import_function(function_type, name, data, context, echoerr, module): def import_function(function_type, name, data, context, echoerr, module):
havemarks(name, module) havemarks(name, module)
if module == 'powerline.segments.i3wm' and name == 'workspaces':
echoerr(context='Warning while checking segments (key {key})'.format(key=context.key),
context_mark=name.mark,
problem='segment {0} from {1} is deprecated'.format(name, module),
problem_mark=module.mark)
with WithPath(data['import_paths']): with WithPath(data['import_paths']):
try: try:
func = getattr(__import__(str(module), fromlist=[str(name)]), str(name)) func = getattr(__import__(str(module), fromlist=[str(name)]), str(name))

View File

@ -531,7 +531,8 @@ class Spec(object):
if max_len == min_len: if max_len == min_len:
self.len('eq', len(specs)) self.len('eq', len(specs))
else: else:
self.len('ge', min_len) if min_len > 0:
self.len('ge', min_len)
self.len('le', max_len) self.len('le', max_len)
start = len(self.specs) start = len(self.specs)

68
powerline/listers/i3wm.py Normal file
View File

@ -0,0 +1,68 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import requires_segment_info
from powerline.lib.dict import updated
from powerline.bindings.wm import get_i3_connection, get_connected_xrandr_outputs
@requires_segment_info
def output_lister(pl, segment_info):
'''List all outputs in segment_info format
'''
return (
(
updated(segment_info, output=output['name']),
{
'draw_inner_divider': None
}
)
for output in get_connected_xrandr_outputs(pl)
)
@requires_segment_info
def workspace_lister(pl, segment_info, only_show=None, output=None):
'''List all workspaces in segment_info format
Sets the segment info values of ``workspace`` and ``output`` to the name of
the i3 workspace and the ``xrandr`` output respectively and the keys
``"visible"``, ``"urgent"`` and ``"focused"`` to a boolean indicating these
states.
:param list only_show:
Specifies which workspaces to list. Valid entries are ``"visible"``,
``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces
are listed.
:param str output:
May be set to the name of an X output. If specified, only workspaces
on that output are listed. Overrides automatic output detection by
the lemonbar renderer and bindings. Set to ``false`` to force
all workspaces to be shown.
'''
if output == None:
output = output or segment_info.get('output')
return (
(
updated(
segment_info,
output=w['output'],
workspace={
'name': w['name'],
'visible': w['visible'],
'urgent': w['urgent'],
'focused': w['focused'],
},
),
{
'draw_inner_divider': None
}
)
for w in get_i3_connection().get_workspaces()
if (((not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output)))
)

View File

@ -2,12 +2,12 @@
from __future__ import (unicode_literals, division, absolute_import, print_function) from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import requires_segment_info from powerline.theme import requires_segment_info
from powerline.bindings.vim import (current_tabpage, list_tabpages, vim_getbufoption) from powerline.bindings.vim import (current_tabpage, list_tabpages)
try: try:
import vim import vim
except ImportError: except ImportError:
vim = {} vim = object()
def tabpage_updated_segment_info(segment_info, tabpage): def tabpage_updated_segment_info(segment_info, tabpage):
@ -49,7 +49,10 @@ def tablister(pl, segment_info, **kwargs):
return ( return (
(lambda tabpage, prefix: ( (lambda tabpage, prefix: (
tabpage_updated_segment_info(segment_info, tabpage), tabpage_updated_segment_info(segment_info, tabpage),
add_multiplier(tabpage, {'highlight_group_prefix': prefix}) add_multiplier(tabpage, {
'highlight_group_prefix': prefix,
'divider_highlight_group': 'tab:divider'
})
))(tabpage, 'tab' if tabpage == cur_tabpage else 'tab_nc') ))(tabpage, 'tab' if tabpage == cur_tabpage else 'tab_nc')
for tabpage in list_tabpages() for tabpage in list_tabpages()
) )
@ -75,7 +78,8 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs):
and ``bufnr`` keys set to buffer-specific ones, ``window``, ``winnr`` and and ``bufnr`` keys set to buffer-specific ones, ``window``, ``winnr`` and
``window_id`` keys set to None. ``window_id`` keys set to None.
Adds either ``buf:`` or ``buf_nc:`` prefix to all segment highlight groups. Adds one of ``buf:``, ``buf_nc:``, ``buf_mod:``, or ``buf_nc_mod``
prefix to all segment highlight groups.
:param bool show_unlisted: :param bool show_unlisted:
True if unlisted buffers should be shown as well. Current buffer is True if unlisted buffers should be shown as well. Current buffer is
@ -89,22 +93,31 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs):
return dct return dct
return ( return (
( (lambda buffer, current, modified: (
buf_segment_info, buffer_updated_segment_info(segment_info, buffer),
add_multiplier(buf_segment_info['buffer'], {'highlight_group_prefix': prefix}) add_multiplier(buffer, {
'highlight_group_prefix': '{0}{1}'.format(current, modified),
'divider_highlight_group': 'tab:divider'
})
))(
buffer,
'buf' if buffer is cur_buffer else 'buf_nc',
'_mod' if int(vim.eval('getbufvar({0}, \'&modified\')'.format(buffer.number))) > 0 else ''
) )
for buf_segment_info, prefix in ( for buffer in vim.buffers if (
( buffer is cur_buffer
buffer_updated_segment_info( or show_unlisted
segment_info, # We can't use vim_getbufoption(segment_info, 'buflisted')
buffer # here for performance reasons. Querying the buffer options
), # through the vim python module's option attribute caused
('buf' if buffer is cur_buffer else 'buf_nc') # vim to think it needed to update the tabline for every
) # keystroke after any event that changed the buffer's
for buffer in vim.buffers # options.
) if ( #
buf_segment_info['buffer'] is cur_buffer # Using the vim module's eval method to directly use the
or show_unlisted # buflisted(nr) vim method instead does not cause vim to
or int(vim_getbufoption(buf_segment_info, 'buflisted')) # update the tabline after every keystroke, but rather after
# events that would change that status. Fixes #1281
or int(vim.eval('buflisted(%s)' % buffer.number)) > 0
) )
) )

View File

@ -7,7 +7,7 @@ from powerline.bindings.vim import vim_getbufoption, buffer_name
def help(matcher_info): def help(matcher_info):
return str(vim_getbufoption(matcher_info, 'buftype')) == 'help' return vim_getbufoption(matcher_info, 'buftype') == 'help'
def cmdwin(matcher_info): def cmdwin(matcher_info):
@ -16,4 +16,4 @@ def cmdwin(matcher_info):
def quickfix(matcher_info): def quickfix(matcher_info):
return str(vim_getbufoption(matcher_info, 'buftype')) == 'quickfix' return vim_getbufoption(matcher_info, 'buftype') == 'quickfix'

View File

@ -3,9 +3,12 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
import os import os
from powerline.bindings.vim import buffer_name from powerline.bindings.vim import vim_getbufoption, buffer_name
def commandt(matcher_info): def commandt(matcher_info):
name = buffer_name(matcher_info) name = buffer_name(matcher_info)
return name and os.path.basename(name) == b'GoToFile' return (
vim_getbufoption(matcher_info, 'filetype') == 'command-t'
or (name and os.path.basename(name) == b'GoToFile')
)

View File

@ -1,45 +0,0 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.renderer import Renderer
from powerline.colorscheme import ATTR_UNDERLINE
class BarRenderer(Renderer):
'''bar (bar ain't recursive) renderer
See documentation of `bar <https://github.com/LemonBoy/bar>`_ and :ref:`the usage instructions <bar-usage>`
'''
character_translations = Renderer.character_translations.copy()
character_translations[ord('%')] = '%%'
@staticmethod
def hlstyle(*args, **kwargs):
# We dont need to explicitly reset attributes, so skip those calls
return ''
def hl(self, contents, fg=None, bg=None, attrs=None):
text = ''
if fg is not None:
if fg is not False and fg[1] is not False:
text += '%{{F#ff{0:06x}}}'.format(fg[1])
if bg is not None:
if bg is not False and bg[1] is not False:
text += '%{{B#ff{0:06x}}}'.format(bg[1])
if attrs & ATTR_UNDERLINE:
text += '%{+u}'
return text + contents + '%{F-B--u}'
def render(self, *args, **kwargs):
return '%{{l}}{0}%{{r}}{1}'.format(
super(BarRenderer, self).render(side='left', *args, **kwargs),
super(BarRenderer, self).render(side='right', *args, **kwargs),
)
renderer = BarRenderer

View File

@ -0,0 +1,61 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.renderer import Renderer
from powerline.theme import Theme
from powerline.colorscheme import ATTR_UNDERLINE
class LemonbarRenderer(Renderer):
'''lemonbar (formerly bar/bar ain't recursive) renderer
See documentation of `lemonbar <https://github.com/LemonBoy/bar>`_ and :ref:`the usage instructions <lemonbar-usage>`
'''
character_translations = Renderer.character_translations.copy()
character_translations[ord('%')] = '%%{}'
@staticmethod
def hlstyle(*args, **kwargs):
# We dont need to explicitly reset attributes, so skip those calls
return ''
def hl(self, contents, fg=None, bg=None, attrs=None):
text = ''
if fg is not None:
if fg is not False and fg[1] is not False:
text += '%{{F#ff{0:06x}}}'.format(fg[1])
if bg is not None:
if bg is not False and bg[1] is not False:
text += '%{{B#ff{0:06x}}}'.format(bg[1])
if attrs & ATTR_UNDERLINE:
text += '%{+u}'
return text + contents + '%{F-B--u}'
def render(self, *args, **kwargs):
return '%{{l}}{0}%{{r}}{1}'.format(
super(LemonbarRenderer, self).render(side='left', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs),
super(LemonbarRenderer, self).render(side='right', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs),
)
def get_theme(self, matcher_info):
if not matcher_info or matcher_info not in self.local_themes:
return self.theme
match = self.local_themes[matcher_info]
try:
return match['theme']
except KeyError:
match['theme'] = Theme(
theme_config=match['config'],
main_theme_config=self.theme_config,
**self.theme_kwargs
)
return match['theme']
renderer = LemonbarRenderer

View File

@ -30,6 +30,7 @@ def _fetch_battery_info(pl):
else: else:
devinterface = 'org.freedesktop.DBus.Properties' devinterface = 'org.freedesktop.DBus.Properties'
devtype_name = interface + '.Device' devtype_name = interface + '.Device'
devices = []
for devpath in up.EnumerateDevices(dbus_interface=interface): for devpath in up.EnumerateDevices(dbus_interface=interface):
dev = bus.get_object(interface, devpath) dev = bus.get_object(interface, devpath)
devget = lambda what: dev.Get( devget = lambda what: dev.Get(
@ -46,37 +47,63 @@ def _fetch_battery_info(pl):
if not bool(devget('PowerSupply')): if not bool(devget('PowerSupply')):
pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath) pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath)
continue continue
devices.append(devpath)
pl.debug('Using DBUS+UPower with {0}', devpath) pl.debug('Using DBUS+UPower with {0}', devpath)
return lambda pl: ( if devices:
float( def _flatten_battery(pl):
dbus.Interface(dev, dbus_interface=devinterface).Get( energy = 0.0
energy_full = 0.0
state = True
for devpath in devices:
dev = bus.get_object(interface, devpath)
energy_full += float(
dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'EnergyFull'
),
)
energy += float(
dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'Energy'
),
)
state &= dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name, devtype_name,
'Percentage' 'State'
), ) != 2
), return (energy * 100.0 / energy_full), state
dbus.Interface(dev, dbus_interface=devinterface).Get( return _flatten_battery
devtype_name,
'State'
) == 1
)
pl.debug('Not using DBUS+UPower as no batteries were found') pl.debug('Not using DBUS+UPower as no batteries were found')
if os.path.isdir('/sys/class/power_supply'): if os.path.isdir('/sys/class/power_supply'):
linux_bat_fmt = '/sys/class/power_supply/{0}/capacity' linux_energy_full_fmt = '/sys/class/power_supply/{0}/energy_full'
linux_ac_fmt = '/sys/class/power_supply/{0}/online' linux_energy_fmt = '/sys/class/power_supply/{0}/energy_now'
for linux_bat in os.listdir('/sys/class/power_supply'): linux_status_fmt = '/sys/class/power_supply/{0}/status'
cap_path = linux_bat_fmt.format(linux_bat) devices = []
online_path = linux_ac_fmt.format(linux_bat) for linux_supplier in os.listdir('/sys/class/power_supply'):
if linux_bat.startswith('BAT') and os.path.exists(cap_path): energy_path = linux_energy_fmt.format(linux_supplier)
pl.debug('Using /sys/class/power_supply with battery {0}', linux_bat) if not os.path.exists(energy_path):
continue
def _get_battery_status(pl): pl.debug('Using /sys/class/power_supply with battery {0}', linux_supplier)
with open(cap_path, 'r') as f: devices.append(linux_supplier)
_capacity = int(float(f.readline().split()[0])) if devices:
with open(online_path, 'r') as f: def _get_battery_status(pl):
_ac_powered = f.readline() == 1 energy = 0.0
return _capacity, _ac_powered energy_full = 0.0
return _get_battery_status state = True
for device in devices:
with open(linux_energy_full_fmt.format(device), 'r') as f:
energy_full += int(float(f.readline().split()[0]))
with open(linux_energy_fmt.format(device), 'r') as f:
energy += int(float(f.readline().split()[0]))
try:
with open(linux_status_fmt.format(device), 'r') as f:
state &= (f.readline().strip() != 'Discharging')
except IOError:
state = None
return (energy * 100.0 / energy_full), state
return _get_battery_status
pl.debug('Not using /sys/class/power_supply as no batteries were found') pl.debug('Not using /sys/class/power_supply as no batteries were found')
else: else:
pl.debug('Not using /sys/class/power_supply: no directory') pl.debug('Not using /sys/class/power_supply: no directory')

View File

@ -74,6 +74,7 @@ else:
_interface_starts = { _interface_starts = {
'eth': 10, # Regular ethernet adapters : eth1 'eth': 10, # Regular ethernet adapters : eth1
'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0 'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0
'en': 10, # OS X : en0
'ath': 9, # Atheros WiFi adapters : ath0 'ath': 9, # Atheros WiFi adapters : ath0
'wlan': 9, # Other WiFi adapters : wlan1 'wlan': 9, # Other WiFi adapters : wlan1
'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0 'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0

View File

@ -189,7 +189,11 @@ class MpdPlayerSegment(PlayerSegment):
'total': now_playing[3], 'total': now_playing[3],
} }
else: else:
client = mpd.MPDClient() try:
client = mpd.MPDClient(use_unicode=True)
except TypeError:
# python-mpd 1.x does not support use_unicode
client = mpd.MPDClient()
client.connect(host, port) client.connect(host, port)
if password: if password:
client.password(password) client.password(password)
@ -204,7 +208,7 @@ class MpdPlayerSegment(PlayerSegment):
'album': now_playing.get('album'), 'album': now_playing.get('album'),
'artist': now_playing.get('artist'), 'artist': now_playing.get('artist'),
'title': now_playing.get('title'), 'title': now_playing.get('title'),
'elapsed': _convert_seconds(now_playing.get('elapsed', 0)), 'elapsed': _convert_seconds(status.get('elapsed', 0)),
'total': _convert_seconds(now_playing.get('time', 0)), 'total': _convert_seconds(now_playing.get('time', 0)),
} }

View File

@ -13,7 +13,8 @@ from powerline.segments import with_docstring
cpu_count = None cpu_count = None
def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track_cpu_count=False): def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2,
track_cpu_count=False, short=False):
'''Return system load average. '''Return system load average.
Highlights using ``system_load_good``, ``system_load_bad`` and Highlights using ``system_load_good``, ``system_load_bad`` and
@ -35,6 +36,8 @@ def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track
:param bool track_cpu_count: :param bool track_cpu_count:
if True powerline will continuously poll the system to detect changes if True powerline will continuously poll the system to detect changes
in the number of CPUs. in the number of CPUs.
:param bool short:
if True only the sys load over last 1 minute will be displayed.
Divider highlight group used: ``background:divider``. Divider highlight group used: ``background:divider``.
@ -61,6 +64,10 @@ def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track
'divider_highlight_group': 'background:divider', 'divider_highlight_group': 'background:divider',
'gradient_level': gradient_level, 'gradient_level': gradient_level,
}) })
if short:
return ret
ret[0]['contents'] += ' ' ret[0]['contents'] += ' '
ret[1]['contents'] += ' ' ret[1]['contents'] += ' '
return ret return ret

View File

@ -1,13 +1,16 @@
# vim:fileencoding=utf-8:noet # vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function) from __future__ import (unicode_literals, division, absolute_import, print_function)
import re
from powerline.theme import requires_segment_info from powerline.theme import requires_segment_info
from powerline.bindings.wm import get_i3_connection
conn = None WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?')
def calcgrp(w): def workspace_groups(w):
group = [] group = []
if w['focused']: if w['focused']:
group.append('w_focused') group.append('w_focused')
@ -19,7 +22,14 @@ def calcgrp(w):
return group return group
def workspaces(pl, only_show=None, output=None, strip=0): def format_name(name, strip=False):
if strip:
return WORKSPACE_REGEX.sub('', name, count=1)
return name
@requires_segment_info
def workspaces(pl, segment_info, only_show=None, output=None, strip=0):
'''Return list of used workspaces '''Return list of used workspaces
:param list only_show: :param list only_show:
@ -28,7 +38,9 @@ def workspaces(pl, only_show=None, output=None, strip=0):
are shown. are shown.
:param str output: :param str output:
If specified, only workspaces on this output are shown. May be set to the name of an X output. If specified, only workspaces
on that output are shown. Overrides automatic output detection by
the lemonbar renderer and bindings.
:param int strip: :param int strip:
Specifies how many characters from the front of each workspace name Specifies how many characters from the front of each workspace name
@ -36,22 +48,57 @@ def workspaces(pl, only_show=None, output=None, strip=0):
Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``.
''' '''
global conn output = output or segment_info.get('output')
if not conn:
return [
{
'contents': w['name'][strip:],
'highlight_groups': workspace_groups(w)
}
for w in get_i3_connection().get_workspaces()
if ((not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output))
]
@requires_segment_info
def workspace(pl, segment_info, workspace=None, strip=False):
'''Return the specified workspace name
:param str workspace:
Specifies which workspace to show. If unspecified, may be set by the
``list_workspaces`` lister if used, otherwise falls back to
currently focused workspace.
:param bool strip:
Specifies whether workspace numbers (in the ``1: name`` format) should
be stripped from workspace names before being displayed. Defaults to false.
Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``.
'''
if workspace:
try: try:
import i3ipc w = next((
except ImportError: w for w in get_i3_connection().get_workspaces()
import i3 as conn if w['name'] == workspace
else: ))
conn = i3ipc.Connection() except StopIteration:
return None
elif segment_info.get('workspace'):
w = segment_info['workspace']
else:
try:
w = next((
w for w in get_i3_connection().get_workspaces()
if w['focused']
))
except StopIteration:
return None
return [{ return [{
'contents': w['name'][min(len(w['name']), strip):], 'contents': format_name(w['name'], strip=strip),
'highlight_groups': calcgrp(w) 'highlight_groups': workspace_groups(w)
} for w in conn.get_workspaces() }]
if (not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output)
]
@requires_segment_info @requires_segment_info
@ -68,3 +115,41 @@ def mode(pl, segment_info, names={'default': None}):
if mode in names: if mode in names:
return names[mode] return names[mode]
return mode return mode
def scratchpad_groups(w):
group = []
if w.urgent:
group.append('scratchpad:urgent')
if w.nodes[0].focused:
group.append('scratchpad:focused')
if w.workspace().name != '__i3_scratch':
group.append('scratchpad:visible')
group.append('scratchpad')
return group
SCRATCHPAD_ICONS = {
'fresh': 'O',
'changed': 'X',
}
def scratchpad(pl, icons=SCRATCHPAD_ICONS):
'''Returns the windows currently on the scratchpad
:param dict icons:
Specifies the strings to show for the different scratchpad window states. Must
contain the keys ``fresh`` and ``changed``.
Highlight groups used: ``scratchpad`` or ``scratchpad:visible``, ``scratchpad`` or ``scratchpad:focused``, ``scratchpad`` or ``scratchpad:urgent``.
'''
return [
{
'contents': icons.get(w.scratchpad_state, icons['changed']),
'highlight_groups': scratchpad_groups(w)
}
for w in get_i3_connection().get_tree().descendents()
if w.scratchpad_state != 'none'
]

View File

@ -11,7 +11,7 @@ from collections import defaultdict
try: try:
import vim import vim
except ImportError: except ImportError:
vim = {} vim = object()
from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption, from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption,
buffer_name, vim_getwinvar, buffer_name, vim_getwinvar,

View File

@ -7,10 +7,13 @@ import logging
from itertools import count from itertools import count
import vim try:
import vim
except ImportError:
vim = object()
from powerline.bindings.vim import vim_get_func, vim_getvar, get_vim_encoding, python_to_vim from powerline.bindings.vim import vim_get_func, vim_getvar, get_vim_encoding, python_to_vim
from powerline import Powerline, FailedUnicode from powerline import Powerline, FailedUnicode, finish_common_config
from powerline.lib.dict import mergedicts from powerline.lib.dict import mergedicts
from powerline.lib.unicode import u from powerline.lib.unicode import u
@ -32,19 +35,21 @@ def _override_from(config, override_varname, key=None):
class VimVarHandler(logging.Handler, object): class VimVarHandler(logging.Handler, object):
'''Vim-specific handler which emits messages to Vim global variables '''Vim-specific handler which emits messages to Vim global variables
Used variable: ``g:powerline_log_messages``. :param str varname:
Variable where
''' '''
def __init__(self, *args, **kwargs): def __init__(self, varname):
super(VimVarHandler, self).__init__(*args, **kwargs) super(VimVarHandler, self).__init__()
vim.command('unlet! g:powerline_log_messages') utf_varname = u(varname)
vim.command('let g:powerline_log_messages = []') self.vim_varname = utf_varname.encode('ascii')
vim.command('unlet! g:' + utf_varname)
vim.command('let g:' + utf_varname + ' = []')
@staticmethod def emit(self, record):
def emit(record):
message = u(record.message) message = u(record.message)
if record.exc_text: if record.exc_text:
message += '\n' + u(record.exc_text) message += '\n' + u(record.exc_text)
vim.eval(b'add(g:powerline_log_messages, ' + python_to_vim(message) + b')') vim.eval(b'add(g:' + self.vim_varname + b', ' + python_to_vim(message) + b')')
class VimPowerline(Powerline): class VimPowerline(Powerline):
@ -53,6 +58,12 @@ class VimPowerline(Powerline):
self.last_window_id = 1 self.last_window_id = 1
self.pyeval = pyeval self.pyeval = pyeval
self.construct_window_statusline = self.create_window_statusline_constructor() self.construct_window_statusline = self.create_window_statusline_constructor()
if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))):
self.win_idx = self.new_win_idx
else:
self.win_idx = self.old_win_idx
self._vim_getwinvar = vim_get_func('getwinvar', 'bytes')
self._vim_setwinvar = vim_get_func('setwinvar')
if sys.version_info < (3,): if sys.version_info < (3,):
def create_window_statusline_constructor(self): def create_window_statusline_constructor(self):
@ -80,18 +91,6 @@ class VimPowerline(Powerline):
default_log_stream = sys.stdout default_log_stream = sys.stdout
def create_logger(self):
logger = super(VimPowerline, self).create_logger()
try:
if int(vim_getvar('powerline_use_var_handler')):
formatter = logging.Formatter(self.common_config['log_format'])
handler = VimVarHandler(getattr(logging, self.common_config['log_level']))
handler.setFormatter(formatter)
logger.addHandler(handler)
except KeyError:
pass
return logger
def add_local_theme(self, key, config): def add_local_theme(self, key, config):
'''Add local themes at runtime (during vim session). '''Add local themes at runtime (during vim session).
@ -137,7 +136,16 @@ class VimPowerline(Powerline):
get_encoding = staticmethod(get_vim_encoding) get_encoding = staticmethod(get_vim_encoding)
def load_main_config(self): def load_main_config(self):
return _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides') main_config = _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides')
try:
use_var_handler = bool(int(vim_getvar('powerline_use_var_handler')))
except KeyError:
use_var_handler = False
if use_var_handler:
main_config.setdefault('common', {})
main_config['common'] = finish_common_config(self.get_encoding(), main_config['common'])
main_config['common']['log_file'].append(['powerline.vim.VimVarHandler', [['powerline_log_messages']]])
return main_config
def load_theme_config(self, name): def load_theme_config(self, name):
return _override_from( return _override_from(
@ -252,44 +260,40 @@ class VimPowerline(Powerline):
# do anything. # do anything.
pass pass
if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))): def new_win_idx(self, window_id):
def win_idx(self, window_id): r = None
r = None for window in vim.windows:
for window in vim.windows: try:
try: curwindow_id = window.vars['powerline_window_id']
curwindow_id = window.vars['powerline_window_id'] if r is not None and curwindow_id == window_id:
if r is not None and curwindow_id == window_id: raise KeyError
raise KeyError except KeyError:
except KeyError: curwindow_id = self.last_window_id
curwindow_id = self.last_window_id self.last_window_id += 1
self.last_window_id += 1 window.vars['powerline_window_id'] = curwindow_id
window.vars['powerline_window_id'] = curwindow_id statusline = self.construct_window_statusline(curwindow_id)
statusline = self.construct_window_statusline(curwindow_id) if window.options['statusline'] != statusline:
if window.options['statusline'] != statusline: window.options['statusline'] = statusline
window.options['statusline'] = statusline if curwindow_id == window_id if window_id else window is vim.current.window:
if curwindow_id == window_id if window_id else window is vim.current.window: r = (window, curwindow_id, window.number)
r = (window, curwindow_id, window.number) return r
return r
else:
_vim_getwinvar = staticmethod(vim_get_func('getwinvar', 'bytes'))
_vim_setwinvar = staticmethod(vim_get_func('setwinvar'))
def win_idx(self, window_id): def old_win_idx(self, window_id):
r = None r = None
for winnr, window in zip(count(1), vim.windows): for winnr, window in zip(count(1), vim.windows):
curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id') curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id')
if curwindow_id and not (r is not None and curwindow_id == window_id): if curwindow_id and not (r is not None and curwindow_id == window_id):
curwindow_id = int(curwindow_id) curwindow_id = int(curwindow_id)
else: else:
curwindow_id = self.last_window_id curwindow_id = self.last_window_id
self.last_window_id += 1 self.last_window_id += 1
self._vim_setwinvar(winnr, 'powerline_window_id', curwindow_id) self._vim_setwinvar(winnr, 'powerline_window_id', curwindow_id)
statusline = self.construct_window_statusline(curwindow_id) statusline = self.construct_window_statusline(curwindow_id)
if self._vim_getwinvar(winnr, '&statusline') != statusline: if self._vim_getwinvar(winnr, '&statusline') != statusline:
self._vim_setwinvar(winnr, '&statusline', statusline) self._vim_setwinvar(winnr, '&statusline', statusline)
if curwindow_id == window_id if window_id else window is vim.current.window: if curwindow_id == window_id if window_id else window is vim.current.window:
r = (window, curwindow_id, winnr) r = (window, curwindow_id, winnr)
return r return r
def statusline(self, window_id): def statusline(self, window_id):
window, window_id, winnr = self.win_idx(window_id) or (None, None, None) window, window_id, winnr = self.win_idx(window_id) or (None, None, None)

View File

@ -59,7 +59,7 @@ else:
def get_version(): def get_version():
base_version = '2.3' base_version = '2.4'
base_version += '.dev9999' base_version += '.dev9999'
try: try:
return base_version + '+git.' + str(subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()) return base_version + '+git.' + str(subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip())
@ -70,7 +70,7 @@ def get_version():
setup( setup(
name='powerline-status', name='powerline-status',
version='2.3', version='2.4',
description='The ultimate statusline/prompt utility.', description='The ultimate statusline/prompt utility.',
long_description=README, long_description=README,
classifiers=[ classifiers=[

View File

@ -18,7 +18,9 @@ exit_suite() {
echo "${FAIL_SUMMARY}" echo "${FAIL_SUMMARY}"
fi fi
export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE%/*}" export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE%/*}"
exit $FAILED if test "x$1" != "x--continue" ; then
exit $FAILED
fi
} }
fail() { fail() {

View File

@ -16,11 +16,16 @@ class Pl(object):
self.use_daemon_threads = True self.use_daemon_threads = True
for meth in ('error', 'warn', 'debug', 'exception', 'info'): for meth in ('error', 'warn', 'debug', 'exception', 'info'):
exec (( exec((
'def {0}(self, msg, *args, **kwargs):\n' 'def {0}(self, msg, *args, **kwargs):\n'
' self.{0}s.append((kwargs.get("prefix") or self.prefix, msg, args, kwargs))\n' ' self.{0}s.append((kwargs.get("prefix") or self.prefix, msg, args, kwargs))\n'
).format(meth)) ).format(meth))
def __nonzero__(self):
return bool(self.exceptions or self.errors or self.warns)
__bool__ = __nonzero__
class Args(object): class Args(object):
theme_override = {} theme_override = {}

201
tests/run_bar_tests.sh Executable file
View File

@ -0,0 +1,201 @@
#!/bin/sh
. tests/common.sh
enter_suite bar
TEST_ROOT="$ROOT/tests/bar"
TEST_PATH="$TEST_ROOT/path"
TEST_STATIC_ROOT="$ROOT/tests/test_bar"
test -d "$TEST_ROOT" && rm -r "$TEST_ROOT"
mkdir "$TEST_ROOT"
cp -r "$TEST_STATIC_ROOT/path" "$TEST_ROOT"
cp -r "$TEST_STATIC_ROOT/powerline" "$TEST_ROOT"
export PYTHONPATH="$ROOT${PYTHONPATH:+:}$PYTHONPATH"
ln -s "$(which "${PYTHON}")" "$TEST_PATH"/python
ln -s "$(which sed)" "$TEST_PATH"
ln -s "$(which cat)" "$TEST_PATH"
ln -s "$(which mkdir)" "$TEST_PATH"
ln -s "$(which basename)" "$TEST_PATH"
ln -s "$TEST_PATH/lemonbar" "$TEST_PATH/bar-aint-recursive"
DEPRECATED_SCRIPT="$ROOT/powerline/bindings/bar/powerline-bar.py"
run() {
env -i \
LANG=C \
PATH="$TEST_PATH" \
XDG_CONFIG_HOME="$TEST_ROOT" \
XDG_CONFIG_DIRS="$TEST_ROOT/dummy" \
PYTHONPATH="$PYTHONPATH" \
TEST_ROOT="$TEST_ROOT" \
LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \
"$@" || true
}
display_log() {
local log_file="$1"
echo "$log_file:"
echo '============================================================'
cat -v "$log_file"
echo
echo '____________________________________________________________'
}
check_log() {
local log_file="$1"
local text="$2"
local warns="$3"
if test "$warns" = "warns" ; then
local warning="$(head -n1 "$log_file" | sed 's/.*://')"
local expwarning="The 'bar' bindings are deprecated, please switch to 'lemonbar'"
if test "x$warning" != "x$expwarning" ; then
echo "Got: $warning"
echo "Exp: $expwarning"
fail "warn" F "Expected warning"
fi
sed -r -i -e '1d' "$log_file"
fi
local line="$(head -n1 "$log_file")"
local linenum="$(cat "$log_file" | wc -l)"
if test $linenum -lt 5 ; then
fail "log:lt" F "Script was run not enough times"
return 1
elif test $linenum -gt 15 ; then
fail "log:gt" E "Script was run too many times"
return 1
fi
local expline="%{l}%{F#ffd0d0d0}%{B#ff303030} $text-left %{F-B--u}%{F#ff303030} %{F-B--u}%{r}%{F#ff303030} %{F-B--u}%{F#ffd0d0d0}%{B#ff303030} $text-right %{F-B--u}"
if test "x$expline" != "x$line" ; then
echo "Line: '$line'"
echo "Expected: '$expline'"
fail "log:line" F "Unexpected line"
return 1
fi
local ret=0
while test $linenum -gt 0 ; do
echo "$line" >> "$TEST_ROOT/ok"
linenum=$(( linenum - 1 ))
done
if ! diff "$TEST_ROOT/ok" "$log_file" ; then
fail "log:diff" F "Unexpected output"
ret=1
fi
rm "$TEST_ROOT/ok"
return $ret
}
killscript() {
kill -KILL $1 || true
}
if ! test -e "$DEPRECATED_SCRIPT" ; then
# TODO: uncomment when skip is available
# skip "deprecated" "Missing deprecated bar bindings script"
:
else
enter_suite "deprecated"
run python "$DEPRECATED_SCRIPT" $args > "$TEST_ROOT/deprecated.log" 2>&1 &
SPID=$!
sleep 5
killscript $SPID
if ! check_log "$TEST_ROOT/deprecated.log" "default" warns ; then
display_log "$TEST_ROOT/deprecated.log"
fail "log" F "Checking log failed"
fi
rm "$TEST_ROOT/deprecated.log"
exit_suite --continue
fi
LEMONBAR_SCRIPT="$ROOT/powerline/bindings/lemonbar/powerline-lemonbar.py"
if ! test -e "$LEMONBAR_SCRIPT" ; then
# TODO: uncomment when skip is available
# skip "lemonbar" "Missing lemonbar bindings script"
:
else
enter_suite "lemonbar"
for args in "" "-i0.5" "--interval=0.5" "-- test args" "--bar-command bar-aint-recursive" "--height=10"; do
rm -rf "$TEST_ROOT/results"
run python "$LEMONBAR_SCRIPT" $args > "$TEST_ROOT/lemonbar.log" 2>&1 &
SPID=$!
sleep 5
killscript $SPID
sleep 0.5
enter_suite "args($args)"
fnum=0
for file in "$TEST_ROOT/results"/*.log ; do
if ! test -e "$file" ; then
fail "log" E "Log file is missing"
break
fi
fnum=$(( fnum + 1 ))
args_file="${file%.log}.args"
if ! test -e "$args_file" ; then
fail "args" E "$args_file is missing"
else
cat "$args_file" >> "$TEST_ROOT/args.log"
fi
text="dvi"
if cat "$args_file" | grep -q +1 ; then
text="default"
fi
if ! check_log "$file" "$text" ; then
display_log "$file"
fail "log" F "Checking log failed"
fi
rm "$file"
done
if test "$fnum" -ne 2 ; then
fail "fnum" F "Expected two output files"
fi
if test "x${args#--height}" != "x$args" ; then
height="${args#--height}"
height="${height# }"
height="${height#=}"
height="${height%% *}"
fi
command="lemonbar"
if test "x${args#--bar-command}" != "x$args" ; then
command="${args#--bar-command}"
command="${command# }"
command="${command#=}"
command="${command%% *}"
fi
received_args="$(cat "$TEST_ROOT/args.log" | sort)"
rm "$TEST_ROOT/args.log"
script_args="${args#*-- }"
script_args="${script_args# }"
if test "x${script_args}" '=' "x$args" ; then
script_args=
fi
expected_args="$command -g 1920x$height+0${script_args:+ }$script_args${NL}$command -g 1920x$height+1${script_args:+ }$script_args"
if test "x$expected_args" != "x$received_args" ; then
echo "args:${NL}<$received_args>"
echo "expected:${NL}<$expected_args>"
fail "args" F "Expected different args"
fi
if ! test -z "$(cat "$TEST_ROOT/lemonbar.log")" ; then
display_log "$TEST_ROOT/lemonbar.log"
fail "stderr" E "Unexpected script output"
fi
rm "$TEST_ROOT/lemonbar.log"
exit_suite --continue
done
exit_suite --continue
fi
if ! powerline-lint \
-p "$ROOT/powerline/config_files" \
-p "$TEST_STATIC_ROOT/powerline"
then
fail "lint" F "Checking test config failed"
fi
if test $FAILED -eq 0 ; then
rm -r "$TEST_ROOT"
fi
exit_suite

11
tests/test_bar/path/lemonbar Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
RES_DIR="$TEST_ROOT/results"
mkdir -p "$RES_DIR"
RES_FILE="$RES_DIR/$$"
while test -e "$RES_FILE.log" ; do
RES_FILE="$RES_FILE.${RANDOM:-`date +%N | sed s/^0*//`}"
done
echo $(basename $0) "$@" > "$RES_FILE.args"
cat > "$RES_FILE.log"

31
tests/test_bar/path/xrandr Executable file
View File

@ -0,0 +1,31 @@
#!/bin/sh
cat << EOF
Screen 0: minimum 8 x 8, current 1920 x 1200, maximum 16384 x 16384
DVI-I-0 disconnected (normal left inverted right x axis y axis)
VGA-0 connected 1920x1200+1+0 (normal left inverted right x axis y axis) 520mm x 330mm
1920x1200 59.95*+
1920x1080 60.00
1680x1050 59.95
1600x1200 60.00
1440x900 59.89
1280x1024 75.02 60.02
1280x800 59.81
1152x864 75.00
1024x768 75.03 60.00
800x600 75.00 60.32
640x480 75.00 59.94
DVI-I-1 connected 1920x1200+0+0 (normal left inverted right x axis y axis) 520mm x 330mm
1920x1200 59.95*+
1920x1080 60.00
1680x1050 59.95
1600x1200 60.00
1440x900 59.89
1280x1024 75.02 60.02
1280x800 59.81
1152x864 75.00
1024x768 75.03 60.00
800x600 75.00 60.32
640x480 75.00 59.94
HDMI-0 disconnected (normal left inverted right x axis y axis)
EOF

View File

@ -0,0 +1,9 @@
{
"ext": {
"wm": {
"local_themes": {
"DVI-I-1": "dvi"
}
}
}
}

View File

@ -0,0 +1,18 @@
{
"segments": {
"left": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "default-left"
}
],
"right": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "default-right"
}
]
}
}

View File

@ -0,0 +1,18 @@
{
"segments": {
"left": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "dvi-left"
}
],
"right": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "dvi-right"
}
]
}
}

View File

@ -825,28 +825,28 @@ class TestVim(TestCase):
sys.path.pop(0) sys.path.pop(0)
class TestBar(TestRender): class TestLemonbar(TestRender):
def test_bar(self): def test_lemonbar(self):
import powerline as powerline_module import powerline as powerline_module
with swap_attributes(config, powerline_module): with swap_attributes(config, powerline_module):
with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='bar') as powerline: with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline:
self.assertRenderEqual( self.assertRenderEqual(
powerline, powerline,
'%{l}%{F#ffc00000}%{B#ff008000}%{+u} A%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}B%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}' '%{l}%{F#ffc00000}%{B#ff008000}%{+u} A%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}B%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}'
) )
@with_new_config @with_new_config
def test_bar_escape(self, config): def test_lemonbar_escape(self, config):
import powerline as powerline_module import powerline as powerline_module
config['themes/wm/default']['segments']['left'] = ( config['themes/wm/default']['segments']['left'] = (
highlighted_string('%{asd}', 'hl1'), highlighted_string('%{asd}', 'hl1'),
highlighted_string('10% %', 'hl2'), highlighted_string('10% %', 'hl2'),
) )
with swap_attributes(config, powerline_module): with swap_attributes(config, powerline_module):
with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='bar') as powerline: with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline:
self.assertRenderEqual( self.assertRenderEqual(
powerline, powerline,
'%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%% %%%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}' '%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{}{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%%{} %%{}%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}'
) )

View File

@ -17,6 +17,7 @@ from powerline.lib.vcs import guess, get_fallback_create_watcher
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
from powerline.lib.monotonic import monotonic from powerline.lib.monotonic import monotonic
from powerline.lib.vcs.git import git_directory from powerline.lib.vcs.git import git_directory
from powerline.lib.shell import run_cmd
import powerline.lib.unicode as plu import powerline.lib.unicode as plu
@ -48,6 +49,24 @@ def thread_number():
return len(threading.enumerate()) return len(threading.enumerate())
class TestShell(TestCase):
def test_run_cmd(self):
pl = Pl()
self.assertEqual(run_cmd(pl, ['xxx_nonexistent_command_xxx']), None)
self.assertEqual(len(pl.exceptions), 1)
pl = Pl()
self.assertEqual(run_cmd(pl, ['echo', ' test ']), 'test')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=True), 'test')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=False), ' test \n')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['cat'], stdin='test'), 'test')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['sh', '-c', 'cat >&2'], stdin='test'), '')
self.assertFalse(pl)
class TestThreaded(TestCase): class TestThreaded(TestCase):
def test_threaded_segment(self): def test_threaded_segment(self):
log = [] log = []

227
tests/test_listers.py Normal file
View File

@ -0,0 +1,227 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import powerline.listers.i3wm as i3wm
from tests.lib import Args, replace_attr, Pl
from tests import TestCase
class TestI3WM(TestCase):
@staticmethod
def get_workspaces():
return iter([
{'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
{'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
{'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
{'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
])
@staticmethod
def get_outputs(pl):
return iter([
{'name': 'LVDS1'},
{'name': 'HDMI1'},
{'name': 'DVI01'},
])
def test_output_lister(self):
pl = Pl()
with replace_attr(i3wm, 'get_connected_xrandr_outputs', self.get_outputs):
self.assertEqual(
list(i3wm.output_lister(pl=pl, segment_info={'a': 1})),
[
({'a': 1, 'output': 'LVDS1'}, {'draw_inner_divider': None}),
({'a': 1, 'output': 'HDMI1'}, {'draw_inner_divider': None}),
({'a': 1, 'output': 'DVI01'}, {'draw_inner_divider': None}),
]
)
def test_workspace_lister(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
self.assertEqual(
list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1})),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1}, output='LVDS1')),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1, 'output': 'LVDS1'}
)),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1, 'output': 'LVDS1'},
output=False
)),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1},
only_show=['focused', 'urgent']
)),
[
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
if __name__ == '__main__':
from tests import main
main()

467
tests/test_logging.py Normal file
View File

@ -0,0 +1,467 @@
# vim:fileencoding=utf-8:noet
'''Tests for various logging features'''
from __future__ import (unicode_literals, division, absolute_import, print_function)
import sys
import re
import codecs
import os
from io import StringIO
from shutil import rmtree
from powerline import finish_common_config, create_logger
from tests import TestCase
from tests.lib import replace_attr
TIMESTAMP_RE = r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3}'
class TestRE(TestCase):
def assertMatches(self, text, regexp):
self.assertTrue(
re.match(regexp, text),
'{0!r} did not match {1!r}'.format(text, regexp),
)
def close_handlers(logger):
for handler in logger.handlers:
handler.close()
class TestHandlers(TestRE):
def test_stderr_handler_is_default(self):
out = StringIO()
err = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(out.getvalue(), '')
def test_stream_override(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_explicit_none(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [None]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_explicit_stream_handler(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', [[]]]]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertEqual(stream.getvalue(), '')
self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(out.getvalue(), '')
def test_explicit_stream_handler_implicit_stream(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', []]]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_file_handler(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_file_handler'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': file_name})
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_file_handler_create_dir(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_file_handler_create_dir/file'
self.assertFalse(os.path.isdir(os.path.dirname(file_name)))
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': file_name})
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertTrue(os.path.isdir(os.path.dirname(file_name)))
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
rmtree(os.path.dirname(file_name))
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_multiple_files(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name_1 = 'test_logging-test_multiple_files-1'
file_name_2 = file_name_1[:-1] + '2'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2]})
try:
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
for file_name in (file_name_1, file_name_2):
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name_1)
finally:
os.unlink(file_name_2)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_multiple_files_and_stream(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name_1 = 'test_logging-test_multiple_files_and_stream-1'
file_name_2 = file_name_1[:-1] + '2'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2, None]})
try:
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
for file_name in (file_name_1, file_name_2):
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name_1)
finally:
os.unlink(file_name_2)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_handler_args(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_handler_args'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['RotatingFileHandler', [[file_name]]]
]})
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_handler_args_kwargs(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_handler_args_kwargs'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['RotatingFileHandler', [[file_name], {'maxBytes': 1, 'backupCount': 1}]]
]})
try:
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
pl.error('Bar')
close_handlers(logger)
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
with codecs.open(file_name + '.1', encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name + '.1')
finally:
os.unlink(file_name)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_logger_level(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
stream2 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'WARNING'],
['logging.StreamHandler', [[stream2]], 'ERROR'],
]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertMatches(stream1.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:Foo\n'
+ TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$'
))
self.assertMatches(stream2.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_logger_level_not_overriding_default(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'DEBUG'],
]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.debug('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertMatches(stream1.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_top_log_level(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'DEBUG'],
], 'log_level': 'DEBUG'})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.debug('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertMatches(stream1.getvalue(), (
'^' + TIMESTAMP_RE + ':DEBUG:__unknown__:Foo\n'
+ TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$'
))
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_logger_format(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'],
]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_top_log_format(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
stream2 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'],
['logging.StreamHandler', [[stream2]], 'WARNING'],
], 'log_format': 'BAR'})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertEqual(stream2.getvalue(), 'BAR\nBAR\n')
self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
class TestPowerlineLogger(TestRE):
def test_args_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('foo {0}', 'Test')
pl.warn('bar {0!r}', 'Test')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$'
))
def test_prefix_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.prefix = '1'
pl.warn('foo')
pl.prefix = '2'
pl.warn('bar')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:1:foo\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:2:bar\n$'
))
def test_kwargs_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('foo {arg}', arg='Test')
pl.warn('bar {arg!r}', arg='Test')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$'
))
def test_args_kwargs_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('foo {0!r} {arg}', 'Test0', arg='Test')
pl.warn('bar {0} {arg!r}', 'Test0', arg='Test')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo u?\'Test0\' Test\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:bar Test0 u?\'Test\'\n$'
))
def test_exception_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
try:
raise ValueError('foo')
except ValueError:
pl.exception('Message')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':ERROR:__unknown__:Message\n'
+ 'Traceback \\(most recent call last\\):\n'
+ '(?: File ".*?", line \\d+, in \\w+\n [^\n]*\n)+'
+ 'ValueError: foo\n$'
))
def test_levels(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {'log_level': 'DEBUG'})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.debug('1')
pl.info('2')
pl.warn('3')
pl.error('4')
pl.critical('5')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':DEBUG:__unknown__:1\n'
+ TIMESTAMP_RE + ':INFO:__unknown__:2\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:3\n'
+ TIMESTAMP_RE + ':ERROR:__unknown__:4\n'
+ TIMESTAMP_RE + ':CRITICAL:__unknown__:5\n$'
))
old_cwd = None
def setUpModule():
global old_cwd
global __file__
old_cwd = os.getcwd()
__file__ = os.path.abspath(__file__)
os.chdir(os.path.dirname(__file__))
def tearDownModule():
global old_cwd
os.chdir(old_cwd)
if __name__ == '__main__':
from tests import main
main()

View File

@ -811,6 +811,12 @@ class TestSys(TestCommon):
{'contents': '4 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, {'contents': '4 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
{'contents': '2', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0} {'contents': '2', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}
]) ])
self.assertEqual(self.module.system_load(pl=pl, short=True), [
{'contents': '7.5', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
])
self.assertEqual(self.module.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1, short=True), [
{'contents': '8', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
])
def test_cpu_load_percent(self): def test_cpu_load_percent(self):
try: try:
@ -883,49 +889,85 @@ class TestWthr(TestCommon):
class TestI3WM(TestCase): class TestI3WM(TestCase):
def test_workspaces(self): @staticmethod
pl = Pl() def get_workspaces():
with replace_attr(i3wm, 'conn', Args(get_workspaces=lambda: iter([ return iter([
{'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
{'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
{'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
{'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
]))): ])
self.assertEqual(i3wm.workspaces(pl=pl), [
def test_workspaces(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
segment_info = {}
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [
{'contents': '1: w1', 'highlight_groups': ['workspace']}, {'contents': '1: w1', 'highlight_groups': ['workspace']},
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
]) ])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=None), [ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=None), [
{'contents': '1: w1', 'highlight_groups': ['workspace']}, {'contents': '1: w1', 'highlight_groups': ['workspace']},
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
]) ])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['focused', 'urgent']), [ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent']), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
]) ])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible']), [ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible']), [
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
]) ])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], strip=3), [ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, {'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
]) ])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['focused', 'urgent'], output='DVI01'), [ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent'], output='DVI01'), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
]) ])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], output='HDMI1'), [ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
]) ])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], strip=3, output='LVDS1'), [ self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3, output='LVDS1'), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
]) ])
segment_info['output'] = 'LVDS1'
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
])
def test_workspace(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
segment_info = {}
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='1: w1'), [
{'contents': '1: w1', 'highlight_groups': ['workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='3: w3', strip=True), [
{'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='9: w9'), None)
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
segment_info['workspace'] = next(self.get_workspaces())
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='4: w4'), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [
{'contents': 'w1', 'highlight_groups': ['workspace']},
])
def test_mode(self): def test_mode(self):
pl = Pl() pl = Pl()
@ -934,6 +976,45 @@ class TestI3WM(TestCase):
self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}, names={'default': 'test'}), 'test') self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}, names={'default': 'test'}), 'test')
self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}, names={'default': 'test', 'test': 't'}), 't') self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}, names={'default': 'test', 'test': 't'}), 't')
def test_scratchpad(self):
class Conn(object):
def get_tree(self):
return self
def descendents(self):
nodes_unfocused = [Args(focused = False)]
nodes_focused = [Args(focused = True)]
workspace_scratch = lambda: Args(name='__i3_scratch')
workspace_noscratch = lambda: Args(name='2: www')
return [
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused),
Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused),
]
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()):
self.assertEqual(i3wm.scratchpad(pl=pl), [
{'contents': 'O', 'highlight_groups': ['scratchpad']},
{'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']},
{'contents': 'O', 'highlight_groups': ['scratchpad']},
{'contents': 'X', 'highlight_groups': ['scratchpad:visible', 'scratchpad']},
{'contents': 'O', 'highlight_groups': ['scratchpad:focused', 'scratchpad']},
{'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']},
])
self.assertEqual(i3wm.scratchpad(pl=pl, icons={'changed': '-', 'fresh': 'o'}), [
{'contents': 'o', 'highlight_groups': ['scratchpad']},
{'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']},
{'contents': 'o', 'highlight_groups': ['scratchpad']},
{'contents': '-', 'highlight_groups': ['scratchpad:visible', 'scratchpad']},
{'contents': 'o', 'highlight_groups': ['scratchpad:focused', 'scratchpad']},
{'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']},
])
class TestMail(TestCommon): class TestMail(TestCommon):
module_name = 'mail' module_name = 'mail'

View File

@ -16,7 +16,7 @@ catch
cquit cquit
endtry endtry
if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  %2T2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs ' if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %2T%#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs '
call writefile(['Unexpected tabline', result], 'message.fail') call writefile(['Unexpected tabline', result], 'message.fail')
cquit cquit
endif endif
@ -30,7 +30,7 @@ catch
cquit cquit
endtry endtry
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
call writefile(['Unexpected tabline (2)', result], 'message.fail') call writefile(['Unexpected tabline (2)', result], 'message.fail')
cquit cquit
endif endif
@ -42,7 +42,7 @@ catch
call writefile(['Exception while evaluating &tabline (3)', v:exception], 'message.fail') call writefile(['Exception while evaluating &tabline (3)', v:exception], 'message.fail')
endtry endtry
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
call writefile(['Unexpected tabline (3)', result], 'message.fail') call writefile(['Unexpected tabline (3)', result], 'message.fail')
cquit cquit
endif endif

View File

@ -265,6 +265,14 @@ def eval(expr):
import os import os
assert os.path.basename(current.buffer.name).startswith('NERD_tree_') assert os.path.basename(current.buffer.name).startswith('NERD_tree_')
return '/usr/include' return '/usr/include'
elif expr.startswith('getbufvar('):
import re
match = re.match(r'^getbufvar\((\d+), ["\'](.+)["\']\)$', expr)
if not match:
raise NotImplementedError(expr)
bufnr = int(match.group(1))
varname = match.group(2)
return _emul_getbufvar(bufnr, varname)
elif expr == 'tabpagenr()': elif expr == 'tabpagenr()':
return current.tabpage.number return current.tabpage.number
elif expr == 'tabpagenr("$")': elif expr == 'tabpagenr("$")':