mirror of
https://github.com/powerline/powerline.git
synced 2025-07-29 00:34:49 +02:00
Release 1.1
Changes: - Deprecated `powerline.segments.common`, moved all segments into deeply nested modules (e.g. `powerline.segments.common.vcs`). - Added support for different tag formats provided by tagbar plugin. - Improved non-unicode locales handling. - Improved non-unicode filenames handling. - Improved Vim troubleshooting: it now runs powerline-lint automatically. - Fixed weather segment: it was remembering its argument from the first run and never allowed to change it. - Fixed Uv watcher: it raised an exception for nonexistent files. - Fixed zsh named directories handling when AUTO_NAME_DIRS option is set. - Fixed --config_path handling when using daemon: in some cases it was remembered once and never changed. - Fixed Vim overrides when Vim is using Python-3*. - Fixed Uv watcher on Python-3*: it used os.path.walk which was removed. - Refactored and documented powerline.lint.
This commit is contained in:
commit
7aafbf0e3b
@ -113,6 +113,8 @@ Programming style
|
|||||||
|
|
||||||
'''Powerline super module'''
|
'''Powerline super module'''
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
@ -2,3 +2,5 @@ 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 client *.*
|
recursive-include client *.*
|
||||||
|
recursive-include docs/source *.rst *.py
|
||||||
|
include docs/Makefile
|
||||||
|
12
README.rst
12
README.rst
@ -34,24 +34,24 @@ Features
|
|||||||
* **Support for prompts and statuslines in many applications.** Originally
|
* **Support for prompts and statuslines in many applications.** Originally
|
||||||
created exclusively for vim statuslines, the project has evolved to
|
created exclusively for vim statuslines, the project has evolved to
|
||||||
provide statuslines in tmux and several WMs, and prompts for shells like
|
provide statuslines in tmux and several WMs, and prompts for shells like
|
||||||
bash/zsh and other applications. It's simple to write renderers for any
|
bash/zsh and other applications. It’s simple to write renderers for any
|
||||||
other applications that Powerline doesn't yet support.
|
other applications that Powerline doesn’t yet support.
|
||||||
* **Configuration and colorschemes written in JSON.** JSON is
|
* **Configuration and colorschemes written in JSON.** JSON is
|
||||||
a standardized, simple and easy to use file format that allows for easy
|
a standardized, simple and easy to use file format that allows for easy
|
||||||
user configuration across all of Powerline's supported applications.
|
user configuration across all of Powerline’s supported applications.
|
||||||
* **Fast and lightweight, with daemon support for even better performance.**
|
* **Fast and lightweight, with daemon support for even better performance.**
|
||||||
Although the code base spans a couple of thousand lines of code with no
|
Although the code base spans a couple of thousand lines of code with no
|
||||||
goal of "less than X lines of code", the main focus is on good performance
|
goal of “less than X lines of code”, the main focus is on good performance
|
||||||
and as little code as possible while still providing a rich set of
|
and as little code as possible while still providing a rich set of
|
||||||
features. The new daemon also ensures that only one Python instance is
|
features. The new daemon also ensures that only one Python instance is
|
||||||
launched for prompts and statuslines, which provides excellent
|
launched for prompts and statuslines, which provides excellent
|
||||||
performance.
|
performance.
|
||||||
|
|
||||||
*But I hate Python / I don't need shell prompts / this is just too much
|
*But I hate Python / I don’t need shell prompts / this is just too much
|
||||||
hassle for me / what happened to the original vim-powerline project / …*
|
hassle for me / what happened to the original vim-powerline project / …*
|
||||||
|
|
||||||
You should check out some of the Powerline derivatives. The most lightweight
|
You should check out some of the Powerline derivatives. The most lightweight
|
||||||
and feature-rich alternative is currently Bailey Ling's `vim-airline
|
and feature-rich alternative is currently Bailey Ling’s `vim-airline
|
||||||
<https://github.com/bling/vim-airline>`_ project.
|
<https://github.com/bling/vim-airline>`_ project.
|
||||||
|
|
||||||
------
|
------
|
||||||
|
@ -7,13 +7,20 @@ import socket
|
|||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from posix import environ
|
from posix import environ
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
|
# XXX Hack for importing powerline modules to work.
|
||||||
|
sys.path.pop(0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from powerline.lib.encoding import get_preferred_output_encoding
|
||||||
|
except ImportError:
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))))
|
||||||
|
from powerline.lib.encoding import get_preferred_output_encoding
|
||||||
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print('Must provide at least one argument.', file=sys.stderr)
|
print('Must provide at least one argument.', file=sys.stderr)
|
||||||
@ -51,7 +58,7 @@ except Exception:
|
|||||||
args = ['powerline-render'] + sys.argv[1:]
|
args = ['powerline-render'] + sys.argv[1:]
|
||||||
os.execvp('powerline-render', args)
|
os.execvp('powerline-render', args)
|
||||||
|
|
||||||
fenc = getpreferredencoding() or 'utf-8'
|
fenc = get_preferred_output_encoding()
|
||||||
|
|
||||||
|
|
||||||
def tobytes(s):
|
def tobytes(s):
|
||||||
|
@ -11,7 +11,7 @@ sys.path.insert(0, os.path.abspath(os.getcwd()))
|
|||||||
extensions = ['powerline_autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
extensions = ['powerline_autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
project = u'Powerline'
|
project = 'Powerline'
|
||||||
version = 'beta'
|
version = 'beta'
|
||||||
release = 'beta'
|
release = 'beta'
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ['_build']
|
||||||
@ -22,7 +22,7 @@ html_show_copyright = False
|
|||||||
|
|
||||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||||
|
|
||||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
if not on_rtd: # only import and set the theme if we’re building docs locally
|
||||||
try:
|
try:
|
||||||
import sphinx_rtd_theme
|
import sphinx_rtd_theme
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
@ -87,29 +87,29 @@ You can move the segment dictionaries around to change the segment
|
|||||||
positions, or remove the entire dictionary to remove the segment from the
|
positions, or remove the entire dictionary to remove the segment from the
|
||||||
prompt or statusline.
|
prompt or statusline.
|
||||||
|
|
||||||
.. note:: It's essential that the contents of all your configuration files
|
.. note:: It’s essential that the contents of all your configuration files
|
||||||
is valid JSON! It's strongly recommended that you run your configuration
|
is valid JSON! It’s strongly recommended that you run your configuration
|
||||||
files through ``jsonlint`` after changing them.
|
files through ``jsonlint`` after changing them.
|
||||||
|
|
||||||
Some segments need a user configuration to work properly. Here's a couple of
|
Some segments need a user configuration to work properly. Here’s a couple of
|
||||||
segments that you may want to customize right away:
|
segments that you may want to customize right away:
|
||||||
|
|
||||||
**E-mail alert segment**
|
**E-mail alert segment**
|
||||||
You have to set your username and password (and possibly server/port)
|
You have to set your username and password (and possibly server/port)
|
||||||
for the e-mail alert segment. If you're using GMail it's recommended
|
for the e-mail alert segment. If you’re using GMail it’s recommended
|
||||||
that you `generate an application-specific password
|
that you `generate an application-specific password
|
||||||
<https://accounts.google.com/IssuedAuthSubTokens>`_ for this purpose.
|
<https://accounts.google.com/IssuedAuthSubTokens>`_ for this purpose.
|
||||||
|
|
||||||
Open a theme file, scroll down to the ``email_imap_alert`` segment and
|
Open a theme file, scroll down to the ``email_imap_alert`` segment and
|
||||||
set your ``username`` and ``password``. The server defaults to GMail's
|
set your ``username`` and ``password``. The server defaults to GMail’s
|
||||||
IMAP server, but you can set the server/port by adding a ``server`` and
|
IMAP server, but you can set the server/port by adding a ``server`` and
|
||||||
a ``port`` argument.
|
a ``port`` argument.
|
||||||
**Weather segment**
|
**Weather segment**
|
||||||
The weather segment will try to find your location using a GeoIP lookup,
|
The weather segment will try to find your location using a GeoIP lookup,
|
||||||
so unless you're on a VPN you probably won't have to change the location
|
so unless you’re on a VPN you probably won’t have to change the location
|
||||||
query.
|
query.
|
||||||
|
|
||||||
If you want to change the location query or the temperature unit you'll
|
If you want to change the location query or the temperature unit you’ll
|
||||||
have to update the segment arguments. Open a theme file, scroll down to
|
have to update the segment arguments. Open a theme file, scroll down to
|
||||||
the weather segment and update it to include unit/location query
|
the weather segment and update it to include unit/location query
|
||||||
arguments:
|
arguments:
|
||||||
|
@ -177,7 +177,7 @@ Color definitions
|
|||||||
* A cterm color index.
|
* A cterm color index.
|
||||||
* A list with a cterm color index and a hex color string (e.g. ``[123,
|
* A list with a cterm color index and a hex color string (e.g. ``[123,
|
||||||
"aabbcc"]``). This is useful for colorschemes that use colors that
|
"aabbcc"]``). This is useful for colorschemes that use colors that
|
||||||
aren't available in color terminals.
|
aren’t available in color terminals.
|
||||||
|
|
||||||
``gradients``
|
``gradients``
|
||||||
Gradient definitions, consisting of a dict where the key is the name of the
|
Gradient definitions, consisting of a dict where the key is the name of the
|
||||||
@ -236,7 +236,7 @@ override those from each previous file. It is required that either
|
|||||||
|
|
||||||
``mode_translations``
|
``mode_translations``
|
||||||
Mode-specific highlighting for extensions that support it (e.g. the vim
|
Mode-specific highlighting for extensions that support it (e.g. the vim
|
||||||
extension). It's an easy way of changing a color in a specific mode.
|
extension). It’s an easy way of changing a color in a specific mode.
|
||||||
Consists of a dict where the key is the mode and the value is a dict
|
Consists of a dict where the key is the mode and the value is a dict
|
||||||
with the following options:
|
with the following options:
|
||||||
|
|
||||||
@ -308,7 +308,7 @@ ascii Theme without any unicode characters at all
|
|||||||
|
|
||||||
``dividers``
|
``dividers``
|
||||||
Defines the dividers used in all Powerline extensions. This option
|
Defines the dividers used in all Powerline extensions. This option
|
||||||
should usually only be changed if you don't have a patched font, or if
|
should usually only be changed if you don’t have a patched font, or if
|
||||||
you use a font patched with the legacy font patcher.
|
you use a font patched with the legacy font patcher.
|
||||||
|
|
||||||
The ``hard`` dividers are used to divide segments with different
|
The ``hard`` dividers are used to divide segments with different
|
||||||
|
@ -15,7 +15,7 @@ always absolute.
|
|||||||
|
|
||||||
Segments are regular Python functions, and they may accept arguments. All
|
Segments are regular Python functions, and they may accept arguments. All
|
||||||
arguments should have a default value which will be used for themes that
|
arguments should have a default value which will be used for themes that
|
||||||
don't provide an ``args`` dict.
|
don’t provide an ``args`` dict.
|
||||||
|
|
||||||
More information is available in :ref:`Writing segments <dev-segments>` section.
|
More information is available in :ref:`Writing segments <dev-segments>` section.
|
||||||
|
|
||||||
|
@ -2,5 +2,56 @@
|
|||||||
Common segments
|
Common segments
|
||||||
***************
|
***************
|
||||||
|
|
||||||
.. automodule:: powerline.segments.common
|
VCS submodule
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.vcs
|
||||||
|
:members:
|
||||||
|
|
||||||
|
System properties
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.sys
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Network
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.net
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Current environment
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.env
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Battery
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.bat
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Weather
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.wthr
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Date and time
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.time
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Mail
|
||||||
|
====
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.mail
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Media players
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. automodule:: powerline.segments.common.players
|
||||||
:members:
|
:members:
|
||||||
|
@ -371,6 +371,10 @@ Vim ``segment_info`` argument is a dictionary with the following keys:
|
|||||||
``mode``
|
``mode``
|
||||||
Current mode.
|
Current mode.
|
||||||
|
|
||||||
|
``encoding``
|
||||||
|
Value of ``&encoding`` from the time when powerline was initialized. It
|
||||||
|
should be used to convert return values.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Your segment generally should not assume that it is run for the current
|
Your segment generally should not assume that it is run for the current
|
||||||
window, current buffer or current tabpage. “Current window” and “current
|
window, current buffer or current tabpage. “Current window” and “current
|
||||||
|
@ -90,8 +90,8 @@ Patched fonts
|
|||||||
This method is the fallback method and works for every terminal, with the
|
This method is the fallback method and works for every terminal, with the
|
||||||
exception of :ref:`rxvt-unicode <tips-and-tricks-urxvt>`.
|
exception of :ref:`rxvt-unicode <tips-and-tricks-urxvt>`.
|
||||||
|
|
||||||
Download the font of your choice from `powerline-fonts`_. If you can't find
|
Download the font of your choice from `powerline-fonts`_. If you can’t find
|
||||||
your preferred font in the `powerline-fonts`_ repo, you'll have to patch your
|
your preferred font in the `powerline-fonts`_ repo, you’ll have to patch your
|
||||||
own font instead.
|
own font instead.
|
||||||
|
|
||||||
.. _powerline-fonts: https://github.com/Lokaltog/powerline-fonts
|
.. _powerline-fonts: https://github.com/Lokaltog/powerline-fonts
|
||||||
|
@ -10,7 +10,7 @@ automatically do most of the configuration for you.
|
|||||||
* `Arch Linux (AUR), Python 3 version <https://aur.archlinux.org/packages/python-powerline-git/>`_
|
* `Arch Linux (AUR), Python 3 version <https://aur.archlinux.org/packages/python-powerline-git/>`_
|
||||||
* Gentoo Live ebuild in `raiagent <https://github.com/leycec/raiagent>`_ overlay
|
* Gentoo Live ebuild in `raiagent <https://github.com/leycec/raiagent>`_ overlay
|
||||||
|
|
||||||
If you're running a distribution without an official package you'll have to
|
If you’re running a distribution without an official package you’ll have to
|
||||||
follow the installation guide below:
|
follow the installation guide below:
|
||||||
|
|
||||||
1. Install Python 3.2+ or Python 2.6+ with ``pip``. This step is
|
1. Install Python 3.2+ or Python 2.6+ with ``pip``. This step is
|
||||||
@ -34,8 +34,8 @@ Fonts installation
|
|||||||
Fontconfig
|
Fontconfig
|
||||||
----------
|
----------
|
||||||
|
|
||||||
This method only works on Linux. It's the recommended method if your
|
This method only works on Linux. It’s the recommended method if your
|
||||||
terminal emulator supports it as you don't have to patch any fonts, and it
|
terminal emulator supports it as you don’t have to patch any fonts, and it
|
||||||
generally works well with any coding font.
|
generally works well with any coding font.
|
||||||
|
|
||||||
#. Download the latest version of the symbol font and fontconfig file::
|
#. Download the latest version of the symbol font and fontconfig file::
|
||||||
@ -54,16 +54,16 @@ generally works well with any coding font.
|
|||||||
fc-cache -vf ~/.fonts/
|
fc-cache -vf ~/.fonts/
|
||||||
|
|
||||||
#. Install the fontconfig file. For newer versions of fontconfig the config
|
#. Install the fontconfig file. For newer versions of fontconfig the config
|
||||||
path is ``~/.config/fontconfig/conf.d/``, for older versions it's
|
path is ``~/.config/fontconfig/conf.d/``, for older versions it’s
|
||||||
``~/.fonts.conf.d/``::
|
``~/.fonts.conf.d/``::
|
||||||
|
|
||||||
mv 10-powerline-symbols.conf ~/.config/fontconfig/conf.d/
|
mv 10-powerline-symbols.conf ~/.config/fontconfig/conf.d/
|
||||||
|
|
||||||
If you can't see the custom symbols, please close all instances of your
|
If you can’t see the custom symbols, please close all instances of your
|
||||||
terminal emulator. You may need to restart X for the changes to take
|
terminal emulator. You may need to restart X for the changes to take
|
||||||
effect.
|
effect.
|
||||||
|
|
||||||
If you *still* can't see the custom symbols, double-check that you have
|
If you *still* can’t see the custom symbols, double-check that you have
|
||||||
installed the font to a valid X font path, and that you have installed the
|
installed the font to a valid X font path, and that you have installed the
|
||||||
fontconfig file to a valid fontconfig path. Alternatively try to install
|
fontconfig file to a valid fontconfig path. Alternatively try to install
|
||||||
a :ref:`patched font <installation-patched-fonts>`.
|
a :ref:`patched font <installation-patched-fonts>`.
|
||||||
@ -87,9 +87,9 @@ After installing the patched font you need to update Gvim or your terminal
|
|||||||
emulator to use the patched font. The correct font usually ends with *for
|
emulator to use the patched font. The correct font usually ends with *for
|
||||||
Powerline*.
|
Powerline*.
|
||||||
|
|
||||||
If you can't see the custom symbols, please close all instances of your
|
If you can’t see the custom symbols, please close all instances of your
|
||||||
terminal emulator. You may need to restart X for the changes to take
|
terminal emulator. You may need to restart X for the changes to take
|
||||||
effect.
|
effect.
|
||||||
|
|
||||||
If you *still* can't see the custom symbols, double-check that you have
|
If you *still* can’t see the custom symbols, double-check that you have
|
||||||
installed the font to a valid X font path.
|
installed the font to a valid X font path.
|
||||||
|
@ -45,7 +45,7 @@ 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 if you're using MacVim you need to install it using the following command::
|
but if you’re using MacVim you need to install it using the following command::
|
||||||
|
|
||||||
brew install macvim --env-std --override-system-vim
|
brew install macvim --env-std --override-system-vim
|
||||||
|
|
||||||
|
@ -20,24 +20,24 @@ Features
|
|||||||
* **Support for prompts and statuslines in many applications.** Originally
|
* **Support for prompts and statuslines in many applications.** Originally
|
||||||
created exclusively for vim statuslines, the project has evolved to
|
created exclusively for vim statuslines, the project has evolved to
|
||||||
provide statuslines in tmux and several WMs, and prompts for shells like
|
provide statuslines in tmux and several WMs, and prompts for shells like
|
||||||
bash/zsh and other applications. It's simple to write renderers for any
|
bash/zsh and other applications. It’s simple to write renderers for any
|
||||||
other applications that Powerline doesn't yet support.
|
other applications that Powerline doesn’t yet support.
|
||||||
* **Configuration and colorschemes written in JSON.** JSON is
|
* **Configuration and colorschemes written in JSON.** JSON is
|
||||||
a standardized, simple and easy to use file format that allows for easy
|
a standardized, simple and easy to use file format that allows for easy
|
||||||
user configuration across all of Powerline's supported applications.
|
user configuration across all of Powerline’s supported applications.
|
||||||
* **Fast and lightweight, with daemon support for even better performance.**
|
* **Fast and lightweight, with daemon support for even better performance.**
|
||||||
Although the code base spans a couple of thousand lines of code with no
|
Although the code base spans a couple of thousand lines of code with no
|
||||||
goal of "less than X lines of code", the main focus is on good performance
|
goal of “less than X lines of code”, the main focus is on good performance
|
||||||
and as little code as possible while still providing a rich set of
|
and as little code as possible while still providing a rich set of
|
||||||
features. The new daemon also ensures that only one Python instance is
|
features. The new daemon also ensures that only one Python instance is
|
||||||
launched for prompts and statuslines, which provides excellent
|
launched for prompts and statuslines, which provides excellent
|
||||||
performance.
|
performance.
|
||||||
|
|
||||||
*But I hate Python / I don't need shell prompts / this is just too much
|
*But I hate Python / I don’t need shell prompts / this is just too much
|
||||||
hassle for me / what happened to the original vim-powerline project / …*
|
hassle for me / what happened to the original vim-powerline project / …*
|
||||||
|
|
||||||
You should check out some of the Powerline derivatives. The most lightweight
|
You should check out some of the Powerline derivatives. The most lightweight
|
||||||
and feature-rich alternative is currently Bailey Ling's `vim-airline
|
and feature-rich alternative is currently Bailey Ling’s `vim-airline
|
||||||
<https://github.com/bling/vim-airline>`_ project.
|
<https://github.com/bling/vim-airline>`_ project.
|
||||||
|
|
||||||
Screenshots
|
Screenshots
|
||||||
|
@ -47,7 +47,7 @@ In the ``~/.Xdefaults`` add the following::
|
|||||||
I noticed that Source Code Pro has the glyphs there already, but the pixel size
|
I noticed that Source Code Pro has the glyphs there already, but the pixel size
|
||||||
of the fonts play a role in whether or not the > or the < separators showing up
|
of the fonts play a role in whether or not the > or the < separators showing up
|
||||||
or not. Using font size 12, glyphs on the right hand side of the powerline are
|
or not. Using font size 12, glyphs on the right hand side of the powerline are
|
||||||
present, but the ones on the left don't. Pixel size 14, brings the reverse
|
present, but the ones on the left don’t. Pixel size 14, brings the reverse
|
||||||
problem. Font size 13 seems to work just fine.
|
problem. Font size 13 seems to work just fine.
|
||||||
|
|
||||||
Reloading powerline after update
|
Reloading powerline after update
|
||||||
|
@ -13,14 +13,14 @@ System-specific issues
|
|||||||
Common issues
|
Common issues
|
||||||
=============
|
=============
|
||||||
|
|
||||||
I'm using tmux and Powerline looks like crap, what's wrong?
|
I’m using tmux and Powerline looks like crap, what’s wrong?
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
|
|
||||||
* You need to tell tmux that it has 256-color capabilities. Add this to your
|
* You need to tell tmux that it has 256-color capabilities. Add this to your
|
||||||
:file:`.tmux.conf` to solve this issue::
|
:file:`.tmux.conf` to solve this issue::
|
||||||
|
|
||||||
set -g default-terminal "screen-256color"
|
set -g default-terminal "screen-256color"
|
||||||
* If you're using iTerm2, make sure that you have enabled the setting
|
* If you’re using iTerm2, make sure that you have enabled the setting
|
||||||
:guilabel:`Set locale variables automatically` in :menuselection:`Profiles -->
|
:guilabel:`Set locale variables automatically` in :menuselection:`Profiles -->
|
||||||
Terminal --> Environment`.
|
Terminal --> Environment`.
|
||||||
* Make sure tmux knows that terminal it is running in support 256 colors. You
|
* Make sure tmux knows that terminal it is running in support 256 colors. You
|
||||||
|
@ -2,19 +2,27 @@
|
|||||||
Troubleshooting on Linux
|
Troubleshooting on Linux
|
||||||
************************
|
************************
|
||||||
|
|
||||||
I can't see any fancy symbols, what's wrong?
|
I can’t see any fancy symbols, what’s wrong?
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
* Make sure that you've configured gvim or your terminal emulator to use
|
* Make sure that you’ve configured gvim or your terminal emulator to use
|
||||||
a patched font.
|
a patched 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 distro's
|
a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distro’s
|
||||||
documentation for information about setting these variables correctly.
|
documentation for information about setting these variables correctly.
|
||||||
* Make sure that vim is compiled with the ``--with-features=big`` flag.
|
* Make sure that vim is compiled with the ``--with-features=big`` flag.
|
||||||
* If you're using rxvt-unicode, make sure that it's compiled with the
|
* If you’re using rxvt-unicode make sure that it’s compiled with the
|
||||||
``--enable-unicode3`` flag.
|
``--enable-unicode3`` flag.
|
||||||
|
* If you’re using xterm make sure you have told it to work with unicode. You may
|
||||||
|
need ``-u8`` command-line argument, ``uxterm`` shell wrapper that is usually
|
||||||
|
shipped with xterm for this or ``xterm*utf8`` property set to ``1`` or ``2``
|
||||||
|
in ``~/.Xresources`` (applied with ``xrdb``). Note that in case ``uxterm`` is
|
||||||
|
used configuration is done via ``uxterm*…`` properties and not ``xterm*…``.
|
||||||
|
|
||||||
The fancy symbols look a bit blurry or "off"!
|
In any case the only absolute requirement is launching xterm with UTF-8
|
||||||
|
locale.
|
||||||
|
|
||||||
|
The fancy symbols look a bit blurry or “off”!
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
* Make sure that you have patched all variants of your font (i.e. both the
|
* Make sure that you have patched all variants of your font (i.e. both the
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
Troubleshooting on OS X
|
Troubleshooting on OS X
|
||||||
***********************
|
***********************
|
||||||
|
|
||||||
I can't see any fancy symbols, what's wrong?
|
I can’t see any fancy symbols, what’s wrong?
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
* If you're using iTerm2, please update to `this revision
|
* If you’re 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.
|
||||||
* 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 distro's
|
a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distro’s
|
||||||
documentation for information about setting these variables correctly.
|
documentation for information about setting these variables correctly.
|
||||||
|
|
||||||
The colors look weird in the default OS X Terminal app!
|
The colors look weird in the default OS X Terminal app!
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
|
|
||||||
* The arrows may have the wrong colors if you have changed the "minimum
|
* The arrows may have the wrong colors if you have changed the “minimum
|
||||||
contrast" slider in the color tab of your OS X settings.
|
contrast” slider in the color tab of your OS X settings.
|
||||||
* The default OS X Terminal app is known to have some issues with the
|
* The default OS X Terminal app is known to have some issues with the
|
||||||
Powerline colors. Please use another terminal emulator. iTerm2 should work
|
Powerline colors. Please use another terminal emulator. iTerm2 should work
|
||||||
fine.
|
fine.
|
||||||
@ -24,8 +24,8 @@ The colors look weird in the default OS X Terminal app!
|
|||||||
The colors look weird in iTerm2!
|
The colors look weird in iTerm2!
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
* The arrows may have the wrong colors if you have changed the "minimum
|
* The arrows may have the wrong colors if you have changed the “minimum
|
||||||
contrast" slider in the color tab of your OS X settings.
|
contrast” slider in the color tab of your OS X settings.
|
||||||
* Please disable background transparency to resolve this issue.
|
* Please disable background transparency to resolve this issue.
|
||||||
|
|
||||||
Statusline is getting wrapped to the next line in iTerm2
|
Statusline is getting wrapped to the next line in iTerm2
|
||||||
|
@ -12,11 +12,11 @@ The vim plugin requires a vim version with Python support compiled in. You
|
|||||||
can check if your vim supports Python by running ``vim --version | grep
|
can check if your vim supports Python by running ``vim --version | grep
|
||||||
+python``.
|
+python``.
|
||||||
|
|
||||||
If your vim version doesn't have support for Python, you'll have to compile
|
If your vim version doesn’t have support for Python, you’ll have to compile
|
||||||
it with the ``--enable-pythoninterp`` flag (``--enable-python3interp`` if
|
it with the ``--enable-pythoninterp`` flag (``--enable-python3interp`` if
|
||||||
you want Python 3 support instead). Note that this also requires the related
|
you want Python 3 support instead). Note that this also requires the related
|
||||||
Python headers to be installed on your system. Please consult your
|
Python headers to be installed on your system. Please consult your
|
||||||
distribution's documentation for details on how to compile and install
|
distribution’s documentation for details on how to compile and install
|
||||||
packages.
|
packages.
|
||||||
|
|
||||||
Vim version 7.4 or newer is recommended for performance reasons, but Powerline
|
Vim version 7.4 or newer is recommended for performance reasons, but Powerline
|
||||||
|
@ -25,7 +25,7 @@ directory:
|
|||||||
|
|
||||||
set rtp+={repository_root}/powerline/bindings/vim
|
set rtp+={repository_root}/powerline/bindings/vim
|
||||||
|
|
||||||
If you're using pathogen and don't want Powerline functionality in any other
|
If you’re using pathogen and don’t want Powerline functionality in any other
|
||||||
applications, simply add Powerline as a bundle and point the path above to the
|
applications, simply add Powerline as a bundle and point the path above to the
|
||||||
Powerline bundle directory, e.g.
|
Powerline bundle directory, e.g.
|
||||||
``~/.vim/bundle/powerline/powerline/bindings/vim``.
|
``~/.vim/bundle/powerline/powerline/bindings/vim``.
|
||||||
|
@ -5,7 +5,6 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
from threading import Lock, Event
|
from threading import Lock, Event
|
||||||
|
|
||||||
from powerline.colorscheme import Colorscheme
|
from powerline.colorscheme import Colorscheme
|
||||||
@ -13,6 +12,11 @@ from powerline.lib.config import ConfigLoader
|
|||||||
from powerline.lib.unicode import safe_unicode, FailedUnicode
|
from powerline.lib.unicode import safe_unicode, FailedUnicode
|
||||||
from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR
|
from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR
|
||||||
from powerline.lib import mergedicts
|
from powerline.lib import mergedicts
|
||||||
|
from powerline.lib.encoding import get_preferred_output_encoding
|
||||||
|
|
||||||
|
|
||||||
|
class NotInterceptedError(BaseException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _config_loader_condition(path):
|
def _config_loader_condition(path):
|
||||||
@ -413,7 +417,7 @@ class Powerline(object):
|
|||||||
self.setup_kwargs = {}
|
self.setup_kwargs = {}
|
||||||
self.imported_modules = set()
|
self.imported_modules = set()
|
||||||
|
|
||||||
get_encoding = staticmethod(getpreferredencoding)
|
get_encoding = staticmethod(get_preferred_output_encoding)
|
||||||
'''Get encoding used by the current application
|
'''Get encoding used by the current application
|
||||||
|
|
||||||
Usually returns encoding of the current locale.
|
Usually returns encoding of the current locale.
|
||||||
@ -744,16 +748,12 @@ class Powerline(object):
|
|||||||
self.update_renderer()
|
self.update_renderer()
|
||||||
return self.renderer.render(*args, **kwargs)
|
return self.renderer.render(*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
exc = e
|
||||||
try:
|
try:
|
||||||
self.exception('Failed to render: {0}', str(e))
|
self.exception('Failed to render: {0}', str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Updates e variable to new value, masking previous one.
|
exc = e
|
||||||
# Normally it is the same exception (due to raise in case pl is
|
return FailedUnicode(safe_unicode(exc))
|
||||||
# unset), but it may also show error in logger. Note that latter
|
|
||||||
# is not logged by logger for obvious reasons, thus this also
|
|
||||||
# prevents us from seeing logger traceback.
|
|
||||||
pass
|
|
||||||
return FailedUnicode(safe_unicode(e))
|
|
||||||
|
|
||||||
def render_above_lines(self, *args, **kwargs):
|
def render_above_lines(self, *args, **kwargs):
|
||||||
'''Like .render(), but for ``self.renderer.render_above_lines()``
|
'''Like .render(), but for ``self.renderer.render_above_lines()``
|
||||||
@ -763,16 +763,12 @@ class Powerline(object):
|
|||||||
for line in self.renderer.render_above_lines(*args, **kwargs):
|
for line in self.renderer.render_above_lines(*args, **kwargs):
|
||||||
yield line
|
yield line
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
exc = e
|
||||||
try:
|
try:
|
||||||
self.exception('Failed to render: {0}', str(e))
|
self.exception('Failed to render: {0}', str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Updates e variable to new value, masking previous one.
|
exc = e
|
||||||
# Normally it is the same exception (due to raise in case pl is
|
yield FailedUnicode(safe_unicode(exc))
|
||||||
# unset), but it may also show error in logger. Note that latter
|
|
||||||
# is not logged by logger for obvious reasons, thus this also
|
|
||||||
# prevents us from seeing logger traceback.
|
|
||||||
pass
|
|
||||||
yield FailedUnicode(safe_unicode(e))
|
|
||||||
|
|
||||||
def setup(self, *args, **kwargs):
|
def setup(self, *args, **kwargs):
|
||||||
'''Setup the environment to use powerline.
|
'''Setup the environment to use powerline.
|
||||||
|
@ -33,7 +33,7 @@ def read_to_log(pl, client):
|
|||||||
while True:
|
while True:
|
||||||
start_time = monotonic()
|
start_time = monotonic()
|
||||||
s = powerline.render(side='right')
|
s = powerline.render(side='right')
|
||||||
request = "powerline_widget:set_markup('" + s.replace('\\', '\\\\').replace("'", "\\'") + "')\n"
|
request = 'powerline_widget:set_markup(\'' + s.replace('\\', '\\\\').replace('\'', '\\\'') + '\')\n'
|
||||||
client = Popen(['awesome-client'], shell=False, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
client = Popen(['awesome-client'], shell=False, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
||||||
client.stdin.write(request.encode('utf-8'))
|
client.stdin.write(request.encode('utf-8'))
|
||||||
client.stdin.close()
|
client.stdin.close()
|
||||||
|
@ -31,7 +31,7 @@ _powerline_init_tmux_support() {
|
|||||||
# TMUX variable may be unset to create new tmux session inside this one
|
# TMUX variable may be unset to create new tmux session inside this one
|
||||||
_POWERLINE_TMUX="$TMUX"
|
_POWERLINE_TMUX="$TMUX"
|
||||||
|
|
||||||
trap "_powerline_tmux_set_columns" WINCH
|
trap '_powerline_tmux_set_columns' WINCH
|
||||||
_powerline_tmux_set_columns
|
_powerline_tmux_set_columns
|
||||||
|
|
||||||
test "x$PROMPT_COMMAND" != "x${PROMPT_COMMAND/_powerline_tmux_set_pwd}" ||
|
test "x$PROMPT_COMMAND" != "x${PROMPT_COMMAND/_powerline_tmux_set_pwd}" ||
|
||||||
|
@ -5,13 +5,12 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
|
|
||||||
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, PowerlineLogger, finish_common_config
|
||||||
from powerline.lib.shell import which
|
from powerline.lib.shell import which
|
||||||
from powerline.bindings.tmux import TmuxVersionInfo, run_tmux_command, get_tmux_version
|
from powerline.bindings.tmux import TmuxVersionInfo, run_tmux_command, get_tmux_version
|
||||||
|
from powerline.lib.encoding import get_preferred_output_encoding
|
||||||
|
|
||||||
|
|
||||||
CONFIG_FILE_NAME = re.compile(r'powerline_tmux_(?P<major>\d+)\.(?P<minor>\d+)(?P<suffix>[a-z]+)?(?:_(?P<mod>plus|minus))?\.conf')
|
CONFIG_FILE_NAME = re.compile(r'powerline_tmux_(?P<major>\d+)\.(?P<minor>\d+)(?P<suffix>[a-z]+)?(?:_(?P<mod>plus|minus))?\.conf')
|
||||||
@ -84,7 +83,7 @@ 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(getpreferredencoding(), config['common'])
|
common_config = finish_common_config(get_preferred_output_encoding(), config['common'])
|
||||||
logger = create_logger(common_config)
|
logger = create_logger(common_config)
|
||||||
return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config')
|
return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config')
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from powerline import Powerline as PowerlineCore
|
|||||||
|
|
||||||
|
|
||||||
class Powerline(base._TextBox):
|
class Powerline(base._TextBox):
|
||||||
def __init__(self, timeout=2, text=" ", width=bar.CALCULATED, **config):
|
def __init__(self, timeout=2, text=' ', width=bar.CALCULATED, **config):
|
||||||
base._TextBox.__init__(self, text, width, **config)
|
base._TextBox.__init__(self, text, width, **config)
|
||||||
self.timeout_add(timeout, self.update)
|
self.timeout_add(timeout, self.update)
|
||||||
self.powerline = PowerlineCore(ext='wm', renderer_module='pango_markup')
|
self.powerline = PowerlineCore(ext='wm', renderer_module='pango_markup')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
if-shell 'test -z "$POWERLINE_CONFIG_COMMAND"' 'set-environment -g POWERLINE_CONFIG_COMMAND powerline-config'
|
if-shell 'test -z "$POWERLINE_CONFIG_COMMAND"' 'set-environment -g POWERLINE_CONFIG_COMMAND powerline-config'
|
||||||
|
|
||||||
# Don't version-check for this core functionality -- anything too old to
|
# Don’t version-check for this core functionality -- anything too old to
|
||||||
# support these options likely won't work well with powerline
|
# support these options likely won’t work well with powerline
|
||||||
set -g status on
|
set -g status on
|
||||||
set -g status-utf8 on
|
set -g status-utf8 on
|
||||||
set -g status-interval 2
|
set -g status-interval 2
|
||||||
|
@ -7,43 +7,127 @@ import codecs
|
|||||||
try:
|
try:
|
||||||
import vim
|
import vim
|
||||||
except ImportError:
|
except ImportError:
|
||||||
vim = {}
|
vim = object()
|
||||||
|
|
||||||
if not hasattr(vim, 'bindeval'):
|
from powerline.lib.unicode import unicode
|
||||||
import json
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
vim_encoding = vim.eval('&encoding')
|
||||||
|
except AttributeError:
|
||||||
|
vim_encoding = 'utf-8'
|
||||||
|
|
||||||
|
|
||||||
|
python_to_vim_types = {
|
||||||
|
unicode: (
|
||||||
|
lambda o: b'\'' + (o.translate({
|
||||||
|
ord('\''): '\'\'',
|
||||||
|
}).encode(vim_encoding)) + b'\''
|
||||||
|
),
|
||||||
|
bytes: (lambda o: b'\'' + o.replace(b'\'', b'\'\'') + b'\''),
|
||||||
|
int: (str if str is bytes else (lambda o: unicode(o).encode('ascii'))),
|
||||||
|
}
|
||||||
|
python_to_vim_types[float] = python_to_vim_types[int]
|
||||||
|
|
||||||
|
|
||||||
|
def python_to_vim(o):
|
||||||
|
return python_to_vim_types[type(o)](o)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3,):
|
||||||
|
def str_to_bytes(s):
|
||||||
|
return s
|
||||||
|
|
||||||
|
def unicode_eval(expr):
|
||||||
|
ret = vim.eval(expr)
|
||||||
|
return ret.decode(vim_encoding, 'powerline_vim_strtrans_error')
|
||||||
|
else:
|
||||||
|
def str_to_bytes(s):
|
||||||
|
return s.encode(vim_encoding)
|
||||||
|
|
||||||
|
def unicode_eval(expr):
|
||||||
|
return vim.eval(expr)
|
||||||
|
|
||||||
|
|
||||||
|
def safe_bytes_eval(expr):
|
||||||
|
return bytes(bytearray((
|
||||||
|
int(chunk) for chunk in (
|
||||||
|
vim.eval(
|
||||||
|
b'substitute(' + expr + b', ' +
|
||||||
|
b'\'^.*$\', \'\\=join(map(range(len(submatch(0))), ' +
|
||||||
|
b'"char2nr(submatch(0)[v:val])"))\', "")'
|
||||||
|
).split()
|
||||||
|
)
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
|
def eval_bytes(expr):
|
||||||
|
try:
|
||||||
|
return str_to_bytes(vim.eval(expr))
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return safe_bytes_eval(expr)
|
||||||
|
|
||||||
|
|
||||||
|
def eval_unicode(expr):
|
||||||
|
try:
|
||||||
|
return unicode_eval(expr)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return safe_bytes_eval(expr).decode(vim_encoding, 'powerline_vim_strtrans_error')
|
||||||
|
|
||||||
|
|
||||||
if hasattr(vim, 'bindeval'):
|
if hasattr(vim, 'bindeval'):
|
||||||
|
rettype_func = {
|
||||||
|
None: lambda f: f,
|
||||||
|
'unicode': (
|
||||||
|
lambda f: (
|
||||||
|
lambda *args, **kwargs: (
|
||||||
|
f(*args, **kwargs).decode(
|
||||||
|
vim_encoding, 'powerline_vim_strtrans_error'
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
rettype_func['int'] = rettype_func['bytes'] = rettype_func[None]
|
||||||
|
rettype_func['str'] = rettype_func['bytes'] if str is bytes else rettype_func['unicode']
|
||||||
|
|
||||||
def vim_get_func(f, rettype=None):
|
def vim_get_func(f, rettype=None):
|
||||||
'''Return a vim function binding.'''
|
'''Return a vim function binding.'''
|
||||||
try:
|
try:
|
||||||
func = vim.bindeval('function("' + f + '")')
|
func = vim.bindeval('function("' + f + '")')
|
||||||
if sys.version_info >= (3,) and rettype is str:
|
|
||||||
return (lambda *args, **kwargs: func(*args, **kwargs).decode('utf-8', errors='replace'))
|
|
||||||
return func
|
|
||||||
except vim.error:
|
except vim.error:
|
||||||
return None
|
return None
|
||||||
|
else:
|
||||||
|
return rettype_func[rettype](func)
|
||||||
else:
|
else:
|
||||||
|
rettype_eval = {
|
||||||
|
None: getattr(vim, 'eval', None),
|
||||||
|
'int': lambda expr: int(vim.eval(expr)),
|
||||||
|
'bytes': eval_bytes,
|
||||||
|
'unicode': eval_unicode,
|
||||||
|
}
|
||||||
|
rettype_eval['str'] = rettype_eval[None]
|
||||||
|
|
||||||
class VimFunc(object):
|
class VimFunc(object):
|
||||||
'''Evaluate a vim function using vim.eval().
|
'''Evaluate a vim function using vim.eval().
|
||||||
|
|
||||||
This is a fallback class for older vim versions.
|
This is a fallback class for older vim versions.
|
||||||
'''
|
'''
|
||||||
__slots__ = ('f', 'rettype')
|
__slots__ = ('f', 'eval')
|
||||||
|
|
||||||
def __init__(self, f, rettype=None):
|
def __init__(self, f, rettype=None):
|
||||||
self.f = f
|
self.f = f.encode('utf-8')
|
||||||
self.rettype = rettype
|
self.eval = rettype_eval[rettype]
|
||||||
|
|
||||||
def __call__(self, *args):
|
def __call__(self, *args):
|
||||||
r = vim.eval(self.f + '(' + json.dumps(args)[1:-1] + ')')
|
return self.eval(self.f + b'(' + (b','.join((
|
||||||
if self.rettype:
|
python_to_vim(o) for o in args
|
||||||
return self.rettype(r)
|
))) + b')')
|
||||||
return r
|
|
||||||
|
|
||||||
vim_get_func = VimFunc
|
vim_get_func = VimFunc
|
||||||
|
|
||||||
|
|
||||||
|
if type(vim) is object:
|
||||||
|
vim_get_func = lambda *args, **kwargs: None
|
||||||
|
|
||||||
|
|
||||||
_getbufvar = vim_get_func('getbufvar')
|
_getbufvar = vim_get_func('getbufvar')
|
||||||
|
|
||||||
|
|
||||||
@ -52,7 +136,10 @@ _getbufvar = vim_get_func('getbufvar')
|
|||||||
if hasattr(vim, 'vvars') and vim.vvars['version'] > 703:
|
if hasattr(vim, 'vvars') and vim.vvars['version'] > 703:
|
||||||
_vim_to_python_types = {
|
_vim_to_python_types = {
|
||||||
getattr(vim, 'Dictionary', None) or type(vim.bindeval('{}')):
|
getattr(vim, 'Dictionary', None) or type(vim.bindeval('{}')):
|
||||||
lambda value: dict(((key, _vim_to_python(value[key])) for key in value.keys())),
|
lambda value: dict((
|
||||||
|
(_vim_to_python(k), _vim_to_python(v))
|
||||||
|
for k, v in value.items()
|
||||||
|
)),
|
||||||
getattr(vim, 'List', None) or type(vim.bindeval('[]')):
|
getattr(vim, 'List', None) or type(vim.bindeval('[]')):
|
||||||
lambda value: [_vim_to_python(item) for item in value],
|
lambda value: [_vim_to_python(item) for item in value],
|
||||||
getattr(vim, 'Function', None) or type(vim.bindeval('function("mode")')):
|
getattr(vim, 'Function', None) or type(vim.bindeval('function("mode")')):
|
||||||
@ -74,7 +161,7 @@ else:
|
|||||||
list: (lambda value: [_vim_to_python(i) for i in value]),
|
list: (lambda value: [_vim_to_python(i) for i in value]),
|
||||||
}
|
}
|
||||||
|
|
||||||
_vim_exists = vim_get_func('exists', rettype=int)
|
_vim_exists = vim_get_func('exists', rettype='int')
|
||||||
|
|
||||||
def vim_getvar(varname):
|
def vim_getvar(varname):
|
||||||
varname = 'g:' + varname
|
varname = 'g:' + varname
|
||||||
@ -102,7 +189,7 @@ else:
|
|||||||
if sys.version_info < (3,):
|
if sys.version_info < (3,):
|
||||||
getbufvar = _getbufvar
|
getbufvar = _getbufvar
|
||||||
else:
|
else:
|
||||||
_vim_to_python_types[bytes] = lambda value: value.decode('utf-8')
|
_vim_to_python_types[bytes] = lambda value: value.decode(vim_encoding)
|
||||||
|
|
||||||
def getbufvar(*args):
|
def getbufvar(*args):
|
||||||
return _vim_to_python(_getbufvar(*args))
|
return _vim_to_python(_getbufvar(*args))
|
||||||
@ -133,7 +220,7 @@ else:
|
|||||||
|
|
||||||
def vim_setoption(option, value):
|
def vim_setoption(option, value):
|
||||||
vim.command('let &g:{option} = {value}'.format(
|
vim.command('let &g:{option} = {value}'.format(
|
||||||
option=option, value=json.encode(value)))
|
option=option, value=python_to_vim(value)))
|
||||||
|
|
||||||
|
|
||||||
if hasattr(vim, 'tabpages'):
|
if hasattr(vim, 'tabpages'):
|
||||||
@ -256,29 +343,27 @@ class VimEnviron(object):
|
|||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3,):
|
if sys.version_info < (3,):
|
||||||
def buffer_name(buf):
|
def buffer_name(segment_info):
|
||||||
return buf.name
|
return segment_info['buffer'].name
|
||||||
else:
|
else:
|
||||||
vim_bufname = vim_get_func('bufname')
|
vim_bufname = vim_get_func('bufname', rettype='bytes')
|
||||||
|
|
||||||
def buffer_name(buf):
|
def buffer_name(segment_info):
|
||||||
try:
|
try:
|
||||||
name = buf.name
|
name = segment_info['buffer'].name
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return vim_bufname(buf.number)
|
return vim_bufname(segment_info['bufnr'])
|
||||||
else:
|
else:
|
||||||
return name.encode('utf-8') if name else None
|
return name.encode(segment_info['encoding']) if name else None
|
||||||
|
|
||||||
|
|
||||||
vim_strtrans = vim_get_func('strtrans')
|
vim_strtrans = vim_get_func('strtrans', rettype='unicode')
|
||||||
|
|
||||||
|
|
||||||
def powerline_vim_strtrans_error(e):
|
def powerline_vim_strtrans_error(e):
|
||||||
if not isinstance(e, UnicodeDecodeError):
|
if not isinstance(e, UnicodeDecodeError):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
# Assuming &encoding is utf-8 strtrans should not return anything but ASCII
|
text = vim_strtrans(e.object[e.start:e.end])
|
||||||
# under current circumstances
|
|
||||||
text = vim_strtrans(e.object[e.start:e.end]).decode()
|
|
||||||
return (text, e.end)
|
return (text, e.end)
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,22 +61,24 @@ function s:rcmd(s)
|
|||||||
endfunction
|
endfunction
|
||||||
try
|
try
|
||||||
let s:can_replace_pyeval = !exists('g:powerline_pyeval')
|
let s:can_replace_pyeval = !exists('g:powerline_pyeval')
|
||||||
call s:rcmd("try:")
|
call s:rcmd('try:')
|
||||||
call s:rcmd(" powerline_appended_path = None")
|
call s:rcmd(' powerline_appended_path = None')
|
||||||
call s:rcmd(" try:")
|
call s:rcmd(' try:')
|
||||||
call s:rcmd(" ".s:import_cmd."")
|
call s:rcmd(' '.s:import_cmd.'')
|
||||||
call s:rcmd(" except ImportError:")
|
call s:rcmd(' except ImportError:')
|
||||||
call s:rcmd(" import sys, vim")
|
call s:rcmd(' import sys, vim')
|
||||||
call s:rcmd(" powerline_appended_path = vim.eval('expand(\"<sfile>:h:h:h:h:h\")')")
|
call s:rcmd(' powerline_appended_path = vim.eval("expand(\"<sfile>:h:h:h:h:h\")")')
|
||||||
call s:rcmd(" sys.path.append(powerline_appended_path)")
|
call s:rcmd(' sys.path.append(powerline_appended_path)')
|
||||||
call s:rcmd(" ".s:import_cmd."")
|
call s:rcmd(' '.s:import_cmd.'')
|
||||||
call s:rcmd(" import vim")
|
call s:rcmd(' import vim')
|
||||||
call s:rcmd(" VimPowerline().setup(pyeval=vim.eval('s:pyeval'), pycmd=vim.eval('s:pycmd'), can_replace_pyeval=int(vim.eval('s:can_replace_pyeval')))")
|
call s:rcmd(' powerline_instance = VimPowerline()')
|
||||||
call s:rcmd(" del VimPowerline")
|
call s:rcmd(' powerline_instance.setup(pyeval=vim.eval("s:pyeval"), pycmd=vim.eval("s:pycmd"), can_replace_pyeval=int(vim.eval("s:can_replace_pyeval")))')
|
||||||
call s:rcmd("except Exception:")
|
call s:rcmd(' del VimPowerline')
|
||||||
call s:rcmd(" import traceback, sys")
|
call s:rcmd(' del powerline_instance')
|
||||||
call s:rcmd(" traceback.print_exc(file=sys.stdout)")
|
call s:rcmd('except Exception:')
|
||||||
call s:rcmd(" raise")
|
call s:rcmd(' import traceback, sys')
|
||||||
|
call s:rcmd(' traceback.print_exc(file=sys.stdout)')
|
||||||
|
call s:rcmd(' raise')
|
||||||
execute s:pycmd s:pystr
|
execute s:pycmd s:pystr
|
||||||
unlet s:pystr
|
unlet s:pystr
|
||||||
let s:launched = 1
|
let s:launched = 1
|
||||||
@ -86,7 +88,7 @@ finally
|
|||||||
if !exists('s:launched')
|
if !exists('s:launched')
|
||||||
unlet s:pystr
|
unlet s:pystr
|
||||||
echohl ErrorMsg
|
echohl ErrorMsg
|
||||||
echomsg 'An error occurred while importing powerline package.'
|
echomsg 'An error occurred while importing powerline module.'
|
||||||
echomsg 'This could be caused by invalid sys.path setting,'
|
echomsg 'This could be caused by invalid sys.path setting,'
|
||||||
echomsg 'or by an incompatible Python version (powerline requires'
|
echomsg 'or by an incompatible Python version (powerline requires'
|
||||||
echomsg 'Python 2.6, 2.7 or 3.2 and later to work). Please consult'
|
echomsg 'Python 2.6, 2.7 or 3.2 and later to work). Please consult'
|
||||||
@ -97,48 +99,61 @@ finally
|
|||||||
echomsg 'should set g:powerline_pycmd to "py3" to make it load correctly.'
|
echomsg 'should set g:powerline_pycmd to "py3" to make it load correctly.'
|
||||||
endif
|
endif
|
||||||
echohl None
|
echohl None
|
||||||
call s:rcmd("def powerline_troubleshoot():")
|
call s:rcmd('def powerline_troubleshoot():')
|
||||||
call s:rcmd(" import sys")
|
call s:rcmd(' import sys')
|
||||||
call s:rcmd(" import vim")
|
call s:rcmd(' import vim')
|
||||||
call s:rcmd(" if sys.version_info < (2, 6):")
|
call s:rcmd(' if sys.version_info < (2, 6):')
|
||||||
call s:rcmd(" print('Too old python version: ' + sys.version + ' (first supported is 2.6)')")
|
call s:rcmd(' print("Too old python version: " + sys.version + " (first supported is 2.6)")')
|
||||||
call s:rcmd(" elif sys.version_info[0] == 3 and sys.version_info[1] < 2:")
|
call s:rcmd(' elif sys.version_info[0] == 3 and sys.version_info[1] < 2:')
|
||||||
call s:rcmd(" print('Too old python 3 version: ' + sys.version + ' (first supported is 3.2)')")
|
call s:rcmd(' print("Too old python 3 version: " + sys.version + " (first supported is 3.2)")')
|
||||||
call s:rcmd(" try:")
|
call s:rcmd(' try:')
|
||||||
call s:rcmd(" import powerline")
|
call s:rcmd(' import powerline')
|
||||||
call s:rcmd(" except ImportError:")
|
call s:rcmd(' except ImportError:')
|
||||||
call s:rcmd(" print('Unable to import powerline, is it installed?')")
|
call s:rcmd(' print("Unable to import powerline, is it installed?")')
|
||||||
call s:rcmd(" else:")
|
call s:rcmd(' else:')
|
||||||
call s:rcmd(" if not vim.eval('expand(\"<sfile>\")').startswith('/usr/'):")
|
call s:rcmd(' if not vim.eval(''expand("<sfile>")'').startswith("/usr/"):')
|
||||||
call s:rcmd(" import os")
|
call s:rcmd(' import os')
|
||||||
call s:rcmd(" powerline_dir = os.path.realpath(os.path.normpath(powerline.__file__))")
|
call s:rcmd(' powerline_dir = os.path.realpath(os.path.normpath(powerline.__file__))')
|
||||||
call s:rcmd(" powerline_dir = os.path.dirname(powerline.__file__)")
|
call s:rcmd(' powerline_dir = os.path.dirname(powerline.__file__)')
|
||||||
call s:rcmd(" this_dir = os.path.realpath(os.path.normpath(vim.eval('expand(\"<sfile>:p\")')))")
|
call s:rcmd(' this_dir = os.path.realpath(os.path.normpath(vim.eval(''expand("<sfile>:p")'')))')
|
||||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline/bindings/vim/plugin
|
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings/vim/plugin
|
||||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline/bindings/vim
|
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings/vim
|
||||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline/bindings
|
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings
|
||||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline
|
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline
|
||||||
call s:rcmd(" if os.path.basename(this_dir) != 'powerline':")
|
call s:rcmd(' if os.path.basename(this_dir) != "powerline":')
|
||||||
call s:rcmd(" print('Check your installation:')")
|
call s:rcmd(' print("Check your installation:")')
|
||||||
call s:rcmd(" print('this script is not in powerline[/bindings/vim/plugin] directory,')")
|
call s:rcmd(' print("this script is not in powerline[/bindings/vim/plugin] directory,")')
|
||||||
call s:rcmd(" print('neither it is installed system-wide')")
|
call s:rcmd(' print("neither it is installed system-wide")')
|
||||||
call s:rcmd(" real_powerline_dir = os.path.realpath(powerline_dir)")
|
call s:rcmd(' real_powerline_dir = os.path.realpath(powerline_dir)')
|
||||||
call s:rcmd(" real_this_dir = os.path.realpath(this_dir)")
|
call s:rcmd(' real_this_dir = os.path.realpath(this_dir)')
|
||||||
call s:rcmd(" this_dir_par = os.path.dirname(real_this_dir)")
|
call s:rcmd(' this_dir_par = os.path.dirname(real_this_dir)')
|
||||||
call s:rcmd(" powerline_appended_path = globals().get('powerline_appended_path')")
|
call s:rcmd(' powerline_appended_path = globals().get("powerline_appended_path")')
|
||||||
call s:rcmd(" if powerline_appended_path is not None and this_dir_par != powerline_appended_path:")
|
call s:rcmd(' if powerline_appended_path is not None and this_dir_par != powerline_appended_path:')
|
||||||
call s:rcmd(" print('Check your installation: this script is symlinked somewhere')")
|
call s:rcmd(' print("Check your installation: this script is symlinked somewhere")')
|
||||||
call s:rcmd(" print('where powerline is not present: {0!r} != {1!r}.'.format(")
|
call s:rcmd(' print("where powerline is not present: {0!r} != {1!r}.".format(')
|
||||||
call s:rcmd(" real_this_dir, powerline_appended_path))")
|
call s:rcmd(' real_this_dir, powerline_appended_path))')
|
||||||
call s:rcmd(" elif real_powerline_dir != real_this_dir:")
|
call s:rcmd(' elif real_powerline_dir != real_this_dir:')
|
||||||
call s:rcmd(" print('It appears that you have two powerline versions installed:')")
|
call s:rcmd(' print("It appears that you have two powerline versions installed:")')
|
||||||
call s:rcmd(" print('one in ' + real_powerline_dir + ', other in ' + real_this_dir + '.')")
|
call s:rcmd(' print("one in " + real_powerline_dir + ", other in " + real_this_dir + ".")')
|
||||||
call s:rcmd(" print('You should remove one of this. Check out troubleshooting section,')")
|
call s:rcmd(' print("You should remove one of this. Check out troubleshooting section,")')
|
||||||
call s:rcmd(" print('it contains some information about the alternatives.')")
|
call s:rcmd(' print("it contains some information about the alternatives.")')
|
||||||
call s:rcmd("try:")
|
call s:rcmd(' try:')
|
||||||
call s:rcmd(" powerline_troubleshoot()")
|
call s:rcmd(' from powerline.lint import check')
|
||||||
call s:rcmd("finally:")
|
call s:rcmd(' except ImportError:')
|
||||||
call s:rcmd(" del powerline_troubleshoot")
|
call s:rcmd(' print("Failed to import powerline.lint.check, cannot run powerline-lint")')
|
||||||
|
call s:rcmd(' else:')
|
||||||
|
call s:rcmd(' try:')
|
||||||
|
call s:rcmd(' paths = powerline_instance.get_config_paths()')
|
||||||
|
call s:rcmd(' except NameError:')
|
||||||
|
call s:rcmd(' pass')
|
||||||
|
call s:rcmd(' else:')
|
||||||
|
call s:rcmd(' from powerline.lint.markedjson.error import echoerr')
|
||||||
|
call s:rcmd(' ee = lambda *args, **kwargs: echoerr(*args, stream=sys.stdout, **kwargs)')
|
||||||
|
call s:rcmd(' check(paths=paths, echoerr=ee, require_ext="vim")')
|
||||||
|
call s:rcmd('try:')
|
||||||
|
call s:rcmd(' powerline_troubleshoot()')
|
||||||
|
call s:rcmd('finally:')
|
||||||
|
call s:rcmd(' del powerline_troubleshoot')
|
||||||
execute s:pycmd s:pystr
|
execute s:pycmd s:pystr
|
||||||
unlet s:pystr
|
unlet s:pystr
|
||||||
unlet s:pycmd
|
unlet s:pycmd
|
||||||
|
@ -10,6 +10,8 @@ import zsh
|
|||||||
from powerline.shell import ShellPowerline
|
from powerline.shell import ShellPowerline
|
||||||
from powerline.lib import parsedotval
|
from powerline.lib import parsedotval
|
||||||
from powerline.lib.unicode import unicode
|
from powerline.lib.unicode import unicode
|
||||||
|
from powerline.lib.encoding import (get_preferred_output_encoding,
|
||||||
|
get_preferred_environment_encoding)
|
||||||
|
|
||||||
|
|
||||||
used_powerlines = WeakValueDictionary()
|
used_powerlines = WeakValueDictionary()
|
||||||
@ -65,7 +67,7 @@ class Args(object):
|
|||||||
|
|
||||||
def string(s):
|
def string(s):
|
||||||
if type(s) is bytes:
|
if type(s) is bytes:
|
||||||
return s.decode('utf-8', 'replace')
|
return s.decode(get_preferred_environment_encoding(), 'replace')
|
||||||
else:
|
else:
|
||||||
return str(s)
|
return str(s)
|
||||||
|
|
||||||
@ -129,6 +131,7 @@ class Prompt(object):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
zsh.eval('_POWERLINE_PARSER_STATE="${(%):-%_}"')
|
zsh.eval('_POWERLINE_PARSER_STATE="${(%):-%_}"')
|
||||||
|
zsh.eval('_POWERLINE_SHORTENED_PATH="${(%):-%~}"')
|
||||||
segment_info = {
|
segment_info = {
|
||||||
'args': self.args,
|
'args': self.args,
|
||||||
'environ': environ,
|
'environ': environ,
|
||||||
@ -137,6 +140,8 @@ class Prompt(object):
|
|||||||
'parser_state': zsh.getvalue('_POWERLINE_PARSER_STATE'),
|
'parser_state': zsh.getvalue('_POWERLINE_PARSER_STATE'),
|
||||||
'shortened_path': zsh.getvalue('_POWERLINE_SHORTENED_PATH'),
|
'shortened_path': zsh.getvalue('_POWERLINE_SHORTENED_PATH'),
|
||||||
}
|
}
|
||||||
|
zsh.setvalue('_POWERLINE_PARSER_STATE', None)
|
||||||
|
zsh.setvalue('_POWERLINE_SHORTENED_PATH', None)
|
||||||
r = ''
|
r = ''
|
||||||
if self.above:
|
if self.above:
|
||||||
for line in self.powerline.render_above_lines(
|
for line in self.powerline.render_above_lines(
|
||||||
@ -151,9 +156,9 @@ class Prompt(object):
|
|||||||
)
|
)
|
||||||
if type(r) is not str:
|
if type(r) is not str:
|
||||||
if type(r) is bytes:
|
if type(r) is bytes:
|
||||||
return r.decode('utf-8')
|
return r.decode(get_preferred_output_encoding(), 'replace')
|
||||||
else:
|
else:
|
||||||
return r.encode('utf-8')
|
return r.encode(get_preferred_output_encoding(), 'replace')
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
@ -36,7 +36,7 @@ _powerline_init_tmux_support() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
chpwd_functions+=( _powerline_tmux_set_pwd )
|
chpwd_functions+=( _powerline_tmux_set_pwd )
|
||||||
trap "_powerline_tmux_set_columns" SIGWINCH
|
trap '_powerline_tmux_set_columns' SIGWINCH
|
||||||
_powerline_tmux_set_columns
|
_powerline_tmux_set_columns
|
||||||
_powerline_tmux_set_pwd
|
_powerline_tmux_set_pwd
|
||||||
fi
|
fi
|
||||||
@ -103,10 +103,6 @@ _powerline_set_jobnum() {
|
|||||||
_POWERLINE_JOBNUM=${(%):-%j}
|
_POWERLINE_JOBNUM=${(%):-%j}
|
||||||
}
|
}
|
||||||
|
|
||||||
_powerline_set_shortened_path() {
|
|
||||||
_POWERLINE_SHORTENED_PATH="${(%):-%~}"
|
|
||||||
}
|
|
||||||
|
|
||||||
_powerline_update_counter() {
|
_powerline_update_counter() {
|
||||||
zpython '_powerline.precmd()'
|
zpython '_powerline.precmd()'
|
||||||
}
|
}
|
||||||
@ -115,13 +111,11 @@ _powerline_setup_prompt() {
|
|||||||
emulate -L zsh
|
emulate -L zsh
|
||||||
|
|
||||||
for f in "${precmd_functions[@]}"; do
|
for f in "${precmd_functions[@]}"; do
|
||||||
if [[ "$f" = "_powerline_set_jobnum" ]]; then
|
if [[ "$f" = '_powerline_set_jobnum' ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
precmd_functions+=( _powerline_set_jobnum )
|
precmd_functions+=( _powerline_set_jobnum )
|
||||||
chpwd_functions+=( _powerline_set_shortened_path )
|
|
||||||
_powerline_set_shortened_path
|
|
||||||
|
|
||||||
VIRTUAL_ENV_DISABLE_PROMPT=1
|
VIRTUAL_ENV_DISABLE_PROMPT=1
|
||||||
|
|
||||||
@ -144,7 +138,7 @@ _powerline_setup_prompt() {
|
|||||||
add_args+=' --last_exit_code=$?'
|
add_args+=' --last_exit_code=$?'
|
||||||
add_args+=' --last_pipe_status="$pipestatus"'
|
add_args+=' --last_pipe_status="$pipestatus"'
|
||||||
add_args+=' --renderer_arg="client_id=$$"'
|
add_args+=' --renderer_arg="client_id=$$"'
|
||||||
add_args+=' --renderer_arg="shortened_path=$_POWERLINE_SHORTENED_PATH"'
|
add_args+=' --renderer_arg="shortened_path=${(%):-%~}"'
|
||||||
add_args+=' --jobnum=$_POWERLINE_JOBNUM'
|
add_args+=' --jobnum=$_POWERLINE_JOBNUM'
|
||||||
local new_args_2=' --renderer_arg="parser_state=${(%%):-%_}"'
|
local new_args_2=' --renderer_arg="parser_state=${(%%):-%_}"'
|
||||||
new_args_2+=' --renderer_arg="local_theme=continuation"'
|
new_args_2+=' --renderer_arg="local_theme=continuation"'
|
||||||
|
@ -29,13 +29,16 @@
|
|||||||
"before": ""
|
"before": ""
|
||||||
},
|
},
|
||||||
|
|
||||||
"powerline.segments.common.network_load": {
|
"powerline.segments.common.net.network_load": {
|
||||||
"args": {
|
"args": {
|
||||||
"recv_format": "DL {value:>8}",
|
"recv_format": "DL {value:>8}",
|
||||||
"sent_format": "UL {value:>8}"
|
"sent_format": "UL {value:>8}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.now_playing": {
|
"powerline.segments.common.net.hostname": {
|
||||||
|
"before": "H "
|
||||||
|
},
|
||||||
|
"powerline.segments.common.players.now_playing": {
|
||||||
"args": {
|
"args": {
|
||||||
"state_symbols": {
|
"state_symbols": {
|
||||||
"fallback": "",
|
"fallback": "",
|
||||||
@ -45,25 +48,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.battery": {
|
"powerline.segments.common.bat.battery": {
|
||||||
"args": {
|
"args": {
|
||||||
"full_heart": "O",
|
"full_heart": "O",
|
||||||
"empty_heart": "O"
|
"empty_heart": "O"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.uptime": {
|
"powerline.segments.common.sys.uptime": {
|
||||||
"before": "UP "
|
"before": "UP "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.email_imap_alert": {
|
"powerline.segments.common.mail.email_imap_alert": {
|
||||||
"before": "MAIL "
|
"before": "MAIL "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.virtualenv": {
|
"powerline.segments.common.env.virtualenv": {
|
||||||
"before": "(e) "
|
"before": "(e) "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.hostname": {
|
"powerline.segments.common.wthr.weather": {
|
||||||
"before": "H "
|
|
||||||
},
|
|
||||||
"powerline.segments.common.weather": {
|
|
||||||
"args": {
|
"args": {
|
||||||
"icons": {
|
"icons": {
|
||||||
"day": "DAY",
|
"day": "DAY",
|
||||||
@ -82,7 +82,7 @@
|
|||||||
"temp_format": "{temp:.0f} C"
|
"temp_format": "{temp:.0f} C"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.fuzzy_time": {
|
"powerline.segments.common.time.fuzzy_time": {
|
||||||
"args": {
|
"args": {
|
||||||
"unicode_text": false
|
"unicode_text": false
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"default_module": "powerline.segments.common",
|
|
||||||
"segments": {
|
"segments": {
|
||||||
"left": [
|
"left": [
|
||||||
{
|
{
|
||||||
"function": "virtualenv",
|
"function": "powerline.segments.common.env.virtualenv",
|
||||||
"priority": 10
|
"priority": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"default_module": "powerline.segments.common",
|
|
||||||
"segments": {
|
"segments": {
|
||||||
"left": [
|
"left": [
|
||||||
{
|
{
|
||||||
|
@ -28,13 +28,16 @@
|
|||||||
"before": "⌚ "
|
"before": "⌚ "
|
||||||
},
|
},
|
||||||
|
|
||||||
"powerline.segments.common.network_load": {
|
"powerline.segments.common.net.network_load": {
|
||||||
"args": {
|
"args": {
|
||||||
"recv_format": "⬇ {value:>8}",
|
"recv_format": "⬇ {value:>8}",
|
||||||
"sent_format": "⬆ {value:>8}"
|
"sent_format": "⬆ {value:>8}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.now_playing": {
|
"powerline.segments.common.net.hostname": {
|
||||||
|
"before": " "
|
||||||
|
},
|
||||||
|
"powerline.segments.common.players.now_playing": {
|
||||||
"args": {
|
"args": {
|
||||||
"state_symbols": {
|
"state_symbols": {
|
||||||
"fallback": "♫",
|
"fallback": "♫",
|
||||||
@ -44,25 +47,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.battery": {
|
"powerline.segments.common.bat.battery": {
|
||||||
"args": {
|
"args": {
|
||||||
"full_heart": "♥",
|
"full_heart": "♥",
|
||||||
"empty_heart": "♥"
|
"empty_heart": "♥"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.uptime": {
|
"powerline.segments.common.sys.uptime": {
|
||||||
"before": "⇑ "
|
"before": "⇑ "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.email_imap_alert": {
|
"powerline.segments.common.mail.email_imap_alert": {
|
||||||
"before": "✉ "
|
"before": "✉ "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.virtualenv": {
|
"powerline.segments.common.env.virtualenv": {
|
||||||
"before": "ⓔ "
|
"before": "ⓔ "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.hostname": {
|
"powerline.segments.common.wthr.weather": {
|
||||||
"before": " "
|
|
||||||
},
|
|
||||||
"powerline.segments.common.weather": {
|
|
||||||
"args": {
|
"args": {
|
||||||
"icons": {
|
"icons": {
|
||||||
"day": "〇",
|
"day": "〇",
|
||||||
@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.fuzzy_time": {
|
"powerline.segments.common.time.fuzzy_time": {
|
||||||
"args": {
|
"args": {
|
||||||
"unicode_text": true
|
"unicode_text": true
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
{
|
{
|
||||||
"default_module": "powerline.segments.common",
|
|
||||||
"segments": {
|
"segments": {
|
||||||
"left": [
|
"left": [
|
||||||
{
|
{
|
||||||
"function": "powerline.segments.shell.mode"
|
"function": "powerline.segments.shell.mode"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "hostname",
|
"function": "powerline.segments.common.net.hostname",
|
||||||
"priority": 10
|
"priority": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "user",
|
"function": "powerline.segments.common.env.user",
|
||||||
"priority": 30
|
"priority": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "virtualenv",
|
"function": "powerline.segments.common.env.virtualenv",
|
||||||
"priority": 50
|
"priority": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -32,7 +31,7 @@
|
|||||||
"priority": 10
|
"priority": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "branch",
|
"function": "powerline.segments.common.vcs.branch",
|
||||||
"priority": 40
|
"priority": 40
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
{
|
{
|
||||||
"default_module": "powerline.segments.common",
|
|
||||||
"segments": {
|
"segments": {
|
||||||
"left": [
|
"left": [
|
||||||
{
|
{
|
||||||
"function": "hostname",
|
"function": "powerline.segments.common.net.hostname",
|
||||||
"priority": 10
|
"priority": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "user",
|
"function": "powerline.segments.common.env.user",
|
||||||
"priority": 30
|
"priority": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "virtualenv",
|
"function": "powerline.segments.common.env.virtualenv",
|
||||||
"priority": 50
|
"priority": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "branch",
|
"function": "powerline.segments.common.vcs.branch",
|
||||||
"priority": 40
|
"priority": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
{
|
{
|
||||||
"default_module": "powerline.segments.common",
|
|
||||||
"segments": {
|
"segments": {
|
||||||
"right": [
|
"right": [
|
||||||
{
|
{
|
||||||
"function": "uptime",
|
"function": "powerline.segments.common.sys.uptime",
|
||||||
"priority": 50
|
"priority": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "system_load",
|
"function": "powerline.segments.common.sys.system_load",
|
||||||
"priority": 50
|
"priority": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "date"
|
"function": "powerline.segments.common.time.date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "date",
|
"function": "powerline.segments.common.time.date",
|
||||||
"name": "time",
|
"name": "time",
|
||||||
"args": {
|
"args": {
|
||||||
"format": "%H:%M",
|
"format": "%H:%M",
|
||||||
@ -22,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "hostname"
|
"function": "powerline.segments.common.net.hostname"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,16 @@
|
|||||||
"before": "⌚ "
|
"before": "⌚ "
|
||||||
},
|
},
|
||||||
|
|
||||||
"powerline.segments.common.network_load": {
|
"powerline.segments.common.net.network_load": {
|
||||||
"args": {
|
"args": {
|
||||||
"recv_format": "⬇ {value:>8}",
|
"recv_format": "⬇ {value:>8}",
|
||||||
"sent_format": "⬆ {value:>8}"
|
"sent_format": "⬆ {value:>8}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.now_playing": {
|
"powerline.segments.common.net.hostname": {
|
||||||
|
"before": "⌂ "
|
||||||
|
},
|
||||||
|
"powerline.segments.common.players.now_playing": {
|
||||||
"args": {
|
"args": {
|
||||||
"state_symbols": {
|
"state_symbols": {
|
||||||
"fallback": "♫",
|
"fallback": "♫",
|
||||||
@ -44,25 +47,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.battery": {
|
"powerline.segments.common.bat.battery": {
|
||||||
"args": {
|
"args": {
|
||||||
"full_heart": "♥",
|
"full_heart": "♥",
|
||||||
"empty_heart": "♥"
|
"empty_heart": "♥"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.uptime": {
|
"powerline.segments.common.sys.uptime": {
|
||||||
"before": "⇑ "
|
"before": "⇑ "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.email_imap_alert": {
|
"powerline.segments.common.mail.email_imap_alert": {
|
||||||
"before": "✉ "
|
"before": "✉ "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.virtualenv": {
|
"powerline.segments.common.env.virtualenv": {
|
||||||
"before": "ⓔ "
|
"before": "ⓔ "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.hostname": {
|
"powerline.segments.common.wthr.weather": {
|
||||||
"before": "⌂ "
|
|
||||||
},
|
|
||||||
"powerline.segments.common.weather": {
|
|
||||||
"args": {
|
"args": {
|
||||||
"icons": {
|
"icons": {
|
||||||
"day": "〇",
|
"day": "〇",
|
||||||
@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.fuzzy_time": {
|
"powerline.segments.common.time.fuzzy_time": {
|
||||||
"args": {
|
"args": {
|
||||||
"unicode_text": true
|
"unicode_text": true
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,16 @@
|
|||||||
"before": ""
|
"before": ""
|
||||||
},
|
},
|
||||||
|
|
||||||
"powerline.segments.common.network_load": {
|
"powerline.segments.common.net.network_load": {
|
||||||
"args": {
|
"args": {
|
||||||
"recv_format": "⇓ {value:>8}",
|
"recv_format": "⇓ {value:>8}",
|
||||||
"sent_format": "⇑ {value:>8}"
|
"sent_format": "⇑ {value:>8}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.now_playing": {
|
"powerline.segments.common.net.hostname": {
|
||||||
|
"before": "⌂ "
|
||||||
|
},
|
||||||
|
"powerline.segments.common.players.now_playing": {
|
||||||
"args": {
|
"args": {
|
||||||
"state_symbols": {
|
"state_symbols": {
|
||||||
"fallback": "♫",
|
"fallback": "♫",
|
||||||
@ -44,25 +47,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.battery": {
|
"powerline.segments.common.bat.battery": {
|
||||||
"args": {
|
"args": {
|
||||||
"full_heart": "♥",
|
"full_heart": "♥",
|
||||||
"empty_heart": "♥"
|
"empty_heart": "♥"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.uptime": {
|
"powerline.segments.common.sys.uptime": {
|
||||||
"before": "↑ "
|
"before": "↑ "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.email_imap_alert": {
|
"powerline.segments.common.mail.email_imap_alert": {
|
||||||
"before": "MAIL "
|
"before": "MAIL "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.virtualenv": {
|
"powerline.segments.common.env.virtualenv": {
|
||||||
"before": "(e) "
|
"before": "(e) "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.hostname": {
|
"powerline.segments.common.wthr.weather": {
|
||||||
"before": "⌂ "
|
|
||||||
},
|
|
||||||
"powerline.segments.common.weather": {
|
|
||||||
"args": {
|
"args": {
|
||||||
"icons": {
|
"icons": {
|
||||||
"day": "DAY",
|
"day": "DAY",
|
||||||
@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.fuzzy_time": {
|
"powerline.segments.common.time.fuzzy_time": {
|
||||||
"args": {
|
"args": {
|
||||||
"unicode_text": true
|
"unicode_text": true
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,16 @@
|
|||||||
"before": ""
|
"before": ""
|
||||||
},
|
},
|
||||||
|
|
||||||
"powerline.segments.common.network_load": {
|
"powerline.segments.common.net.network_load": {
|
||||||
"args": {
|
"args": {
|
||||||
"recv_format": "⇓{value:>8}",
|
"recv_format": "⇓{value:>8}",
|
||||||
"sent_format": "⇑{value:>8}"
|
"sent_format": "⇑{value:>8}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.now_playing": {
|
"powerline.segments.common.net.hostname": {
|
||||||
|
"before": "⌂"
|
||||||
|
},
|
||||||
|
"powerline.segments.common.players.now_playing": {
|
||||||
"args": {
|
"args": {
|
||||||
"state_symbols": {
|
"state_symbols": {
|
||||||
"fallback": "♫",
|
"fallback": "♫",
|
||||||
@ -45,25 +48,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.battery": {
|
"powerline.segments.common.bat.battery": {
|
||||||
"args": {
|
"args": {
|
||||||
"full_heart": "♥",
|
"full_heart": "♥",
|
||||||
"empty_heart": "♥"
|
"empty_heart": "♥"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.uptime": {
|
"powerline.segments.common.sys.uptime": {
|
||||||
"before": "↑"
|
"before": "↑"
|
||||||
},
|
},
|
||||||
"powerline.segments.common.email_imap_alert": {
|
"powerline.segments.common.mail.email_imap_alert": {
|
||||||
"before": "M "
|
"before": "M "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.virtualenv": {
|
"powerline.segments.common.env.virtualenv": {
|
||||||
"before": "E "
|
"before": "E "
|
||||||
},
|
},
|
||||||
"powerline.segments.common.hostname": {
|
"powerline.segments.common.wthr.weather": {
|
||||||
"before": "⌂"
|
|
||||||
},
|
|
||||||
"powerline.segments.common.weather": {
|
|
||||||
"args": {
|
"args": {
|
||||||
"icons": {
|
"icons": {
|
||||||
"day": "D",
|
"day": "D",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"powerline.segments.common.fuzzy_time": {
|
"powerline.segments.common.time.fuzzy_time": {
|
||||||
"args": {
|
"args": {
|
||||||
"unicode_text": true
|
"unicode_text": true
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
{
|
{
|
||||||
"default_module": "powerline.segments.common",
|
|
||||||
"segments": {
|
"segments": {
|
||||||
"right": [
|
"right": [
|
||||||
{
|
{
|
||||||
"function": "weather",
|
"function": "powerline.segments.common.wthr.weather",
|
||||||
"priority": 50
|
"priority": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "date"
|
"function": "powerline.segments.common.time.date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "date",
|
"function": "powerline.segments.common.time.date",
|
||||||
"name": "time",
|
"name": "time",
|
||||||
"args": {
|
"args": {
|
||||||
"format": "%H:%M",
|
"format": "%H:%M",
|
||||||
@ -18,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function": "email_imap_alert",
|
"function": "powerline.segments.common.mail.email_imap_alert",
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
"args": {
|
"args": {
|
||||||
"username": "",
|
"username": "",
|
||||||
|
@ -24,10 +24,11 @@ class RewriteResult(object):
|
|||||||
|
|
||||||
|
|
||||||
class IPythonPowerline(Powerline):
|
class IPythonPowerline(Powerline):
|
||||||
def init(self):
|
def init(self, **kwargs):
|
||||||
super(IPythonPowerline, self).init(
|
super(IPythonPowerline, self).init(
|
||||||
'ipython',
|
'ipython',
|
||||||
use_daemon_threads=True
|
use_daemon_threads=True,
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_config_paths(self):
|
def get_config_paths(self):
|
||||||
|
@ -24,25 +24,25 @@ def print_cycles(objects, outstream=sys.stdout, show_progress=False):
|
|||||||
'''
|
'''
|
||||||
def print_path(path):
|
def print_path(path):
|
||||||
for i, step in enumerate(path):
|
for i, step in enumerate(path):
|
||||||
# next "wraps around"
|
# next “wraps around”
|
||||||
next = path[(i + 1) % len(path)]
|
next = path[(i + 1) % len(path)]
|
||||||
|
|
||||||
outstream.write(" %s -- " % str(type(step)))
|
outstream.write(' %s -- ' % str(type(step)))
|
||||||
written = False
|
written = False
|
||||||
if isinstance(step, dict):
|
if isinstance(step, dict):
|
||||||
for key, val in step.items():
|
for key, val in step.items():
|
||||||
if val is next:
|
if val is next:
|
||||||
outstream.write("[%s]" % repr(key))
|
outstream.write('[%s]' % repr(key))
|
||||||
written = True
|
written = True
|
||||||
break
|
break
|
||||||
if key is next:
|
if key is next:
|
||||||
outstream.write("[key] = %s" % repr(val))
|
outstream.write('[key] = %s' % repr(val))
|
||||||
written = True
|
written = True
|
||||||
break
|
break
|
||||||
elif isinstance(step, (list, tuple)):
|
elif isinstance(step, (list, tuple)):
|
||||||
for i, item in enumerate(step):
|
for i, item in enumerate(step):
|
||||||
if item is next:
|
if item is next:
|
||||||
outstream.write("[%d]" % i)
|
outstream.write('[%d]' % i)
|
||||||
written = True
|
written = True
|
||||||
elif getattr(type(step), '__getattribute__', None) in (object.__getattribute__, type.__getattribute__):
|
elif getattr(type(step), '__getattribute__', None) in (object.__getattribute__, type.__getattribute__):
|
||||||
for attr in chain(dir(step), getattr(step, '__dict__', ())):
|
for attr in chain(dir(step), getattr(step, '__dict__', ())):
|
||||||
@ -55,18 +55,18 @@ def print_cycles(objects, outstream=sys.stdout, show_progress=False):
|
|||||||
break
|
break
|
||||||
if not written:
|
if not written:
|
||||||
outstream.write(repr(step))
|
outstream.write(repr(step))
|
||||||
outstream.write(" ->\n")
|
outstream.write(' ->\n')
|
||||||
outstream.write("\n")
|
outstream.write('\n')
|
||||||
|
|
||||||
def recurse(obj, start, all, current_path):
|
def recurse(obj, start, all, current_path):
|
||||||
if show_progress:
|
if show_progress:
|
||||||
outstream.write("%d\r" % len(all))
|
outstream.write('%d\r' % len(all))
|
||||||
|
|
||||||
all[id(obj)] = None
|
all[id(obj)] = None
|
||||||
|
|
||||||
referents = gc.get_referents(obj)
|
referents = gc.get_referents(obj)
|
||||||
for referent in referents:
|
for referent in referents:
|
||||||
# If we've found our way back to the start, this is
|
# If we’ve found our way back to the start, this is
|
||||||
# a cycle, so print it out
|
# a cycle, so print it out
|
||||||
if referent is start:
|
if referent is start:
|
||||||
try:
|
try:
|
||||||
@ -78,13 +78,13 @@ def print_cycles(objects, outstream=sys.stdout, show_progress=False):
|
|||||||
outstream.write('Cyclic reference: %i\n' % id(referent))
|
outstream.write('Cyclic reference: %i\n' % id(referent))
|
||||||
print_path(current_path)
|
print_path(current_path)
|
||||||
|
|
||||||
# Don't go back through the original list of objects, or
|
# Don’t go back through the original list of objects, or
|
||||||
# through temporary references to the object, since those
|
# through temporary references to the object, since those
|
||||||
# are just an artifact of the cycle detector itself.
|
# are just an artifact of the cycle detector itself.
|
||||||
elif referent is objects or isinstance(referent, FrameType):
|
elif referent is objects or isinstance(referent, FrameType):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# We haven't seen this object before, so recurse
|
# We haven’t seen this object before, so recurse
|
||||||
elif id(referent) not in all:
|
elif id(referent) not in all:
|
||||||
recurse(referent, start, all, current_path + (obj,))
|
recurse(referent, start, all, current_path + (obj,))
|
||||||
|
|
||||||
|
73
powerline/lib/encoding.py
Normal file
73
powerline/lib/encoding.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
|
||||||
|
'''Encodings support
|
||||||
|
|
||||||
|
This is the only module from which functions obtaining encoding should be
|
||||||
|
exported. Note: you should always care about errors= argument since it is not
|
||||||
|
guaranteed that encoding returned by some function can encode/decode given
|
||||||
|
string.
|
||||||
|
|
||||||
|
All functions in this module must always return a valid encoding. Most of them
|
||||||
|
are not thread-safe.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import locale
|
||||||
|
|
||||||
|
|
||||||
|
def get_preferred_file_name_encoding():
|
||||||
|
'''Get preferred file name encoding
|
||||||
|
'''
|
||||||
|
return (
|
||||||
|
sys.getfilesystemencoding()
|
||||||
|
or locale.getpreferredencoding()
|
||||||
|
or 'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_preferred_file_contents_encoding():
|
||||||
|
'''Get encoding preferred for file contents
|
||||||
|
'''
|
||||||
|
return (
|
||||||
|
locale.getpreferredencoding()
|
||||||
|
or 'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_preferred_output_encoding():
|
||||||
|
'''Get encoding that should be used for printing strings
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Falls back to ASCII, so that output is most likely to be displayed
|
||||||
|
correctly.
|
||||||
|
'''
|
||||||
|
return (
|
||||||
|
locale.getlocale(locale.LC_MESSAGES)[1]
|
||||||
|
or locale.getdefaultlocale()[1]
|
||||||
|
or 'ascii'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_preferred_input_encoding():
|
||||||
|
'''Get encoding that should be used for reading shell command output
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Falls back to latin1 so that function is less likely to throw as decoded
|
||||||
|
output is primary searched for ASCII values.
|
||||||
|
'''
|
||||||
|
return (
|
||||||
|
locale.getlocale(locale.LC_MESSAGES)[1]
|
||||||
|
or locale.getdefaultlocale()[1]
|
||||||
|
or 'latin1'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_preferred_environment_encoding():
|
||||||
|
'''Get encoding that should be used for decoding environment variables
|
||||||
|
'''
|
||||||
|
return (
|
||||||
|
locale.getpreferredencoding()
|
||||||
|
or 'utf-8'
|
||||||
|
)
|
@ -9,6 +9,8 @@ import struct
|
|||||||
|
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
from powerline.lib.encoding import get_preferred_file_name_encoding
|
||||||
|
|
||||||
|
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
@ -39,27 +41,27 @@ def load_inotify():
|
|||||||
if not name:
|
if not name:
|
||||||
raise INotifyError('Cannot find C library')
|
raise INotifyError('Cannot find C library')
|
||||||
libc = ctypes.CDLL(name, use_errno=True)
|
libc = ctypes.CDLL(name, use_errno=True)
|
||||||
for function in ("inotify_add_watch", "inotify_init1", "inotify_rm_watch"):
|
for function in ('inotify_add_watch', 'inotify_init1', 'inotify_rm_watch'):
|
||||||
if not hasattr(libc, function):
|
if not hasattr(libc, function):
|
||||||
raise INotifyError('libc is too old')
|
raise INotifyError('libc is too old')
|
||||||
# inotify_init1()
|
# inotify_init1()
|
||||||
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, use_errno=True)
|
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, use_errno=True)
|
||||||
init1 = prototype(('inotify_init1', libc), ((1, "flags", 0),))
|
init1 = prototype(('inotify_init1', libc), ((1, 'flags', 0),))
|
||||||
|
|
||||||
# inotify_add_watch()
|
# inotify_add_watch()
|
||||||
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32, use_errno=True)
|
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32, use_errno=True)
|
||||||
add_watch = prototype(('inotify_add_watch', libc), (
|
add_watch = prototype(('inotify_add_watch', libc), (
|
||||||
(1, "fd"), (1, "pathname"), (1, "mask")))
|
(1, 'fd'), (1, 'pathname'), (1, 'mask')))
|
||||||
|
|
||||||
# inotify_rm_watch()
|
# inotify_rm_watch()
|
||||||
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, use_errno=True)
|
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, use_errno=True)
|
||||||
rm_watch = prototype(('inotify_rm_watch', libc), (
|
rm_watch = prototype(('inotify_rm_watch', libc), (
|
||||||
(1, "fd"), (1, "wd")))
|
(1, 'fd'), (1, 'wd')))
|
||||||
|
|
||||||
# read()
|
# read()
|
||||||
prototype = ctypes.CFUNCTYPE(ctypes.c_ssize_t, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t, use_errno=True)
|
prototype = ctypes.CFUNCTYPE(ctypes.c_ssize_t, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t, use_errno=True)
|
||||||
read = prototype(('read', libc), (
|
read = prototype(('read', libc), (
|
||||||
(1, "fd"), (1, "buf"), (1, "count")))
|
(1, 'fd'), (1, 'buf'), (1, 'count')))
|
||||||
_inotify = (init1, add_watch, rm_watch, read)
|
_inotify = (init1, add_watch, rm_watch, read)
|
||||||
return _inotify
|
return _inotify
|
||||||
|
|
||||||
@ -121,10 +123,8 @@ class INotify(object):
|
|||||||
raise INotifyError(os.strerror(ctypes.get_errno()))
|
raise INotifyError(os.strerror(ctypes.get_errno()))
|
||||||
|
|
||||||
self._buf = ctypes.create_string_buffer(5000)
|
self._buf = ctypes.create_string_buffer(5000)
|
||||||
self.fenc = sys.getfilesystemencoding() or 'utf-8'
|
self.fenc = get_preferred_file_name_encoding()
|
||||||
self.hdr = struct.Struct(b'iIII')
|
self.hdr = struct.Struct(b'iIII')
|
||||||
if self.fenc == 'ascii':
|
|
||||||
self.fenc = 'utf-8'
|
|
||||||
# We keep a reference to os to prevent it from being deleted
|
# We keep a reference to os to prevent it from being deleted
|
||||||
# during interpreter shutdown, which would lead to errors in the
|
# during interpreter shutdown, which would lead to errors in the
|
||||||
# __del__ method
|
# __del__ method
|
||||||
@ -176,7 +176,7 @@ class INotify(object):
|
|||||||
pos += self.hdr.size
|
pos += self.hdr.size
|
||||||
name = None
|
name = None
|
||||||
if get_name:
|
if get_name:
|
||||||
name = raw[pos:pos + name_len].rstrip(b'\0').decode(self.fenc)
|
name = raw[pos:pos + name_len].rstrip(b'\0')
|
||||||
pos += name_len
|
pos += name_len
|
||||||
self.process_event(wd, mask, cookie, name)
|
self.process_event(wd, mask, cookie, name)
|
||||||
|
|
||||||
|
@ -6,3 +6,13 @@ import os
|
|||||||
|
|
||||||
def realpath(path):
|
def realpath(path):
|
||||||
return os.path.abspath(os.path.realpath(path))
|
return os.path.abspath(os.path.realpath(path))
|
||||||
|
|
||||||
|
|
||||||
|
def join(*components):
|
||||||
|
if any((isinstance(p, bytes) for p in components)):
|
||||||
|
return os.path.join(*[
|
||||||
|
p if isinstance(p, bytes) else p.encode('ascii')
|
||||||
|
for p in components
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
return os.path.join(*components)
|
||||||
|
@ -5,9 +5,10 @@ import sys
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from locale import getlocale, getdefaultlocale, LC_MESSAGES
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
from powerline.lib.encoding import get_preferred_input_encoding
|
||||||
|
|
||||||
|
|
||||||
if sys.platform.startswith('win32'):
|
if sys.platform.startswith('win32'):
|
||||||
# Prevent windows from launching consoles when calling commands
|
# Prevent windows from launching consoles when calling commands
|
||||||
@ -15,10 +16,6 @@ if sys.platform.startswith('win32'):
|
|||||||
Popen = partial(Popen, creationflags=0x08000000)
|
Popen = partial(Popen, creationflags=0x08000000)
|
||||||
|
|
||||||
|
|
||||||
def _get_shell_encoding():
|
|
||||||
return getlocale(LC_MESSAGES)[1] or getdefaultlocale()[1] or 'utf-8'
|
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(pl, cmd, stdin=None):
|
def run_cmd(pl, cmd, stdin=None):
|
||||||
'''Run command and return its stdout, stripped
|
'''Run command and return its stdout, stripped
|
||||||
|
|
||||||
@ -38,7 +35,7 @@ def run_cmd(pl, cmd, stdin=None):
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
stdout, err = p.communicate(stdin)
|
stdout, err = p.communicate(stdin)
|
||||||
stdout = stdout.decode(_get_shell_encoding())
|
stdout = stdout.decode(get_preferred_input_encoding())
|
||||||
return stdout.strip()
|
return stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +53,7 @@ def readlines(cmd, cwd):
|
|||||||
Working directory of the command which will be run.
|
Working directory of the command which will be run.
|
||||||
'''
|
'''
|
||||||
p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd)
|
p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd)
|
||||||
encoding = _get_shell_encoding()
|
encoding = get_preferred_input_encoding()
|
||||||
p.stderr.close()
|
p.stderr.close()
|
||||||
with p.stdout:
|
with p.stdout:
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
@ -69,15 +66,14 @@ except ImportError:
|
|||||||
# shutil.which was added in python-3.3. Here is what was added:
|
# shutil.which was added in python-3.3. Here is what was added:
|
||||||
# Lib/shutil.py, commit 5abe28a9c8fe701ba19b1db5190863384e96c798
|
# Lib/shutil.py, commit 5abe28a9c8fe701ba19b1db5190863384e96c798
|
||||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||||
"""Given a command, mode, and a PATH string, return the path which
|
'''Given a command, mode, and a PATH string, return the path which
|
||||||
conforms to the given mode on the PATH, or None if there is no such
|
conforms to the given mode on the PATH, or None if there is no such
|
||||||
file.
|
file.
|
||||||
|
|
||||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
``mode`` defaults to os.F_OK | os.X_OK. ``path`` defaults to the result
|
||||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
of ``os.environ.get('PATH')``, or can be overridden with a custom search
|
||||||
path.
|
path.
|
||||||
|
'''
|
||||||
"""
|
|
||||||
# Check that a given file can be accessed with the correct mode.
|
# Check that a given file can be accessed with the correct mode.
|
||||||
# Additionally check that `file` is not a directory, as on Windows
|
# Additionally check that `file` is not a directory, as on Windows
|
||||||
# directories pass the os.access check.
|
# directories pass the os.access check.
|
||||||
@ -88,7 +84,7 @@ except ImportError:
|
|||||||
and not os.path.isdir(fn)
|
and not os.path.isdir(fn)
|
||||||
)
|
)
|
||||||
|
|
||||||
# If we're given a path with a directory part, look it up directly rather
|
# If we’re given a path with a directory part, look it up directly rather
|
||||||
# than referring to PATH directories. This includes checking relative to the
|
# than referring to PATH directories. This includes checking relative to the
|
||||||
# current directory, e.g. ./script
|
# current directory, e.g. ./script
|
||||||
if os.path.dirname(cmd):
|
if os.path.dirname(cmd):
|
||||||
@ -97,20 +93,20 @@ except ImportError:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = os.environ.get("PATH", os.defpath)
|
path = os.environ.get('PATH', os.defpath)
|
||||||
if not path:
|
if not path:
|
||||||
return None
|
return None
|
||||||
path = path.split(os.pathsep)
|
path = path.split(os.pathsep)
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == 'win32':
|
||||||
# The current directory takes precedence on Windows.
|
# The current directory takes precedence on Windows.
|
||||||
if os.curdir not in path:
|
if os.curdir not in path:
|
||||||
path.insert(0, os.curdir)
|
path.insert(0, os.curdir)
|
||||||
|
|
||||||
# PATHEXT is necessary to check on Windows.
|
# PATHEXT is necessary to check on Windows.
|
||||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
|
||||||
# See if the given file matches any of the expected path extensions.
|
# See if the given file matches any of the expected path extensions.
|
||||||
# This will allow us to short circuit when given "python.exe".
|
# This will allow us to short circuit when given 'python.exe'.
|
||||||
# If it does match, only test that one, otherwise we have to try
|
# If it does match, only test that one, otherwise we have to try
|
||||||
# others.
|
# others.
|
||||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||||
@ -118,7 +114,7 @@ except ImportError:
|
|||||||
else:
|
else:
|
||||||
files = [cmd + ext for ext in pathext]
|
files = [cmd + ext for ext in pathext]
|
||||||
else:
|
else:
|
||||||
# On other platforms you don't have things like PATHEXT to tell you
|
# On other platforms you don’t have things like PATHEXT to tell you
|
||||||
# what file suffixes are executable, so just pass on cmd as-is.
|
# what file suffixes are executable, so just pass on cmd as-is.
|
||||||
files = [cmd]
|
files = [cmd]
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ class ThreadedSegment(Segment, MultiRunnedThread):
|
|||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.shutdown_event.set()
|
self.shutdown_event.set()
|
||||||
if self.daemon and self.is_alive():
|
if self.daemon and self.is_alive():
|
||||||
# Give the worker thread a chance to shutdown, but don't block for
|
# Give the worker thread a chance to shutdown, but don’t block for
|
||||||
# too long
|
# too long
|
||||||
self.join(0.01)
|
self.join(0.01)
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
# 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)
|
||||||
|
|
||||||
from locale import getpreferredencoding
|
import sys
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
from powerline.lib.encoding import get_preferred_output_encoding
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -25,6 +28,62 @@ def u(s):
|
|||||||
return unicode(s, 'utf-8')
|
return unicode(s, 'utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3,):
|
||||||
|
def tointiter(s):
|
||||||
|
'''Convert a byte string to the sequence of integers
|
||||||
|
'''
|
||||||
|
return (ord(c) for c in s)
|
||||||
|
else:
|
||||||
|
def tointiter(s):
|
||||||
|
'''Convert a byte string to the sequence of integers
|
||||||
|
'''
|
||||||
|
return iter(s)
|
||||||
|
|
||||||
|
|
||||||
|
def powerline_decode_error(e):
|
||||||
|
if not isinstance(e, UnicodeDecodeError):
|
||||||
|
raise NotImplementedError
|
||||||
|
return (''.join((
|
||||||
|
'<{0:02X}>'.format(c)
|
||||||
|
for c in tointiter(e.object[e.start:e.end])
|
||||||
|
)), e.end)
|
||||||
|
|
||||||
|
|
||||||
|
codecs.register_error('powerline_decode_error', powerline_decode_error)
|
||||||
|
|
||||||
|
|
||||||
|
last_swe_idx = 0
|
||||||
|
|
||||||
|
|
||||||
|
def register_strwidth_error(strwidth):
|
||||||
|
global last_swe_idx
|
||||||
|
last_swe_idx += 1
|
||||||
|
|
||||||
|
def powerline_encode_strwidth_error(e):
|
||||||
|
if not isinstance(e, UnicodeEncodeError):
|
||||||
|
raise NotImplementedError
|
||||||
|
return ('?' * strwidth(e.object[e.start:e.end]), e.end)
|
||||||
|
|
||||||
|
ename = 'powerline_encode_strwidth_error_{0}'.format(last_swe_idx)
|
||||||
|
codecs.register_error(ename, powerline_encode_strwidth_error)
|
||||||
|
return ename
|
||||||
|
|
||||||
|
|
||||||
|
def out_u(s):
|
||||||
|
'''Return unicode string suitable for displaying
|
||||||
|
|
||||||
|
Unlike other functions assumes get_preferred_output_encoding() first. Unlike
|
||||||
|
u() does not throw exceptions for invalid unicode strings. Unlike
|
||||||
|
safe_unicode() does throw an exception if object is not a string.
|
||||||
|
'''
|
||||||
|
if isinstance(s, unicode):
|
||||||
|
return s
|
||||||
|
elif isinstance(s, bytes):
|
||||||
|
return unicode(s, get_preferred_output_encoding(), 'powerline_decode_error')
|
||||||
|
else:
|
||||||
|
raise TypeError('Expected unicode or bytes instance, got {0}'.format(repr(type(s))))
|
||||||
|
|
||||||
|
|
||||||
def safe_unicode(s):
|
def safe_unicode(s):
|
||||||
'''Return unicode instance without raising an exception.
|
'''Return unicode instance without raising an exception.
|
||||||
|
|
||||||
@ -33,7 +92,7 @@ def safe_unicode(s):
|
|||||||
* UTF-8 string
|
* UTF-8 string
|
||||||
* Object with __str__() or __repr__() method that returns UTF-8 string or
|
* Object with __str__() or __repr__() method that returns UTF-8 string or
|
||||||
unicode object (depending on python version)
|
unicode object (depending on python version)
|
||||||
* String in locale.getpreferredencoding() encoding
|
* String in powerline.lib.encoding.get_preferred_output_encoding() encoding
|
||||||
* If everything failed use safe_unicode on last exception with which
|
* If everything failed use safe_unicode on last exception with which
|
||||||
everything failed
|
everything failed
|
||||||
'''
|
'''
|
||||||
@ -46,7 +105,7 @@ def safe_unicode(s):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
return unicode(str(s), 'utf-8')
|
return unicode(str(s), 'utf-8')
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return unicode(s, getpreferredencoding())
|
return unicode(s, get_preferred_output_encoding())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return safe_unicode(e)
|
return safe_unicode(e)
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ from threading import Lock
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from powerline.lib.watcher import create_tree_watcher
|
from powerline.lib.watcher import create_tree_watcher
|
||||||
|
from powerline.lib.unicode import out_u
|
||||||
|
from powerline.lib.path import join
|
||||||
|
|
||||||
|
|
||||||
def generate_directories(path):
|
def generate_directories(path):
|
||||||
@ -75,10 +77,10 @@ def get_branch_name(directory, config_file, get_func, create_watcher):
|
|||||||
raise
|
raise
|
||||||
# Config file does not exist (happens for mercurial)
|
# Config file does not exist (happens for mercurial)
|
||||||
if config_file not in branch_name_cache:
|
if config_file not in branch_name_cache:
|
||||||
branch_name_cache[config_file] = get_func(directory, config_file)
|
branch_name_cache[config_file] = out_u(get_func(directory, config_file))
|
||||||
if changed:
|
if changed:
|
||||||
# Config file has changed or was not tracked
|
# Config file has changed or was not tracked
|
||||||
branch_name_cache[config_file] = get_func(directory, config_file)
|
branch_name_cache[config_file] = out_u(get_func(directory, config_file))
|
||||||
return branch_name_cache[config_file]
|
return branch_name_cache[config_file]
|
||||||
|
|
||||||
|
|
||||||
@ -96,7 +98,7 @@ class FileStatusCache(dict):
|
|||||||
if nparent == parent:
|
if nparent == parent:
|
||||||
break
|
break
|
||||||
parent = nparent
|
parent = nparent
|
||||||
ignore_files.add(os.path.join(parent, ignore_file_name))
|
ignore_files.add(join(parent, ignore_file_name))
|
||||||
for f in extra_ignore_files:
|
for f in extra_ignore_files:
|
||||||
ignore_files.add(f)
|
ignore_files.add(f)
|
||||||
self.keypath_ignore_map[keypath] = ignore_files
|
self.keypath_ignore_map[keypath] = ignore_files
|
||||||
@ -120,7 +122,7 @@ file_status_cache = FileStatusCache()
|
|||||||
|
|
||||||
def get_file_status(directory, dirstate_file, file_path, ignore_file_name, get_func, create_watcher, extra_ignore_files=()):
|
def get_file_status(directory, dirstate_file, file_path, ignore_file_name, get_func, create_watcher, extra_ignore_files=()):
|
||||||
global file_status_cache
|
global file_status_cache
|
||||||
keypath = file_path if os.path.isabs(file_path) else os.path.join(directory, file_path)
|
keypath = file_path if os.path.isabs(file_path) else join(directory, file_path)
|
||||||
file_status_cache.update_maps(keypath, directory, dirstate_file, ignore_file_name, extra_ignore_files)
|
file_status_cache.update_maps(keypath, directory, dirstate_file, ignore_file_name, extra_ignore_files)
|
||||||
|
|
||||||
with file_status_lock:
|
with file_status_lock:
|
||||||
@ -218,9 +220,15 @@ vcs_props = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
vcs_props_bytes = [
|
||||||
|
(vcs, vcs_dir.encode('ascii'), check)
|
||||||
|
for vcs, vcs_dir, check in vcs_props
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def guess(path, create_watcher):
|
def guess(path, create_watcher):
|
||||||
for directory in generate_directories(path):
|
for directory in generate_directories(path):
|
||||||
for vcs, vcs_dir, check in vcs_props:
|
for vcs, vcs_dir, check in (vcs_props_bytes if isinstance(path, bytes) else vcs_props):
|
||||||
repo_dir = os.path.join(directory, vcs_dir)
|
repo_dir = os.path.join(directory, vcs_dir)
|
||||||
if check(repo_dir):
|
if check(repo_dir):
|
||||||
if os.path.isdir(repo_dir) and not os.access(repo_dir, os.X_OK):
|
if os.path.isdir(repo_dir) and not os.access(repo_dir, os.X_OK):
|
||||||
@ -245,7 +253,7 @@ def debug():
|
|||||||
'''Test run guess(), repo.branch() and repo.status()
|
'''Test run guess(), repo.branch() and repo.status()
|
||||||
|
|
||||||
To use::
|
To use::
|
||||||
python -c "from powerline.lib.vcs import debug; debug()" some_file_to_watch.
|
python -c 'from powerline.lib.vcs import debug; debug()' some_file_to_watch.
|
||||||
'''
|
'''
|
||||||
import sys
|
import sys
|
||||||
dest = sys.argv[-1]
|
dest = sys.argv[-1]
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# 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 sys
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -10,12 +9,14 @@ from io import StringIO
|
|||||||
from bzrlib import (workingtree, status, library_state, trace, ui)
|
from bzrlib import (workingtree, status, library_state, trace, ui)
|
||||||
|
|
||||||
from powerline.lib.vcs import get_branch_name, get_file_status
|
from powerline.lib.vcs import get_branch_name, get_file_status
|
||||||
|
from powerline.lib.path import join
|
||||||
|
from powerline.lib.encoding import get_preferred_file_contents_encoding
|
||||||
|
|
||||||
|
|
||||||
class CoerceIO(StringIO):
|
class CoerceIO(StringIO):
|
||||||
def write(self, arg):
|
def write(self, arg):
|
||||||
if isinstance(arg, bytes):
|
if isinstance(arg, bytes):
|
||||||
arg = arg.decode('utf-8', 'replace')
|
arg = arg.decode(get_preferred_file_contents_encoding(), 'replace')
|
||||||
return super(CoerceIO, self).write(arg)
|
return super(CoerceIO, self).write(arg)
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ def branch_name_from_config_file(directory, config_file):
|
|||||||
for line in f:
|
for line in f:
|
||||||
m = nick_pat.match(line)
|
m = nick_pat.match(line)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
ans = m.group(1).strip().decode('utf-8', 'replace')
|
ans = m.group(1).strip().decode(get_preferred_file_contents_encoding(), 'replace')
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@ -41,8 +42,6 @@ state = None
|
|||||||
|
|
||||||
class Repository(object):
|
class Repository(object):
|
||||||
def __init__(self, directory, create_watcher):
|
def __init__(self, directory, create_watcher):
|
||||||
if isinstance(directory, bytes):
|
|
||||||
directory = directory.decode(sys.getfilesystemencoding() or sys.getdefaultencoding() or 'utf-8')
|
|
||||||
self.directory = os.path.abspath(directory)
|
self.directory = os.path.abspath(directory)
|
||||||
self.create_watcher = create_watcher
|
self.create_watcher = create_watcher
|
||||||
|
|
||||||
@ -51,8 +50,8 @@ class Repository(object):
|
|||||||
|
|
||||||
Without file argument: returns status of the repository:
|
Without file argument: returns status of the repository:
|
||||||
|
|
||||||
:"D?": dirty (tracked modified files: added, removed, deleted, modified),
|
:'D?': dirty (tracked modified files: added, removed, deleted, modified),
|
||||||
:"?U": untracked-dirty (added, but not tracked files)
|
:'?U': untracked-dirty (added, but not tracked files)
|
||||||
:None: clean (status is empty)
|
:None: clean (status is empty)
|
||||||
|
|
||||||
With file argument: returns status of this file: The status codes are
|
With file argument: returns status of this file: The status codes are
|
||||||
@ -61,7 +60,7 @@ class Repository(object):
|
|||||||
if path is not None:
|
if path is not None:
|
||||||
return get_file_status(
|
return get_file_status(
|
||||||
directory=self.directory,
|
directory=self.directory,
|
||||||
dirstate_file=os.path.join(self.directory, '.bzr', 'checkout', 'dirstate'),
|
dirstate_file=join(self.directory, '.bzr', 'checkout', 'dirstate'),
|
||||||
file_path=path,
|
file_path=path,
|
||||||
ignore_file_name='.bzrignore',
|
ignore_file_name='.bzrignore',
|
||||||
get_func=self.do_status,
|
get_func=self.do_status,
|
||||||
@ -100,7 +99,7 @@ class Repository(object):
|
|||||||
return ans if ans.strip() else None
|
return ans if ans.strip() else None
|
||||||
|
|
||||||
def branch(self):
|
def branch(self):
|
||||||
config_file = os.path.join(self.directory, '.bzr', 'branch', 'branch.conf')
|
config_file = join(self.directory, '.bzr', 'branch', 'branch.conf')
|
||||||
return get_branch_name(
|
return get_branch_name(
|
||||||
directory=self.directory,
|
directory=self.directory,
|
||||||
config_file=config_file,
|
config_file=config_file,
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from powerline.lib.vcs import get_branch_name, get_file_status
|
from powerline.lib.vcs import get_branch_name, get_file_status
|
||||||
from powerline.lib.shell import readlines
|
from powerline.lib.shell import readlines
|
||||||
|
from powerline.lib.path import join
|
||||||
|
from powerline.lib.encoding import (get_preferred_file_name_encoding,
|
||||||
|
get_preferred_file_contents_encoding)
|
||||||
|
from powerline.lib.shell import which
|
||||||
|
|
||||||
|
|
||||||
_ref_pat = re.compile(br'ref:\s*refs/heads/(.+)')
|
_ref_pat = re.compile(br'ref:\s*refs/heads/(.+)')
|
||||||
@ -20,20 +23,22 @@ def branch_name_from_config_file(directory, config_file):
|
|||||||
return os.path.basename(directory)
|
return os.path.basename(directory)
|
||||||
m = _ref_pat.match(raw)
|
m = _ref_pat.match(raw)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
return m.group(1).decode('utf-8', 'replace')
|
return m.group(1).decode(get_preferred_file_contents_encoding(), 'replace')
|
||||||
return raw[:7]
|
return raw[:7]
|
||||||
|
|
||||||
|
|
||||||
def git_directory(directory):
|
def git_directory(directory):
|
||||||
path = os.path.join(directory, '.git')
|
path = join(directory, '.git')
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
if not raw.startswith(b'gitdir: '):
|
if not raw.startswith(b'gitdir: '):
|
||||||
raise IOError('invalid gitfile format')
|
raise IOError('invalid gitfile format')
|
||||||
raw = raw[8:].decode(sys.getfilesystemencoding() or 'utf-8')
|
raw = raw[8:]
|
||||||
if raw[-1] == '\n':
|
if raw[-1:] == b'\n':
|
||||||
raw = raw[:-1]
|
raw = raw[:-1]
|
||||||
|
if not isinstance(path, bytes):
|
||||||
|
raw = raw.decode(get_preferred_file_name_encoding())
|
||||||
if not raw:
|
if not raw:
|
||||||
raise IOError('no path in gitfile')
|
raise IOError('no path in gitfile')
|
||||||
return os.path.abspath(os.path.join(directory, raw))
|
return os.path.abspath(os.path.join(directory, raw))
|
||||||
@ -59,28 +64,28 @@ class GitRepository(object):
|
|||||||
:None: repository clean
|
:None: repository clean
|
||||||
|
|
||||||
With file argument: returns status of this file. Output is
|
With file argument: returns status of this file. Output is
|
||||||
equivalent to the first two columns of "git status --porcelain"
|
equivalent to the first two columns of ``git status --porcelain``
|
||||||
(except for merge statuses as they are not supported by libgit2).
|
(except for merge statuses as they are not supported by libgit2).
|
||||||
'''
|
'''
|
||||||
if path:
|
if path:
|
||||||
gitd = git_directory(self.directory)
|
gitd = git_directory(self.directory)
|
||||||
# We need HEAD as without it using fugitive to commit causes the
|
# We need HEAD as without it using fugitive to commit causes the
|
||||||
# current file's status (and only the current file) to not be updated
|
# current file’s status (and only the current file) to not be updated
|
||||||
# for some reason I cannot be bothered to figure out.
|
# for some reason I cannot be bothered to figure out.
|
||||||
return get_file_status(
|
return get_file_status(
|
||||||
directory=self.directory,
|
directory=self.directory,
|
||||||
dirstate_file=os.path.join(gitd, 'index'),
|
dirstate_file=join(gitd, 'index'),
|
||||||
file_path=path,
|
file_path=path,
|
||||||
ignore_file_name='.gitignore',
|
ignore_file_name='.gitignore',
|
||||||
get_func=self.do_status,
|
get_func=self.do_status,
|
||||||
create_watcher=self.create_watcher,
|
create_watcher=self.create_watcher,
|
||||||
extra_ignore_files=tuple(os.path.join(gitd, x) for x in ('logs/HEAD', 'info/exclude')),
|
extra_ignore_files=tuple(join(gitd, x) for x in ('logs/HEAD', 'info/exclude')),
|
||||||
)
|
)
|
||||||
return self.do_status(self.directory, path)
|
return self.do_status(self.directory, path)
|
||||||
|
|
||||||
def branch(self):
|
def branch(self):
|
||||||
directory = git_directory(self.directory)
|
directory = git_directory(self.directory)
|
||||||
head = os.path.join(directory, 'HEAD')
|
head = join(directory, 'HEAD')
|
||||||
return get_branch_name(
|
return get_branch_name(
|
||||||
directory=directory,
|
directory=directory,
|
||||||
config_file=head,
|
config_file=head,
|
||||||
@ -151,6 +156,11 @@ try:
|
|||||||
return r if r != ' ' else None
|
return r if r != ' ' else None
|
||||||
except ImportError:
|
except ImportError:
|
||||||
class Repository(GitRepository):
|
class Repository(GitRepository):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if not which('git'):
|
||||||
|
raise OSError('git executable is not available')
|
||||||
|
super(Repository, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ignore_event(path, name):
|
def ignore_event(path, name):
|
||||||
# Ignore changes to the index.lock file, since they happen
|
# Ignore changes to the index.lock file, since they happen
|
||||||
|
@ -6,13 +6,15 @@ import os
|
|||||||
from mercurial import hg, ui, match
|
from mercurial import hg, ui, match
|
||||||
|
|
||||||
from powerline.lib.vcs import get_branch_name, get_file_status
|
from powerline.lib.vcs import get_branch_name, get_file_status
|
||||||
|
from powerline.lib.path import join
|
||||||
|
from powerline.lib.encoding import get_preferred_file_contents_encoding
|
||||||
|
|
||||||
|
|
||||||
def branch_name_from_config_file(directory, config_file):
|
def branch_name_from_config_file(directory, config_file):
|
||||||
try:
|
try:
|
||||||
with open(config_file, 'rb') as f:
|
with open(config_file, 'rb') as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
return raw.decode('utf-8', 'replace').strip()
|
return raw.decode(get_preferred_file_contents_encoding(), 'replace').strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
return 'default'
|
return 'default'
|
||||||
|
|
||||||
@ -39,18 +41,18 @@ class Repository(object):
|
|||||||
|
|
||||||
Without file argument: returns status of the repository:
|
Without file argument: returns status of the repository:
|
||||||
|
|
||||||
:"D?": dirty (tracked modified files: added, removed, deleted, modified),
|
:'D?': dirty (tracked modified files: added, removed, deleted, modified),
|
||||||
:"?U": untracked-dirty (added, but not tracked files)
|
:'?U': untracked-dirty (added, but not tracked files)
|
||||||
:None: clean (status is empty)
|
:None: clean (status is empty)
|
||||||
|
|
||||||
With file argument: returns status of this file: "M"odified, "A"dded,
|
With file argument: returns status of this file: `M`odified, `A`dded,
|
||||||
"R"emoved, "D"eleted (removed from filesystem, but still tracked),
|
`R`emoved, `D`eleted (removed from filesystem, but still tracked),
|
||||||
"U"nknown, "I"gnored, (None)Clean.
|
`U`nknown, `I`gnored, (None)Clean.
|
||||||
'''
|
'''
|
||||||
if path:
|
if path:
|
||||||
return get_file_status(
|
return get_file_status(
|
||||||
directory=self.directory,
|
directory=self.directory,
|
||||||
dirstate_file=os.path.join(self.directory, '.hg', 'dirstate'),
|
dirstate_file=join(self.directory, '.hg', 'dirstate'),
|
||||||
file_path=path,
|
file_path=path,
|
||||||
ignore_file_name='.hgignore',
|
ignore_file_name='.hgignore',
|
||||||
get_func=self.do_status,
|
get_func=self.do_status,
|
||||||
@ -75,7 +77,7 @@ class Repository(object):
|
|||||||
return self.repo_statuses_str[resulting_status]
|
return self.repo_statuses_str[resulting_status]
|
||||||
|
|
||||||
def branch(self):
|
def branch(self):
|
||||||
config_file = os.path.join(self.directory, '.hg', 'branch')
|
config_file = join(self.directory, '.hg', 'branch')
|
||||||
return get_branch_name(
|
return get_branch_name(
|
||||||
directory=self.directory,
|
directory=self.directory,
|
||||||
config_file=config_file,
|
config_file=config_file,
|
||||||
|
@ -247,6 +247,8 @@ class INotifyTreeWatcher(INotify):
|
|||||||
if mask & self.CREATE:
|
if mask & self.CREATE:
|
||||||
# A new sub-directory might have been created, monitor it.
|
# A new sub-directory might have been created, monitor it.
|
||||||
try:
|
try:
|
||||||
|
if not isinstance(path, bytes):
|
||||||
|
name = name.decode(self.fenc)
|
||||||
self.add_watch(os.path.join(path, name))
|
self.add_watch(os.path.join(path, name))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == errno.ENOENT:
|
if e.errno == errno.ENOENT:
|
||||||
|
@ -7,8 +7,10 @@ from collections import defaultdict
|
|||||||
from threading import RLock
|
from threading import RLock
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from errno import ENOENT
|
||||||
|
|
||||||
from powerline.lib.path import realpath
|
from powerline.lib.path import realpath
|
||||||
|
from powerline.lib.encoding import get_preferred_file_name_encoding
|
||||||
|
|
||||||
|
|
||||||
class UvNotFound(NotImplementedError):
|
class UvNotFound(NotImplementedError):
|
||||||
@ -55,15 +57,24 @@ def start_uv_thread():
|
|||||||
return _uv_thread.uv_loop
|
return _uv_thread.uv_loop
|
||||||
|
|
||||||
|
|
||||||
|
def normpath(path, fenc):
|
||||||
|
path = realpath(path)
|
||||||
|
if isinstance(path, bytes):
|
||||||
|
return path.decode(fenc)
|
||||||
|
else:
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
class UvWatcher(object):
|
class UvWatcher(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
import_pyuv()
|
import_pyuv()
|
||||||
self.watches = {}
|
self.watches = {}
|
||||||
self.lock = RLock()
|
self.lock = RLock()
|
||||||
self.loop = start_uv_thread()
|
self.loop = start_uv_thread()
|
||||||
|
self.fenc = get_preferred_file_name_encoding()
|
||||||
|
|
||||||
def watch(self, path):
|
def watch(self, path):
|
||||||
path = realpath(path)
|
path = normpath(path, self.fenc)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if path not in self.watches:
|
if path not in self.watches:
|
||||||
try:
|
try:
|
||||||
@ -76,12 +87,12 @@ class UvWatcher(object):
|
|||||||
except pyuv.error.FSEventError as e:
|
except pyuv.error.FSEventError as e:
|
||||||
code = e.args[0]
|
code = e.args[0]
|
||||||
if code == pyuv.errno.UV_ENOENT:
|
if code == pyuv.errno.UV_ENOENT:
|
||||||
raise OSError('No such file or directory: ' + path)
|
raise OSError(ENOENT, 'No such file or directory: ' + path)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def unwatch(self, path):
|
def unwatch(self, path):
|
||||||
path = realpath(path)
|
path = normpath(path, self.fenc)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
try:
|
try:
|
||||||
watch = self.watches.pop(path)
|
watch = self.watches.pop(path)
|
||||||
@ -91,7 +102,7 @@ class UvWatcher(object):
|
|||||||
|
|
||||||
def is_watching(self, path):
|
def is_watching(self, path):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return realpath(path) in self.watches
|
return normpath(path, self.fenc) in self.watches
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
try:
|
try:
|
||||||
@ -121,7 +132,7 @@ class UvFileWatcher(UvWatcher):
|
|||||||
self.events.pop(path, None)
|
self.events.pop(path, None)
|
||||||
|
|
||||||
def __call__(self, path):
|
def __call__(self, path):
|
||||||
path = realpath(path)
|
path = normpath(path, self.fenc)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
events = self.events.pop(path, None)
|
events = self.events.pop(path, None)
|
||||||
if events:
|
if events:
|
||||||
@ -138,14 +149,15 @@ class UvTreeWatcher(UvWatcher):
|
|||||||
def __init__(self, basedir, ignore_event=None):
|
def __init__(self, basedir, ignore_event=None):
|
||||||
super(UvTreeWatcher, self).__init__()
|
super(UvTreeWatcher, self).__init__()
|
||||||
self.ignore_event = ignore_event or (lambda path, name: False)
|
self.ignore_event = ignore_event or (lambda path, name: False)
|
||||||
self.basedir = realpath(basedir)
|
self.basedir = normpath(basedir, self.fenc)
|
||||||
self.modified = True
|
self.modified = True
|
||||||
self.watch_directory(self.basedir)
|
self.watch_directory(self.basedir)
|
||||||
|
|
||||||
def watch_directory(self, path):
|
def watch_directory(self, path):
|
||||||
os.path.walk(realpath(path), self.watch_one_directory, None)
|
for root, dirs, files in os.walk(normpath(path, self.fenc)):
|
||||||
|
self.watch_one_directory(root)
|
||||||
|
|
||||||
def watch_one_directory(self, arg, dirname, fnames):
|
def watch_one_directory(self, dirname):
|
||||||
try:
|
try:
|
||||||
self.watch(dirname)
|
self.watch(dirname)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
File diff suppressed because it is too large
Load Diff
706
powerline/lint/checks.py
Normal file
706
powerline/lint/checks.py
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from powerline.lib.threaded import ThreadedSegment
|
||||||
|
from powerline.lib.unicode import unicode
|
||||||
|
from powerline.lint.markedjson.markedvalue import MarkedUnicode
|
||||||
|
from powerline.lint.markedjson.error import DelayedEchoErr, Mark
|
||||||
|
from powerline.lint.selfcheck import havemarks
|
||||||
|
from powerline.lint.context import JStr, list_themes
|
||||||
|
from powerline.lint.imp import WithPath, import_function, import_segment
|
||||||
|
from powerline.lint.spec import Spec
|
||||||
|
from powerline.lint.inspect import getconfigargspec
|
||||||
|
|
||||||
|
|
||||||
|
list_sep = JStr(', ')
|
||||||
|
|
||||||
|
|
||||||
|
generic_keys = set((
|
||||||
|
'exclude_modes', 'include_modes',
|
||||||
|
'exclude_function', 'include_function',
|
||||||
|
'width', 'align',
|
||||||
|
'name',
|
||||||
|
'draw_soft_divider', 'draw_hard_divider',
|
||||||
|
'priority',
|
||||||
|
'after', 'before',
|
||||||
|
'display'
|
||||||
|
))
|
||||||
|
type_keys = {
|
||||||
|
'function': set(('function', 'args', 'draw_inner_divider')),
|
||||||
|
'string': set(('contents', 'type', 'highlight_group', 'divider_highlight_group')),
|
||||||
|
'segment_list': set(('function', 'segments', 'args', 'type')),
|
||||||
|
}
|
||||||
|
required_keys = {
|
||||||
|
'function': set(('function',)),
|
||||||
|
'string': set(()),
|
||||||
|
'segment_list': set(('function', 'segments',)),
|
||||||
|
}
|
||||||
|
highlight_keys = set(('highlight_group', 'name'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_function_strings(function_name, context, ext):
|
||||||
|
if '.' in function_name:
|
||||||
|
module, function_name = function_name.rpartition('.')[::2]
|
||||||
|
else:
|
||||||
|
module = context[0][1].get(
|
||||||
|
'default_module', MarkedUnicode('powerline.segments.' + ext, None))
|
||||||
|
return module, function_name
|
||||||
|
|
||||||
|
|
||||||
|
def check_matcher_func(ext, match_name, data, context, echoerr):
|
||||||
|
havemarks(match_name)
|
||||||
|
import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])]
|
||||||
|
|
||||||
|
match_module, separator, match_function = match_name.rpartition('.')
|
||||||
|
if not separator:
|
||||||
|
match_module = 'powerline.matchers.{0}'.format(ext)
|
||||||
|
match_function = match_name
|
||||||
|
with WithPath(import_paths):
|
||||||
|
try:
|
||||||
|
func = getattr(__import__(str(match_module), fromlist=[str(match_function)]), str(match_function))
|
||||||
|
except ImportError:
|
||||||
|
echoerr(context='Error while loading matcher functions',
|
||||||
|
problem='failed to load module {0}'.format(match_module),
|
||||||
|
problem_mark=match_name.mark)
|
||||||
|
return True, False, True
|
||||||
|
except AttributeError:
|
||||||
|
echoerr(context='Error while loading matcher functions',
|
||||||
|
problem='failed to load matcher function {0}'.format(match_function),
|
||||||
|
problem_mark=match_name.mark)
|
||||||
|
return True, False, True
|
||||||
|
|
||||||
|
if not callable(func):
|
||||||
|
echoerr(context='Error while loading matcher functions',
|
||||||
|
problem='loaded “function” {0} is not callable'.format(match_function),
|
||||||
|
problem_mark=match_name.mark)
|
||||||
|
return True, False, True
|
||||||
|
|
||||||
|
if hasattr(func, 'func_code') and hasattr(func.func_code, 'co_argcount'):
|
||||||
|
if func.func_code.co_argcount != 1:
|
||||||
|
echoerr(
|
||||||
|
context='Error while loading matcher functions',
|
||||||
|
problem=(
|
||||||
|
'function {0} accepts {1} arguments instead of 1. '
|
||||||
|
'Are you sure it is the proper function?'
|
||||||
|
).format(match_function, func.func_code.co_argcount),
|
||||||
|
problem_mark=match_name.mark
|
||||||
|
)
|
||||||
|
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def check_ext(ext, data, context, echoerr):
|
||||||
|
havemarks(ext)
|
||||||
|
hadsomedirs = False
|
||||||
|
hadproblem = False
|
||||||
|
if ext not in data['lists']['exts']:
|
||||||
|
hadproblem = True
|
||||||
|
echoerr(context='Error while loading {0} extension configuration'.format(ext),
|
||||||
|
context_mark=ext.mark,
|
||||||
|
problem='extension configuration does not exist')
|
||||||
|
else:
|
||||||
|
for typ in ('themes', 'colorschemes'):
|
||||||
|
if ext not in data['configs'][typ] and not data['configs']['top_' + typ]:
|
||||||
|
hadproblem = True
|
||||||
|
echoerr(context='Error while loading {0} extension configuration'.format(ext),
|
||||||
|
context_mark=ext.mark,
|
||||||
|
problem='{0} configuration does not exist'.format(typ))
|
||||||
|
else:
|
||||||
|
hadsomedirs = True
|
||||||
|
return hadsomedirs, hadproblem
|
||||||
|
|
||||||
|
|
||||||
|
def check_config(d, theme, data, context, echoerr):
|
||||||
|
if len(context) == 4:
|
||||||
|
ext = context[-2][0]
|
||||||
|
else:
|
||||||
|
# local_themes
|
||||||
|
ext = context[-3][0]
|
||||||
|
if ext not in data['lists']['exts']:
|
||||||
|
echoerr(context='Error while loading {0} extension configuration'.format(ext),
|
||||||
|
context_mark=ext.mark,
|
||||||
|
problem='extension configuration does not exist')
|
||||||
|
return True, False, True
|
||||||
|
if (
|
||||||
|
(ext not in data['configs'][d] or theme not in data['configs'][d][ext])
|
||||||
|
and theme not in data['configs']['top_' + d]
|
||||||
|
):
|
||||||
|
echoerr(context='Error while loading {0} from {1} extension configuration'.format(d[:-1], ext),
|
||||||
|
problem='failed to find configuration file {0}/{1}/{2}.json'.format(d, ext, theme),
|
||||||
|
problem_mark=theme.mark)
|
||||||
|
return True, False, True
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def check_top_theme(theme, data, context, echoerr):
|
||||||
|
havemarks(theme)
|
||||||
|
if theme not in data['configs']['top_themes']:
|
||||||
|
echoerr(context='Error while checking extension configuration (key {key})'.format(key=context.key),
|
||||||
|
context_mark=context[-2][0].mark,
|
||||||
|
problem='failed to find top theme {0}'.format(theme),
|
||||||
|
problem_mark=theme.mark)
|
||||||
|
return True, False, True
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def check_color(color, data, context, echoerr):
|
||||||
|
havemarks(color)
|
||||||
|
if (color not in data['colors_config'].get('colors', {})
|
||||||
|
and color not in data['colors_config'].get('gradients', {})):
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking highlight group in colorscheme (key {key})'.format(
|
||||||
|
key=context.key),
|
||||||
|
problem='found unexistent color or gradient {0}'.format(color),
|
||||||
|
problem_mark=color.mark
|
||||||
|
)
|
||||||
|
return True, False, True
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def check_translated_group_name(group, data, context, echoerr):
|
||||||
|
return check_group(group, data, context, echoerr)
|
||||||
|
|
||||||
|
|
||||||
|
def check_group(group, data, context, echoerr):
|
||||||
|
havemarks(group)
|
||||||
|
if not isinstance(group, unicode):
|
||||||
|
return True, False, False
|
||||||
|
colorscheme = data['colorscheme']
|
||||||
|
ext = data['ext']
|
||||||
|
configs = []
|
||||||
|
if ext:
|
||||||
|
if colorscheme == '__main__':
|
||||||
|
configs.append([config for config in data['ext_colorscheme_configs'][ext].items()])
|
||||||
|
configs.append([config for config in data['top_colorscheme_configs'].items()])
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
configs.append([data['ext_colorscheme_configs'][ext][colorscheme]])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
configs.append([data['ext_colorscheme_configs'][ext]['__main__']])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
configs.append([data['top_colorscheme_configs'][colorscheme]])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
configs.append([data['top_colorscheme_configs'][colorscheme]])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
new_echoerr = DelayedEchoErr(echoerr)
|
||||||
|
hadproblem = False
|
||||||
|
for config_lst in configs:
|
||||||
|
tofind = len(config_lst)
|
||||||
|
not_found = []
|
||||||
|
for config in config_lst:
|
||||||
|
if isinstance(config, tuple):
|
||||||
|
new_colorscheme, config = config
|
||||||
|
new_data = data.copy()
|
||||||
|
new_data['colorscheme'] = new_colorscheme
|
||||||
|
else:
|
||||||
|
new_data = data
|
||||||
|
havemarks(config)
|
||||||
|
try:
|
||||||
|
group_data = config['groups'][group]
|
||||||
|
except KeyError:
|
||||||
|
not_found.append(config.mark.name)
|
||||||
|
else:
|
||||||
|
proceed, echo, chadproblem = check_group(
|
||||||
|
group_data,
|
||||||
|
new_data,
|
||||||
|
context,
|
||||||
|
echoerr,
|
||||||
|
)
|
||||||
|
if chadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
else:
|
||||||
|
tofind -= 1
|
||||||
|
if not tofind:
|
||||||
|
return proceed, echo, hadproblem
|
||||||
|
if not proceed:
|
||||||
|
break
|
||||||
|
if not_found:
|
||||||
|
new_echoerr(
|
||||||
|
context='Error while checking group definition in colorscheme (key {key})'.format(
|
||||||
|
key=context.key),
|
||||||
|
problem='name {0} is not present in {1} {2} colorschemes: {3}'.format(
|
||||||
|
group, tofind, ext, ', '.join(not_found)),
|
||||||
|
problem_mark=group.mark
|
||||||
|
)
|
||||||
|
new_echoerr.echo_all()
|
||||||
|
return True, False, hadproblem
|
||||||
|
|
||||||
|
|
||||||
|
def check_key_compatibility(segment, data, context, echoerr):
|
||||||
|
havemarks(segment)
|
||||||
|
segment_type = segment.get('type', MarkedUnicode('function', None))
|
||||||
|
havemarks(segment_type)
|
||||||
|
|
||||||
|
if segment_type not in type_keys:
|
||||||
|
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
problem='found segment with unknown type {0}'.format(segment_type),
|
||||||
|
problem_mark=segment_type.mark)
|
||||||
|
return False, False, True
|
||||||
|
|
||||||
|
hadproblem = False
|
||||||
|
|
||||||
|
keys = set(segment)
|
||||||
|
if not ((keys - generic_keys) < type_keys[segment_type]):
|
||||||
|
unknown_keys = keys - generic_keys - type_keys[segment_type]
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=context[-1][1].mark,
|
||||||
|
problem='found keys not used with the current segment type: {0}'.format(
|
||||||
|
list_sep.join(unknown_keys)),
|
||||||
|
problem_mark=list(unknown_keys)[0].mark
|
||||||
|
)
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
if not (keys >= required_keys[segment_type]):
|
||||||
|
missing_keys = required_keys[segment_type] - keys
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=context[-1][1].mark,
|
||||||
|
problem='found missing required keys: {0}'.format(
|
||||||
|
list_sep.join(missing_keys))
|
||||||
|
)
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
if not (segment_type == 'function' or (keys & highlight_keys)):
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=context[-1][1].mark,
|
||||||
|
problem=(
|
||||||
|
'found missing keys required to determine highlight group. '
|
||||||
|
'Either highlight_group or name key must be present'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
return True, False, hadproblem
|
||||||
|
|
||||||
|
|
||||||
|
def check_segment_module(module, data, context, echoerr):
|
||||||
|
havemarks(module)
|
||||||
|
with WithPath(data['import_paths']):
|
||||||
|
try:
|
||||||
|
__import__(str(module))
|
||||||
|
except ImportError as e:
|
||||||
|
if echoerr.logger.level >= logging.DEBUG:
|
||||||
|
echoerr.logger.exception(e)
|
||||||
|
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
problem='failed to import module {0}'.format(module),
|
||||||
|
problem_mark=module.mark)
|
||||||
|
return True, False, True
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def check_full_segment_data(segment, data, context, echoerr):
|
||||||
|
if 'name' not in segment and 'function' not in segment:
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
ext = data['ext']
|
||||||
|
theme_segment_data = context[0][1].get('segment_data', {})
|
||||||
|
main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None)
|
||||||
|
if not main_theme_name or data['theme'] == main_theme_name:
|
||||||
|
top_segment_data = {}
|
||||||
|
else:
|
||||||
|
top_segment_data = data['ext_theme_configs'].get(main_theme_name, {}).get('segment_data', {})
|
||||||
|
|
||||||
|
if segment.get('type', 'function') == 'function':
|
||||||
|
function_name = segment.get('function')
|
||||||
|
if function_name:
|
||||||
|
module, function_name = get_function_strings(function_name, context, ext)
|
||||||
|
names = [module + '.' + function_name, function_name]
|
||||||
|
else:
|
||||||
|
names = []
|
||||||
|
elif segment.get('name'):
|
||||||
|
names = [segment['name']]
|
||||||
|
else:
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
segment_copy = segment.copy()
|
||||||
|
|
||||||
|
for key in ('before', 'after', 'args', 'contents'):
|
||||||
|
if key not in segment_copy:
|
||||||
|
for segment_data in [theme_segment_data, top_segment_data]:
|
||||||
|
for name in names:
|
||||||
|
try:
|
||||||
|
val = segment_data[name][key]
|
||||||
|
k = segment_data[name].keydict[key]
|
||||||
|
segment_copy[k] = val
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return check_key_compatibility(segment_copy, data, context, echoerr)
|
||||||
|
|
||||||
|
|
||||||
|
def check_segment_function(function_name, data, context, echoerr):
|
||||||
|
havemarks(function_name)
|
||||||
|
ext = data['ext']
|
||||||
|
module, function_name = get_function_strings(function_name, context, ext)
|
||||||
|
if context[-2][1].get('type', 'function') == 'function':
|
||||||
|
func = import_segment(function_name, data, context, echoerr, module=module)
|
||||||
|
|
||||||
|
if not func:
|
||||||
|
return True, False, True
|
||||||
|
|
||||||
|
hl_groups = []
|
||||||
|
divider_hl_group = None
|
||||||
|
|
||||||
|
if func.__doc__:
|
||||||
|
H_G_USED_STR = 'Highlight groups used: '
|
||||||
|
LHGUS = len(H_G_USED_STR)
|
||||||
|
D_H_G_USED_STR = 'Divider highlight group used: '
|
||||||
|
LDHGUS = len(D_H_G_USED_STR)
|
||||||
|
pointer = 0
|
||||||
|
mark_name = '<{0} docstring>'.format(function_name)
|
||||||
|
for i, line in enumerate(func.__doc__.split('\n')):
|
||||||
|
if H_G_USED_STR in line:
|
||||||
|
idx = line.index(H_G_USED_STR) + LHGUS
|
||||||
|
hl_groups.append((
|
||||||
|
line[idx:],
|
||||||
|
(mark_name, i + 1, idx + 1, func.__doc__),
|
||||||
|
pointer + idx
|
||||||
|
))
|
||||||
|
elif D_H_G_USED_STR in line:
|
||||||
|
idx = line.index(D_H_G_USED_STR) + LDHGUS + 2
|
||||||
|
mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
|
||||||
|
divider_hl_group = MarkedUnicode(line[idx:-3], mark)
|
||||||
|
pointer += len(line) + len('\n')
|
||||||
|
|
||||||
|
hadproblem = False
|
||||||
|
|
||||||
|
if divider_hl_group:
|
||||||
|
r = hl_exists(divider_hl_group, data, context, echoerr, allow_gradients=True)
|
||||||
|
if r:
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||||
|
problem=(
|
||||||
|
'found highlight group {0} not defined in the following colorschemes: {1}\n'
|
||||||
|
'(Group name was obtained from function documentation.)'
|
||||||
|
).format(divider_hl_group, list_sep.join(r)),
|
||||||
|
problem_mark=function_name.mark
|
||||||
|
)
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
if hl_groups:
|
||||||
|
greg = re.compile(r'``([^`]+)``( \(gradient\))?')
|
||||||
|
parsed_hl_groups = []
|
||||||
|
for line, mark_args, pointer in hl_groups:
|
||||||
|
for s in line.split(', '):
|
||||||
|
required_pack = []
|
||||||
|
sub_pointer = pointer
|
||||||
|
for subs in s.split(' or '):
|
||||||
|
match = greg.match(subs)
|
||||||
|
try:
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
hl_group = MarkedUnicode(
|
||||||
|
match.group(1),
|
||||||
|
Mark(*mark_args, pointer=sub_pointer + match.start(1))
|
||||||
|
)
|
||||||
|
gradient = bool(match.group(2))
|
||||||
|
required_pack.append((hl_group, gradient))
|
||||||
|
finally:
|
||||||
|
sub_pointer += len(subs) + len(' or ')
|
||||||
|
parsed_hl_groups.append(required_pack)
|
||||||
|
pointer += len(s) + len(', ')
|
||||||
|
del hl_group, gradient
|
||||||
|
for required_pack in parsed_hl_groups:
|
||||||
|
rs = [
|
||||||
|
hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False))
|
||||||
|
for hl_group, gradient in required_pack
|
||||||
|
]
|
||||||
|
if all(rs):
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||||
|
problem=(
|
||||||
|
'found highlight groups list ({0}) with all groups not defined in some colorschemes\n'
|
||||||
|
'(Group names were taken from function documentation.)'
|
||||||
|
).format(list_sep.join((h[0] for h in required_pack))),
|
||||||
|
problem_mark=function_name.mark
|
||||||
|
)
|
||||||
|
for r, h in zip(rs, required_pack):
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||||
|
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
|
||||||
|
h[0], list_sep.join(r))
|
||||||
|
)
|
||||||
|
hadproblem = True
|
||||||
|
else:
|
||||||
|
r = hl_exists(function_name, data, context, echoerr, allow_gradients=True)
|
||||||
|
if r:
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||||
|
problem=(
|
||||||
|
'found highlight group {0} not defined in the following colorschemes: {1}\n'
|
||||||
|
'(If not specified otherwise in documentation, '
|
||||||
|
'highlight group for function segments\n'
|
||||||
|
'is the same as the function name.)'
|
||||||
|
).format(function_name, list_sep.join(r)),
|
||||||
|
problem_mark=function_name.mark
|
||||||
|
)
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
return True, False, hadproblem
|
||||||
|
elif context[-2][1].get('type') != 'segment_list':
|
||||||
|
if function_name not in context[0][1].get('segment_data', {}):
|
||||||
|
main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None)
|
||||||
|
if data['theme'] == main_theme_name:
|
||||||
|
main_theme = {}
|
||||||
|
else:
|
||||||
|
main_theme = data['ext_theme_configs'].get(main_theme_name, {})
|
||||||
|
if (
|
||||||
|
function_name not in main_theme.get('segment_data', {})
|
||||||
|
and function_name not in data['ext_theme_configs'].get('__main__', {}).get('segment_data', {})
|
||||||
|
and not any(((function_name in theme.get('segment_data', {})) for theme in data['top_themes'].values()))
|
||||||
|
):
|
||||||
|
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
problem='found useless use of name key (such name is not present in theme/segment_data)',
|
||||||
|
problem_mark=function_name.mark)
|
||||||
|
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def hl_exists(hl_group, data, context, echoerr, allow_gradients=False):
|
||||||
|
havemarks(hl_group)
|
||||||
|
ext = data['ext']
|
||||||
|
if ext not in data['colorscheme_configs']:
|
||||||
|
# No colorschemes. Error was already reported, no need to report it
|
||||||
|
# twice
|
||||||
|
return []
|
||||||
|
r = []
|
||||||
|
for colorscheme, cconfig in data['colorscheme_configs'][ext].items():
|
||||||
|
if hl_group not in cconfig.get('groups', {}):
|
||||||
|
r.append(colorscheme)
|
||||||
|
elif not allow_gradients or allow_gradients == 'force':
|
||||||
|
group_config = cconfig['groups'][hl_group]
|
||||||
|
havemarks(group_config)
|
||||||
|
hadgradient = False
|
||||||
|
for ckey in ('fg', 'bg'):
|
||||||
|
color = group_config.get(ckey)
|
||||||
|
if not color:
|
||||||
|
# No color. Error was already reported.
|
||||||
|
continue
|
||||||
|
havemarks(color)
|
||||||
|
# Gradients are only allowed for function segments. Note that
|
||||||
|
# whether *either* color or gradient exists should have been
|
||||||
|
# already checked
|
||||||
|
hascolor = color in data['colors_config'].get('colors', {})
|
||||||
|
hasgradient = color in data['colors_config'].get('gradients', {})
|
||||||
|
if hasgradient:
|
||||||
|
hadgradient = True
|
||||||
|
if allow_gradients is False and not hascolor and hasgradient:
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking highlight group in theme (key {key})'.format(
|
||||||
|
key=context.key),
|
||||||
|
context_mark=hl_group.mark,
|
||||||
|
problem='group {0} is using gradient {1} instead of a color'.format(hl_group, color),
|
||||||
|
problem_mark=color.mark
|
||||||
|
)
|
||||||
|
r.append(colorscheme)
|
||||||
|
continue
|
||||||
|
if allow_gradients == 'force' and not hadgradient:
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking highlight group in theme (key {key})'.format(
|
||||||
|
key=context.key),
|
||||||
|
context_mark=hl_group.mark,
|
||||||
|
problem='group {0} should have at least one gradient color, but it has no'.format(hl_group),
|
||||||
|
problem_mark=group_config.mark
|
||||||
|
)
|
||||||
|
r.append(colorscheme)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def check_highlight_group(hl_group, data, context, echoerr):
|
||||||
|
havemarks(hl_group)
|
||||||
|
r = hl_exists(hl_group, data, context, echoerr)
|
||||||
|
if r:
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||||
|
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
|
||||||
|
hl_group, list_sep.join(r)),
|
||||||
|
problem_mark=hl_group.mark
|
||||||
|
)
|
||||||
|
return True, False, True
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def check_highlight_groups(hl_groups, data, context, echoerr):
|
||||||
|
havemarks(hl_groups)
|
||||||
|
rs = [hl_exists(hl_group, data, context, echoerr) for hl_group in hl_groups]
|
||||||
|
if all(rs):
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||||
|
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format(
|
||||||
|
list_sep.join((unicode(h) for h in hl_groups))),
|
||||||
|
problem_mark=hl_groups.mark
|
||||||
|
)
|
||||||
|
for r, hl_group in zip(rs, hl_groups):
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||||
|
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
|
||||||
|
hl_group, list_sep.join(r)),
|
||||||
|
problem_mark=hl_group.mark
|
||||||
|
)
|
||||||
|
return True, False, True
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
def check_segment_data_key(key, data, context, echoerr):
|
||||||
|
havemarks(key)
|
||||||
|
has_module_name = '.' in key
|
||||||
|
found = False
|
||||||
|
for ext, theme in list_themes(data, context):
|
||||||
|
for segments in theme.get('segments', {}).values():
|
||||||
|
for segment in segments:
|
||||||
|
if 'name' in segment:
|
||||||
|
if key == segment['name']:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
function_name = segment.get('function')
|
||||||
|
if function_name:
|
||||||
|
module, function_name = get_function_strings(function_name, ((None, theme),), ext)
|
||||||
|
if has_module_name:
|
||||||
|
full_name = module + '.' + function_name
|
||||||
|
if key == full_name:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if key == function_name:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if data['theme_type'] != 'top':
|
||||||
|
echoerr(context='Error while checking segment data',
|
||||||
|
problem='found key {0} that cannot be associated with any segment'.format(key),
|
||||||
|
problem_mark=key.mark)
|
||||||
|
return True, False, True
|
||||||
|
|
||||||
|
return True, False, False
|
||||||
|
|
||||||
|
|
||||||
|
threaded_args_specs = {
|
||||||
|
'interval': Spec().cmp('gt', 0.0),
|
||||||
|
'update_first': Spec().type(bool),
|
||||||
|
'shutdown_event': Spec().error('Shutdown event must be set by powerline'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_args_variant(func, args, data, context, echoerr):
|
||||||
|
havemarks(args)
|
||||||
|
argspec = getconfigargspec(func)
|
||||||
|
present_args = set(args)
|
||||||
|
all_args = set(argspec.args)
|
||||||
|
required_args = set(argspec.args[:-len(argspec.defaults)])
|
||||||
|
|
||||||
|
hadproblem = False
|
||||||
|
|
||||||
|
if required_args - present_args:
|
||||||
|
echoerr(
|
||||||
|
context='Error while checking segment arguments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=args.mark,
|
||||||
|
problem='some of the required keys are missing: {0}'.format(list_sep.join(required_args - present_args))
|
||||||
|
)
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
if not all_args >= present_args:
|
||||||
|
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=args.mark,
|
||||||
|
problem='found unknown keys: {0}'.format(list_sep.join(present_args - all_args)),
|
||||||
|
problem_mark=next(iter(present_args - all_args)).mark)
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
if isinstance(func, ThreadedSegment):
|
||||||
|
for key in set(threaded_args_specs) & present_args:
|
||||||
|
proceed, khadproblem = threaded_args_specs[key].match(
|
||||||
|
args[key],
|
||||||
|
args.mark,
|
||||||
|
data,
|
||||||
|
context.enter_key(args, key),
|
||||||
|
echoerr
|
||||||
|
)
|
||||||
|
if khadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
if not proceed:
|
||||||
|
return hadproblem
|
||||||
|
|
||||||
|
return hadproblem
|
||||||
|
|
||||||
|
|
||||||
|
def check_args(get_functions, args, data, context, echoerr):
|
||||||
|
new_echoerr = DelayedEchoErr(echoerr)
|
||||||
|
count = 0
|
||||||
|
hadproblem = False
|
||||||
|
for func in get_functions(data, context, new_echoerr):
|
||||||
|
count += 1
|
||||||
|
shadproblem = check_args_variant(func, args, data, context, echoerr)
|
||||||
|
if shadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
|
||||||
|
if not count:
|
||||||
|
hadproblem = True
|
||||||
|
if new_echoerr:
|
||||||
|
new_echoerr.echo_all()
|
||||||
|
else:
|
||||||
|
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=context[-2][1].mark,
|
||||||
|
problem='no suitable segments found')
|
||||||
|
|
||||||
|
return True, False, hadproblem
|
||||||
|
|
||||||
|
|
||||||
|
def get_one_segment_function(data, context, echoerr):
|
||||||
|
ext = data['ext']
|
||||||
|
function_name = context[-2][1].get('function')
|
||||||
|
if function_name:
|
||||||
|
module, function_name = get_function_strings(function_name, context, ext)
|
||||||
|
func = import_segment(function_name, data, context, echoerr, module=module)
|
||||||
|
if func:
|
||||||
|
yield func
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_possible_functions(data, context, echoerr):
|
||||||
|
name = context[-2][0]
|
||||||
|
module, name = name.rpartition('.')[::2]
|
||||||
|
if module:
|
||||||
|
func = import_segment(name, data, context, echoerr, module=module)
|
||||||
|
if func:
|
||||||
|
yield func
|
||||||
|
else:
|
||||||
|
for ext, theme_config in list_themes(data, context):
|
||||||
|
for segments in theme_config.get('segments', {}).values():
|
||||||
|
for segment in segments:
|
||||||
|
if segment.get('type', 'function') == 'function':
|
||||||
|
function_name = segment.get('function')
|
||||||
|
current_name = segment.get('name')
|
||||||
|
if function_name:
|
||||||
|
module, function_name = get_function_strings(function_name, ((None, theme_config),), ext)
|
||||||
|
if current_name == name or function_name == name:
|
||||||
|
func = import_segment(function_name, data, context, echoerr, module=module)
|
||||||
|
if func:
|
||||||
|
yield func
|
||||||
|
|
||||||
|
|
||||||
|
def check_exinclude_function(name, data, context, echoerr):
|
||||||
|
ext = data['ext']
|
||||||
|
module, name = name.rpartition('.')[::2]
|
||||||
|
if not module:
|
||||||
|
module = MarkedUnicode('powerline.selectors.' + ext, None)
|
||||||
|
func = import_function('selector', name, data, context, echoerr, module=module)
|
||||||
|
if not func:
|
||||||
|
return True, False, True
|
||||||
|
return True, False, False
|
68
powerline/lint/context.py
Normal file
68
powerline/lint/context.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from powerline.lib.unicode import unicode
|
||||||
|
from powerline.lint.markedjson.markedvalue import MarkedUnicode
|
||||||
|
from powerline.lint.selfcheck import havemarks
|
||||||
|
|
||||||
|
|
||||||
|
class JStr(unicode):
|
||||||
|
def join(self, iterable):
|
||||||
|
return super(JStr, self).join((unicode(item) for item in iterable))
|
||||||
|
|
||||||
|
|
||||||
|
key_sep = JStr('/')
|
||||||
|
|
||||||
|
|
||||||
|
def list_themes(data, context):
|
||||||
|
theme_type = data['theme_type']
|
||||||
|
ext = data['ext']
|
||||||
|
main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None)
|
||||||
|
is_main_theme = (data['theme'] == main_theme_name)
|
||||||
|
if theme_type == 'top':
|
||||||
|
return list(itertools.chain(*[
|
||||||
|
[(theme_ext, theme) for theme in theme_configs.values()]
|
||||||
|
for theme_ext, theme_configs in data['theme_configs'].items()
|
||||||
|
]))
|
||||||
|
elif theme_type == 'main' or is_main_theme:
|
||||||
|
return [(ext, theme) for theme in data['ext_theme_configs'].values()]
|
||||||
|
else:
|
||||||
|
return [(ext, context[0][1])]
|
||||||
|
|
||||||
|
|
||||||
|
class Context(tuple):
|
||||||
|
for func in dir(tuple):
|
||||||
|
if func in ('__getitem__', '__init__', '__getattribute__', '__len__', '__iter__'):
|
||||||
|
continue
|
||||||
|
exec((
|
||||||
|
'def {0}(self, *args, **kwargs):\n'
|
||||||
|
' raise TypeError("{0} is not allowed for Context")'
|
||||||
|
).format(func))
|
||||||
|
del func
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __new__(cls, base, context_key=None, context_value=None):
|
||||||
|
if context_key is not None:
|
||||||
|
assert(context_value is not None)
|
||||||
|
assert(type(base) is Context)
|
||||||
|
havemarks(context_key, context_value)
|
||||||
|
return tuple.__new__(cls, tuple.__add__(base, ((context_key, context_value),)))
|
||||||
|
else:
|
||||||
|
havemarks(base)
|
||||||
|
return tuple.__new__(cls, ((MarkedUnicode('', base.mark), base),))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key(self):
|
||||||
|
return key_sep.join((c[0] for c in self))
|
||||||
|
|
||||||
|
def enter_key(self, value, key):
|
||||||
|
return self.enter(value.keydict[key], value[key])
|
||||||
|
|
||||||
|
def enter_item(self, name, item):
|
||||||
|
return self.enter(MarkedUnicode(name, item.mark), item)
|
||||||
|
|
||||||
|
def enter(self, context_key, context_value):
|
||||||
|
return Context.__new__(Context, self, context_key, context_value)
|
56
powerline/lint/imp.py
Normal file
56
powerline/lint/imp.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from powerline.lint.selfcheck import havemarks
|
||||||
|
|
||||||
|
|
||||||
|
class WithPath(object):
|
||||||
|
def __init__(self, import_paths):
|
||||||
|
self.import_paths = import_paths
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.oldpath = sys.path
|
||||||
|
sys.path = self.import_paths + sys.path
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
sys.path = self.oldpath
|
||||||
|
|
||||||
|
|
||||||
|
def import_function(function_type, name, data, context, echoerr, module):
|
||||||
|
havemarks(name, module)
|
||||||
|
|
||||||
|
if module == 'powerline.segments.common':
|
||||||
|
echoerr(context='Warning while checking segments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=name.mark,
|
||||||
|
problem='module {0} is deprecated'.format(module),
|
||||||
|
problem_mark=module.mark)
|
||||||
|
|
||||||
|
with WithPath(data['import_paths']):
|
||||||
|
try:
|
||||||
|
func = getattr(__import__(str(module), fromlist=[str(name)]), str(name))
|
||||||
|
except ImportError:
|
||||||
|
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=name.mark,
|
||||||
|
problem='failed to import module {0}'.format(module),
|
||||||
|
problem_mark=module.mark)
|
||||||
|
return None
|
||||||
|
except AttributeError:
|
||||||
|
echoerr(context='Error while loading {0} function (key {key})'.format(function_type, key=context.key),
|
||||||
|
problem='failed to load function {0} from module {1}'.format(name, module),
|
||||||
|
problem_mark=name.mark)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not callable(func):
|
||||||
|
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||||
|
context_mark=name.mark,
|
||||||
|
problem='imported “function” {0} from module {1} is not callable'.format(name, module),
|
||||||
|
problem_mark=module.mark)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def import_segment(*args, **kwargs):
|
||||||
|
return import_function('segment', *args, **kwargs)
|
@ -5,10 +5,12 @@ from powerline.lint.markedjson.loader import Loader
|
|||||||
|
|
||||||
|
|
||||||
def load(stream, Loader=Loader):
|
def load(stream, Loader=Loader):
|
||||||
"""
|
'''Parse JSON value and produce the corresponding Python object
|
||||||
Parse the first YAML document in a stream
|
|
||||||
and produce the corresponding Python object.
|
:return:
|
||||||
"""
|
(hadproblem, object) where first argument is true if there were errors
|
||||||
|
during loading JSON stream and second is the corresponding JSON object.
|
||||||
|
'''
|
||||||
loader = Loader(stream)
|
loader = Loader(stream)
|
||||||
try:
|
try:
|
||||||
r = loader.get_single_data()
|
r = loader.get_single_data()
|
||||||
|
@ -43,9 +43,9 @@ class Composer:
|
|||||||
if not self.check_event(events.StreamEndEvent):
|
if not self.check_event(events.StreamEndEvent):
|
||||||
event = self.get_event()
|
event = self.get_event()
|
||||||
raise ComposerError(
|
raise ComposerError(
|
||||||
"expected a single document in the stream",
|
'expected a single document in the stream',
|
||||||
document.start_mark,
|
document.start_mark,
|
||||||
"but found another document",
|
'but found another document',
|
||||||
event.start_mark
|
event.start_mark
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,8 +109,8 @@ class Composer:
|
|||||||
# key_event = self.peek_event()
|
# key_event = self.peek_event()
|
||||||
item_key = self.compose_node(node, None)
|
item_key = self.compose_node(node, None)
|
||||||
# if item_key in node.value:
|
# if item_key in node.value:
|
||||||
# raise ComposerError("while composing a mapping", start_event.start_mark,
|
# raise ComposerError('while composing a mapping', start_event.start_mark,
|
||||||
# "found duplicate key", key_event.start_mark)
|
# 'found duplicate key', key_event.start_mark)
|
||||||
item_value = self.compose_node(node, item_key)
|
item_value = self.compose_node(node, item_key)
|
||||||
# node.value[item_key] = item_value
|
# node.value[item_key] = item_value
|
||||||
node.value.append((item_key, item_value))
|
node.value.append((item_key, item_value))
|
||||||
|
@ -94,7 +94,7 @@ class BaseConstructor:
|
|||||||
if not isinstance(node, nodes.ScalarNode):
|
if not isinstance(node, nodes.ScalarNode):
|
||||||
raise ConstructorError(
|
raise ConstructorError(
|
||||||
None, None,
|
None, None,
|
||||||
"expected a scalar node, but found %s" % node.id,
|
'expected a scalar node, but found %s' % node.id,
|
||||||
node.start_mark
|
node.start_mark
|
||||||
)
|
)
|
||||||
return node.value
|
return node.value
|
||||||
@ -103,7 +103,7 @@ class BaseConstructor:
|
|||||||
if not isinstance(node, nodes.SequenceNode):
|
if not isinstance(node, nodes.SequenceNode):
|
||||||
raise ConstructorError(
|
raise ConstructorError(
|
||||||
None, None,
|
None, None,
|
||||||
"expected a sequence node, but found %s" % node.id,
|
'expected a sequence node, but found %s' % node.id,
|
||||||
node.start_mark
|
node.start_mark
|
||||||
)
|
)
|
||||||
return [
|
return [
|
||||||
@ -116,7 +116,7 @@ class BaseConstructor:
|
|||||||
if not isinstance(node, nodes.MappingNode):
|
if not isinstance(node, nodes.MappingNode):
|
||||||
raise ConstructorError(
|
raise ConstructorError(
|
||||||
None, None,
|
None, None,
|
||||||
"expected a mapping node, but found %s" % node.id,
|
'expected a mapping node, but found %s' % node.id,
|
||||||
node.start_mark
|
node.start_mark
|
||||||
)
|
)
|
||||||
mapping = {}
|
mapping = {}
|
||||||
@ -174,9 +174,9 @@ class Constructor(BaseConstructor):
|
|||||||
for subnode in value_node.value:
|
for subnode in value_node.value:
|
||||||
if not isinstance(subnode, nodes.MappingNode):
|
if not isinstance(subnode, nodes.MappingNode):
|
||||||
raise ConstructorError(
|
raise ConstructorError(
|
||||||
"while constructing a mapping",
|
'while constructing a mapping',
|
||||||
node.start_mark,
|
node.start_mark,
|
||||||
"expected a mapping for merging, but found %s" % subnode.id,
|
'expected a mapping for merging, but found %s' % subnode.id,
|
||||||
subnode.start_mark
|
subnode.start_mark
|
||||||
)
|
)
|
||||||
self.flatten_mapping(subnode)
|
self.flatten_mapping(subnode)
|
||||||
@ -186,9 +186,9 @@ class Constructor(BaseConstructor):
|
|||||||
merge.extend(value)
|
merge.extend(value)
|
||||||
else:
|
else:
|
||||||
raise ConstructorError(
|
raise ConstructorError(
|
||||||
"while constructing a mapping",
|
'while constructing a mapping',
|
||||||
node.start_mark,
|
node.start_mark,
|
||||||
("expected a mapping or list of mappings for merging, but found %s" % value_node.id),
|
('expected a mapping or list of mappings for merging, but found %s' % value_node.id),
|
||||||
value_node.start_mark
|
value_node.start_mark
|
||||||
)
|
)
|
||||||
elif key_node.tag == 'tag:yaml.org,2002:value':
|
elif key_node.tag == 'tag:yaml.org,2002:value':
|
||||||
@ -255,7 +255,7 @@ class Constructor(BaseConstructor):
|
|||||||
def construct_undefined(self, node):
|
def construct_undefined(self, node):
|
||||||
raise ConstructorError(
|
raise ConstructorError(
|
||||||
None, None,
|
None, None,
|
||||||
"could not determine a constructor for the tag %r" % node.tag,
|
'could not determine a constructor for the tag %r' % node.tag,
|
||||||
node.start_mark
|
node.start_mark
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,10 +57,10 @@ class Mark:
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
snippet = self.get_snippet()
|
snippet = self.get_snippet()
|
||||||
where = (" in \"%s\", line %d, column %d" % (
|
where = (' in "%s", line %d, column %d' % (
|
||||||
self.name, self.line + 1, self.column + 1))
|
self.name, self.line + 1, self.column + 1))
|
||||||
if snippet is not None:
|
if snippet is not None:
|
||||||
where += ":\n" + snippet
|
where += ':\n' + snippet
|
||||||
if type(where) is str:
|
if type(where) is str:
|
||||||
return where
|
return where
|
||||||
else:
|
else:
|
||||||
@ -68,8 +68,9 @@ class Mark:
|
|||||||
|
|
||||||
|
|
||||||
def echoerr(*args, **kwargs):
|
def echoerr(*args, **kwargs):
|
||||||
sys.stderr.write('\n')
|
stream = kwargs.pop('stream', sys.stderr)
|
||||||
sys.stderr.write(format_error(*args, **kwargs) + '\n')
|
stream.write('\n')
|
||||||
|
stream.write(format_error(*args, **kwargs) + '\n')
|
||||||
|
|
||||||
|
|
||||||
def format_error(context=None, context_mark=None, problem=None, problem_mark=None, note=None):
|
def format_error(context=None, context_mark=None, problem=None, problem_mark=None, note=None):
|
||||||
@ -98,3 +99,34 @@ def format_error(context=None, context_mark=None, problem=None, problem_mark=Non
|
|||||||
class MarkedError(Exception):
|
class MarkedError(Exception):
|
||||||
def __init__(self, context=None, context_mark=None, problem=None, problem_mark=None, note=None):
|
def __init__(self, context=None, context_mark=None, problem=None, problem_mark=None, note=None):
|
||||||
Exception.__init__(self, format_error(context, context_mark, problem, problem_mark, note))
|
Exception.__init__(self, format_error(context, context_mark, problem, problem_mark, note))
|
||||||
|
|
||||||
|
|
||||||
|
class EchoErr(object):
|
||||||
|
__slots__ = ('echoerr', 'logger',)
|
||||||
|
|
||||||
|
def __init__(self, echoerr, logger):
|
||||||
|
self.echoerr = echoerr
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
self.echoerr(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class DelayedEchoErr(EchoErr):
|
||||||
|
__slots__ = ('echoerr', 'logger', 'errs')
|
||||||
|
|
||||||
|
def __init__(self, echoerr):
|
||||||
|
super(DelayedEchoErr, self).__init__(echoerr, echoerr.logger)
|
||||||
|
self.errs = []
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
self.errs.append((args, kwargs))
|
||||||
|
|
||||||
|
def echo_all(self):
|
||||||
|
for args, kwargs in self.errs:
|
||||||
|
self.echoerr(*args, **kwargs)
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return not not self.errs
|
||||||
|
|
||||||
|
__bool__ = __nonzero__
|
||||||
|
@ -88,7 +88,7 @@ class Parser:
|
|||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
self.echoerr(
|
self.echoerr(
|
||||||
None, None,
|
None, None,
|
||||||
("expected '<stream end>', but found %r" % token.id), token.start_mark
|
('expected \'<stream end>\', but found %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
return events.StreamEndEvent(token.start_mark, token.end_mark)
|
return events.StreamEndEvent(token.start_mark, token.end_mark)
|
||||||
else:
|
else:
|
||||||
@ -141,8 +141,8 @@ class Parser:
|
|||||||
else:
|
else:
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
raise ParserError(
|
raise ParserError(
|
||||||
"while parsing a flow node", start_mark,
|
'while parsing a flow node', start_mark,
|
||||||
"expected the node content, but found %r" % token.id,
|
'expected the node content, but found %r' % token.id,
|
||||||
token.start_mark
|
token.start_mark
|
||||||
)
|
)
|
||||||
return event
|
return event
|
||||||
@ -160,14 +160,14 @@ class Parser:
|
|||||||
if self.check_token(tokens.FlowSequenceEndToken):
|
if self.check_token(tokens.FlowSequenceEndToken):
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
self.echoerr(
|
self.echoerr(
|
||||||
"While parsing a flow sequence", self.marks[-1],
|
'While parsing a flow sequence', self.marks[-1],
|
||||||
("expected sequence value, but got %r" % token.id), token.start_mark
|
('expected sequence value, but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
raise ParserError(
|
raise ParserError(
|
||||||
"while parsing a flow sequence", self.marks[-1],
|
'while parsing a flow sequence', self.marks[-1],
|
||||||
("expected ',' or ']', but got %r" % token.id), token.start_mark
|
('expected \',\' or \']\', but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.check_token(tokens.FlowSequenceEndToken):
|
if not self.check_token(tokens.FlowSequenceEndToken):
|
||||||
@ -197,14 +197,14 @@ class Parser:
|
|||||||
if self.check_token(tokens.FlowMappingEndToken):
|
if self.check_token(tokens.FlowMappingEndToken):
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
self.echoerr(
|
self.echoerr(
|
||||||
"While parsing a flow mapping", self.marks[-1],
|
'While parsing a flow mapping', self.marks[-1],
|
||||||
("expected mapping key, but got %r" % token.id), token.start_mark
|
('expected mapping key, but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
raise ParserError(
|
raise ParserError(
|
||||||
"while parsing a flow mapping", self.marks[-1],
|
'while parsing a flow mapping', self.marks[-1],
|
||||||
("expected ',' or '}', but got %r" % token.id), token.start_mark
|
('expected \',\' or \'}\', but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
if self.check_token(tokens.KeyToken):
|
if self.check_token(tokens.KeyToken):
|
||||||
token = self.get_token()
|
token = self.get_token()
|
||||||
@ -214,8 +214,8 @@ class Parser:
|
|||||||
else:
|
else:
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
raise ParserError(
|
raise ParserError(
|
||||||
"while parsing a flow mapping", self.marks[-1],
|
'while parsing a flow mapping', self.marks[-1],
|
||||||
("expected value, but got %r" % token.id), token.start_mark
|
('expected value, but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
elif not self.check_token(tokens.FlowMappingEndToken):
|
elif not self.check_token(tokens.FlowMappingEndToken):
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
@ -226,14 +226,14 @@ class Parser:
|
|||||||
|
|
||||||
if expect_key:
|
if expect_key:
|
||||||
raise ParserError(
|
raise ParserError(
|
||||||
"while parsing a flow mapping", self.marks[-1],
|
'while parsing a flow mapping', self.marks[-1],
|
||||||
("expected string key, but got %r" % token.id), token.start_mark
|
('expected string key, but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
raise ParserError(
|
raise ParserError(
|
||||||
"while parsing a flow mapping", self.marks[-1],
|
'while parsing a flow mapping', self.marks[-1],
|
||||||
("expected ':', but got %r" % token.id), token.start_mark
|
('expected \':\', but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
token = self.get_token()
|
token = self.get_token()
|
||||||
event = events.MappingEndEvent(token.start_mark, token.end_mark)
|
event = events.MappingEndEvent(token.start_mark, token.end_mark)
|
||||||
@ -250,6 +250,6 @@ class Parser:
|
|||||||
|
|
||||||
token = self.peek_token()
|
token = self.peek_token()
|
||||||
raise ParserError(
|
raise ParserError(
|
||||||
"while parsing a flow mapping", self.marks[-1],
|
'while parsing a flow mapping', self.marks[-1],
|
||||||
("expected mapping value, but got %r" % token.id), token.start_mark
|
('expected mapping value, but got %r' % token.id), token.start_mark
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ from powerline.lint.markedjson.error import MarkedError, Mark, NON_PRINTABLE
|
|||||||
from powerline.lib.unicode import unicode
|
from powerline.lib.unicode import unicode
|
||||||
|
|
||||||
|
|
||||||
# This module contains abstractions for the input stream. You don't have to
|
# This module contains abstractions for the input stream. You don’t have to
|
||||||
# looks further, there are no pretty code.
|
# looks further, there are no pretty code.
|
||||||
|
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class Reader(object):
|
|||||||
# Reader accepts
|
# Reader accepts
|
||||||
# - a file-like object with its `read` method returning `str`,
|
# - a file-like object with its `read` method returning `str`,
|
||||||
|
|
||||||
# Yeah, it's ugly and slow.
|
# Yeah, it’s ugly and slow.
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.name = None
|
self.name = None
|
||||||
self.stream = None
|
self.stream = None
|
||||||
@ -42,7 +42,7 @@ class Reader(object):
|
|||||||
self.column = 0
|
self.column = 0
|
||||||
|
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.name = getattr(stream, 'name', "<file>")
|
self.name = getattr(stream, 'name', '<file>')
|
||||||
self.eof = False
|
self.eof = False
|
||||||
self.raw_buffer = None
|
self.raw_buffer = None
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class SimpleKey:
|
|||||||
|
|
||||||
class Scanner:
|
class Scanner:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the scanner."""
|
'''Initialize the scanner.'''
|
||||||
# It is assumed that Scanner and Reader will have a common descendant.
|
# It is assumed that Scanner and Reader will have a common descendant.
|
||||||
# Reader do the dirty work of checking for BOM and converting the
|
# Reader do the dirty work of checking for BOM and converting the
|
||||||
# input data to Unicode. It also adds NUL to the end.
|
# input data to Unicode. It also adds NUL to the end.
|
||||||
@ -168,17 +168,17 @@ class Scanner:
|
|||||||
return self.fetch_value()
|
return self.fetch_value()
|
||||||
|
|
||||||
# Is it a double quoted scalar?
|
# Is it a double quoted scalar?
|
||||||
if ch == '\"':
|
if ch == '"':
|
||||||
return self.fetch_double()
|
return self.fetch_double()
|
||||||
|
|
||||||
# It must be a plain scalar then.
|
# It must be a plain scalar then.
|
||||||
if self.check_plain():
|
if self.check_plain():
|
||||||
return self.fetch_plain()
|
return self.fetch_plain()
|
||||||
|
|
||||||
# No? It's an error. Let's produce a nice error message.
|
# No? It’s an error. Let’s produce a nice error message.
|
||||||
raise ScannerError(
|
raise ScannerError(
|
||||||
"while scanning for the next token", None,
|
'while scanning for the next token', None,
|
||||||
"found character %r that cannot start any token" % ch,
|
'found character %r that cannot start any token' % ch,
|
||||||
self.get_mark()
|
self.get_mark()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ class Scanner:
|
|||||||
|
|
||||||
def next_possible_simple_key(self):
|
def next_possible_simple_key(self):
|
||||||
# Return the number of the nearest possible simple key. Actually we
|
# Return the number of the nearest possible simple key. Actually we
|
||||||
# don't need to loop through the whole dictionary. We may replace it
|
# don’t need to loop through the whole dictionary. We may replace it
|
||||||
# with the following code:
|
# with the following code:
|
||||||
# if not self.possible_simple_keys:
|
# if not self.possible_simple_keys:
|
||||||
# return None
|
# return None
|
||||||
@ -211,11 +211,11 @@ class Scanner:
|
|||||||
del self.possible_simple_keys[level]
|
del self.possible_simple_keys[level]
|
||||||
|
|
||||||
def save_possible_simple_key(self):
|
def save_possible_simple_key(self):
|
||||||
# The next token may start a simple key. We check if it's possible
|
# The next token may start a simple key. We check if it’s possible
|
||||||
# and save its position. This function is called for
|
# and save its position. This function is called for
|
||||||
# SCALAR(flow), '[', and '{'.
|
# SCALAR(flow), '[', and '{'.
|
||||||
|
|
||||||
# The next token might be a simple key. Let's save it's number and
|
# The next token might be a simple key. Let’s save it’s number and
|
||||||
# position.
|
# position.
|
||||||
if self.allow_simple_key:
|
if self.allow_simple_key:
|
||||||
self.remove_possible_simple_key()
|
self.remove_possible_simple_key()
|
||||||
@ -364,7 +364,7 @@ class Scanner:
|
|||||||
def scan_flow_scalar(self):
|
def scan_flow_scalar(self):
|
||||||
# See the specification for details.
|
# See the specification for details.
|
||||||
# Note that we loose indentation rules for quoted scalars. Quoted
|
# Note that we loose indentation rules for quoted scalars. Quoted
|
||||||
# scalars don't need to adhere indentation because " and ' clearly
|
# scalars don’t need to adhere indentation because " and ' clearly
|
||||||
# mark the beginning and the end of them. Therefore we are less
|
# mark the beginning and the end of them. Therefore we are less
|
||||||
# restrictive then the specification requires. We only need to check
|
# restrictive then the specification requires. We only need to check
|
||||||
# that document separators are not included in scalars.
|
# that document separators are not included in scalars.
|
||||||
@ -386,7 +386,7 @@ class Scanner:
|
|||||||
'n': '\x0A',
|
'n': '\x0A',
|
||||||
'f': '\x0C',
|
'f': '\x0C',
|
||||||
'r': '\x0D',
|
'r': '\x0D',
|
||||||
'\"': '\"',
|
'"': '\"',
|
||||||
'\\': '\\',
|
'\\': '\\',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,8 +417,8 @@ class Scanner:
|
|||||||
for k in range(length):
|
for k in range(length):
|
||||||
if self.peek(k) not in '0123456789ABCDEFabcdef':
|
if self.peek(k) not in '0123456789ABCDEFabcdef':
|
||||||
raise ScannerError(
|
raise ScannerError(
|
||||||
"while scanning a double-quoted scalar", start_mark,
|
'while scanning a double-quoted scalar', start_mark,
|
||||||
"expected escape sequence of %d hexdecimal numbers, but found %r" % (
|
'expected escape sequence of %d hexdecimal numbers, but found %r' % (
|
||||||
length, self.peek(k)),
|
length, self.peek(k)),
|
||||||
self.get_mark()
|
self.get_mark()
|
||||||
)
|
)
|
||||||
@ -427,8 +427,8 @@ class Scanner:
|
|||||||
self.forward(length)
|
self.forward(length)
|
||||||
else:
|
else:
|
||||||
raise ScannerError(
|
raise ScannerError(
|
||||||
"while scanning a double-quoted scalar", start_mark,
|
'while scanning a double-quoted scalar', start_mark,
|
||||||
("found unknown escape character %r" % ch), self.get_mark()
|
('found unknown escape character %r' % ch), self.get_mark()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return chunks
|
return chunks
|
||||||
@ -444,13 +444,13 @@ class Scanner:
|
|||||||
ch = self.peek()
|
ch = self.peek()
|
||||||
if ch == '\0':
|
if ch == '\0':
|
||||||
raise ScannerError(
|
raise ScannerError(
|
||||||
"while scanning a quoted scalar", start_mark,
|
'while scanning a quoted scalar', start_mark,
|
||||||
"found unexpected end of stream", self.get_mark()
|
'found unexpected end of stream', self.get_mark()
|
||||||
)
|
)
|
||||||
elif ch == '\n':
|
elif ch == '\n':
|
||||||
raise ScannerError(
|
raise ScannerError(
|
||||||
"while scanning a quoted scalar", start_mark,
|
'while scanning a quoted scalar', start_mark,
|
||||||
"found unexpected line end", self.get_mark()
|
'found unexpected line end', self.get_mark()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
chunks.append(whitespaces)
|
chunks.append(whitespaces)
|
||||||
|
16
powerline/lint/selfcheck.py
Normal file
16
powerline/lint/selfcheck.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
from powerline.lib.unicode import unicode
|
||||||
|
|
||||||
|
|
||||||
|
def havemarks(*args, **kwargs):
|
||||||
|
origin = kwargs.get('origin', '')
|
||||||
|
for i, v in enumerate(args):
|
||||||
|
if not hasattr(v, 'mark'):
|
||||||
|
raise AssertionError('Value #{0}/{1} ({2!r}) has no attribute `mark`'.format(origin, i, v))
|
||||||
|
if isinstance(v, dict):
|
||||||
|
for key, val in v.items():
|
||||||
|
havemarks(key, val, origin=(origin + '[' + unicode(i) + ']/' + unicode(key)))
|
||||||
|
elif isinstance(v, list):
|
||||||
|
havemarks(*v, origin=(origin + '[' + unicode(i) + ']'))
|
724
powerline/lint/spec.py
Normal file
724
powerline/lint/spec.py
Normal file
@ -0,0 +1,724 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import re
|
||||||
|
|
||||||
|
from copy import copy
|
||||||
|
|
||||||
|
from powerline.lib.unicode import unicode
|
||||||
|
from powerline.lint.markedjson.error import echoerr, DelayedEchoErr
|
||||||
|
from powerline.lint.selfcheck import havemarks
|
||||||
|
|
||||||
|
|
||||||
|
class Spec(object):
|
||||||
|
'''Class that describes some JSON value
|
||||||
|
|
||||||
|
In powerline it is only used to describe JSON values stored in powerline
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
:param dict keys:
|
||||||
|
Dictionary that maps keys that may be present in the given JSON
|
||||||
|
dictionary to their descriptions. If this parameter is not empty it
|
||||||
|
implies that described value has dictionary type. Non-dictionary types
|
||||||
|
must be described using ``Spec()``: without arguments.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Methods that create the specifications return ``self``, so calls to them
|
||||||
|
may be chained: ``Spec().type(unicode).re('^\w+$')``. This does not
|
||||||
|
apply to functions that *apply* specification like :py:meth`Spec.match`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Methods starting with ``check_`` return two values: first determines
|
||||||
|
whether caller should proceed on running other checks, second
|
||||||
|
determines whether there were any problems (i.e. whether error was
|
||||||
|
reported). One should not call these methods directly: there is
|
||||||
|
:py:meth:`Spec.match` method for checking values.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
In ``check_`` and ``match`` methods specifications are identified by
|
||||||
|
their indexes for the purpose of simplyfying :py:meth:`Spec.copy`
|
||||||
|
method.
|
||||||
|
|
||||||
|
Some common parameters:
|
||||||
|
|
||||||
|
``data``:
|
||||||
|
Whatever data supplied by the first caller for checker functions. Is not
|
||||||
|
processed by :py:class:`Spec` methods in any fashion.
|
||||||
|
``context``:
|
||||||
|
:py:class:`powerline.lint.context.Context` instance, describes context
|
||||||
|
of the value. :py:class:`Spec` methods only use its ``.key`` methods for
|
||||||
|
error messages.
|
||||||
|
``echoerr``:
|
||||||
|
Callable that should be used to echo errors. Is supposed to take four
|
||||||
|
optional keyword arguments: ``problem``, ``problem_mark``, ``context``,
|
||||||
|
``context_mark``.
|
||||||
|
``value``:
|
||||||
|
Checked value.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, **keys):
|
||||||
|
self.specs = []
|
||||||
|
self.keys = {}
|
||||||
|
self.checks = []
|
||||||
|
self.cmsg = ''
|
||||||
|
self.isoptional = False
|
||||||
|
self.uspecs = []
|
||||||
|
self.ufailmsg = lambda key: 'found unknown key: {0}'.format(key)
|
||||||
|
self.did_type = False
|
||||||
|
self.update(**keys)
|
||||||
|
|
||||||
|
def update(self, **keys):
|
||||||
|
'''Describe additional keys that may be present in given JSON value
|
||||||
|
|
||||||
|
If called with some keyword arguments implies that described value is
|
||||||
|
a dictionary. If called without keyword parameters it is no-op.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
for k, v in keys.items():
|
||||||
|
self.keys[k] = len(self.specs)
|
||||||
|
self.specs.append(v)
|
||||||
|
if self.keys and not self.did_type:
|
||||||
|
self.type(dict)
|
||||||
|
self.did_type = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
def copy(self, copied=None):
|
||||||
|
'''Deep copy the spec
|
||||||
|
|
||||||
|
:param dict copied:
|
||||||
|
Internal dictionary used for storing already copied values. This
|
||||||
|
parameter should not be used.
|
||||||
|
|
||||||
|
:return: New :py:class:`Spec` object that is a deep copy of ``self``.
|
||||||
|
'''
|
||||||
|
copied = copied or {}
|
||||||
|
try:
|
||||||
|
return copied[id(self)]
|
||||||
|
except KeyError:
|
||||||
|
instance = self.__class__()
|
||||||
|
copied[id(self)] = instance
|
||||||
|
return self.__class__()._update(self.__dict__, copied)
|
||||||
|
|
||||||
|
def _update(self, d, copied):
|
||||||
|
'''Helper for the :py:meth:`Spec.copy` function
|
||||||
|
|
||||||
|
Populates new instance with values taken from the old one.
|
||||||
|
|
||||||
|
:param dict d:
|
||||||
|
``__dict__`` of the old instance.
|
||||||
|
:param dict copied:
|
||||||
|
Storage for already copied values.
|
||||||
|
'''
|
||||||
|
self.__dict__.update(d)
|
||||||
|
self.keys = copy(self.keys)
|
||||||
|
self.checks = copy(self.checks)
|
||||||
|
self.uspecs = copy(self.uspecs)
|
||||||
|
self.specs = [spec.copy(copied) for spec in self.specs]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def unknown_spec(self, keyfunc, spec):
|
||||||
|
'''Define specification for non-static keys
|
||||||
|
|
||||||
|
This method should be used if key names cannot be determined at runtime
|
||||||
|
or if a number of keys share identical spec (in order to not repeat it).
|
||||||
|
:py:meth:`Spec.match` method processes dictionary in the given order:
|
||||||
|
|
||||||
|
* First it tries to use specifications provided at the initialization or
|
||||||
|
by the :py:meth:`Spec.update` method.
|
||||||
|
* If no specification for given key was provided it processes
|
||||||
|
specifications from ``keyfunc`` argument in order they were supplied.
|
||||||
|
Once some key matches specification supplied second ``spec`` argument
|
||||||
|
is used to determine correctness of the value.
|
||||||
|
|
||||||
|
:param Spec keyfunc:
|
||||||
|
:py:class:`Spec` instance or a regular function that returns two
|
||||||
|
values (the same :py:meth:`Spec.match` returns). This argument is
|
||||||
|
used to match keys that were not provided at initialization or via
|
||||||
|
:py:meth:`Spec.update`.
|
||||||
|
:param Spec spec:
|
||||||
|
:py:class:`Spec` instance that will be used to check keys matched by
|
||||||
|
``keyfunc``.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
if isinstance(keyfunc, Spec):
|
||||||
|
self.specs.append(keyfunc)
|
||||||
|
keyfunc = len(self.specs) - 1
|
||||||
|
self.specs.append(spec)
|
||||||
|
self.uspecs.append((keyfunc, len(self.specs) - 1))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def unknown_msg(self, msgfunc):
|
||||||
|
'''Define message which will be used when unknown key was found
|
||||||
|
|
||||||
|
“Unknown” is a key that was not provided at the initialization and via
|
||||||
|
:py:meth:`Spec.update` and did not match any ``keyfunc`` proided via
|
||||||
|
:py:meth:`Spec.unknown_spec`.
|
||||||
|
|
||||||
|
:param msgfunc:
|
||||||
|
Function that takes that unknown key as an argument and returns the
|
||||||
|
message text. Text will appear at the top (start of the sentence).
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.ufailmsg = msgfunc
|
||||||
|
return self
|
||||||
|
|
||||||
|
def context_message(self, msg):
|
||||||
|
'''Define message that describes context
|
||||||
|
|
||||||
|
:param str msg:
|
||||||
|
Message that describes context. Is written using the
|
||||||
|
:py:meth:`str.format` syntax and is expected to display keyword
|
||||||
|
parameter ``key``.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.cmsg = msg
|
||||||
|
for spec in self.specs:
|
||||||
|
if not spec.cmsg:
|
||||||
|
spec.context_message(msg)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def check_type(self, value, context_mark, data, context, echoerr, types):
|
||||||
|
'''Check that given value matches given type(s)
|
||||||
|
|
||||||
|
:param tuple types:
|
||||||
|
List of accepted types. Since :py:class:`Spec` is supposed to
|
||||||
|
describe JSON values only ``dict``, ``list``, ``unicode``, ``bool``,
|
||||||
|
``float`` and ``NoneType`` types make any sense.
|
||||||
|
|
||||||
|
:return: proceed, hadproblem.
|
||||||
|
'''
|
||||||
|
havemarks(value)
|
||||||
|
if type(value.value) not in types:
|
||||||
|
echoerr(
|
||||||
|
context=self.cmsg.format(key=context.key),
|
||||||
|
context_mark=context_mark,
|
||||||
|
problem='{0!r} must be a {1} instance, not {2}'.format(
|
||||||
|
value,
|
||||||
|
', '.join((t.__name__ for t in types)),
|
||||||
|
type(value.value).__name__
|
||||||
|
),
|
||||||
|
problem_mark=value.mark
|
||||||
|
)
|
||||||
|
return False, True
|
||||||
|
return True, False
|
||||||
|
|
||||||
|
def check_func(self, value, context_mark, data, context, echoerr, func, msg_func):
|
||||||
|
'''Check value using given function
|
||||||
|
|
||||||
|
:param function func:
|
||||||
|
Callable that should accept four positional parameters:
|
||||||
|
|
||||||
|
#. checked value,
|
||||||
|
#. ``data`` parameter with arbitrary data (supplied by top-level
|
||||||
|
caller),
|
||||||
|
#. current context and
|
||||||
|
#. function used for echoing errors.
|
||||||
|
|
||||||
|
This callable should return three values:
|
||||||
|
|
||||||
|
#. determines whether ``check_func`` caller should proceed
|
||||||
|
calling other checks,
|
||||||
|
#. determines whether ``check_func`` should echo error on its own
|
||||||
|
(it should be set to False if ``func`` echoes error itself) and
|
||||||
|
#. determines whether function has found some errors in the checked
|
||||||
|
value.
|
||||||
|
|
||||||
|
:param function msg_func:
|
||||||
|
Callable that takes checked value as the only positional parameter
|
||||||
|
and returns a string that describes the problem. Only useful for
|
||||||
|
small checker functions since it is ignored when second returned
|
||||||
|
value is false.
|
||||||
|
|
||||||
|
:return: proceed, hadproblem.
|
||||||
|
'''
|
||||||
|
havemarks(value)
|
||||||
|
proceed, echo, hadproblem = func(value, data, context, echoerr)
|
||||||
|
if echo and hadproblem:
|
||||||
|
echoerr(context=self.cmsg.format(key=context.key),
|
||||||
|
context_mark=context_mark,
|
||||||
|
problem=msg_func(value),
|
||||||
|
problem_mark=value.mark)
|
||||||
|
return proceed, hadproblem
|
||||||
|
|
||||||
|
def check_list(self, value, context_mark, data, context, echoerr, item_func, msg_func):
|
||||||
|
'''Check that each value in the list matches given specification
|
||||||
|
|
||||||
|
:param function item_func:
|
||||||
|
Callable like ``func`` from :py:meth:`Spec.check_func`. Unlike
|
||||||
|
``func`` this callable is called for each value in the list and may
|
||||||
|
be a :py:class:`Spec` object index.
|
||||||
|
:param func msg_func:
|
||||||
|
Callable like ``msg_func`` from :py:meth:`Spec.check_func`. Should
|
||||||
|
accept one problematic item and is not used for :py:class:`Spec`
|
||||||
|
object indicies in ``item_func`` method.
|
||||||
|
|
||||||
|
:return: proceed, hadproblem.
|
||||||
|
'''
|
||||||
|
havemarks(value)
|
||||||
|
i = 0
|
||||||
|
hadproblem = False
|
||||||
|
for item in value:
|
||||||
|
havemarks(item)
|
||||||
|
if isinstance(item_func, int):
|
||||||
|
spec = self.specs[item_func]
|
||||||
|
proceed, fhadproblem = spec.match(
|
||||||
|
item,
|
||||||
|
value.mark,
|
||||||
|
data,
|
||||||
|
context.enter_item('list item ' + unicode(i), item),
|
||||||
|
echoerr
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
proceed, echo, fhadproblem = item_func(item, data, context, echoerr)
|
||||||
|
if echo and fhadproblem:
|
||||||
|
echoerr(context=self.cmsg.format(key=context.key + '/list item ' + unicode(i)),
|
||||||
|
context_mark=value.mark,
|
||||||
|
problem=msg_func(item),
|
||||||
|
problem_mark=item.mark)
|
||||||
|
if fhadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
if not proceed:
|
||||||
|
return proceed, hadproblem
|
||||||
|
i += 1
|
||||||
|
return True, hadproblem
|
||||||
|
|
||||||
|
def check_either(self, value, context_mark, data, context, echoerr, start, end):
|
||||||
|
'''Check that given value matches one of the given specifications
|
||||||
|
|
||||||
|
:param int start:
|
||||||
|
First specification index.
|
||||||
|
:param int end:
|
||||||
|
Specification index that is greater by 1 then last specification
|
||||||
|
index.
|
||||||
|
|
||||||
|
This method does not give an error if any specification from
|
||||||
|
``self.specs[start:end]`` is matched by the given value.
|
||||||
|
'''
|
||||||
|
havemarks(value)
|
||||||
|
new_echoerr = DelayedEchoErr(echoerr)
|
||||||
|
|
||||||
|
hadproblem = False
|
||||||
|
for spec in self.specs[start:end]:
|
||||||
|
proceed, hadproblem = spec.match(value, value.mark, data, context, new_echoerr)
|
||||||
|
if not proceed:
|
||||||
|
break
|
||||||
|
if not hadproblem:
|
||||||
|
return True, False
|
||||||
|
|
||||||
|
new_echoerr.echo_all()
|
||||||
|
|
||||||
|
return False, hadproblem
|
||||||
|
|
||||||
|
def check_tuple(self, value, context_mark, data, context, echoerr, start, end):
|
||||||
|
'''Check that given value is a list with items matching specifications
|
||||||
|
|
||||||
|
:param int start:
|
||||||
|
First specification index.
|
||||||
|
:param int end:
|
||||||
|
Specification index that is greater by 1 then last specification
|
||||||
|
index.
|
||||||
|
|
||||||
|
This method checks that each item in the value list matches
|
||||||
|
specification with index ``start + item_number``.
|
||||||
|
'''
|
||||||
|
havemarks(value)
|
||||||
|
hadproblem = False
|
||||||
|
for (i, item, spec) in zip(itertools.count(), value, self.specs[start:end]):
|
||||||
|
proceed, ihadproblem = spec.match(
|
||||||
|
item,
|
||||||
|
value.mark,
|
||||||
|
data,
|
||||||
|
context.enter_item('tuple item ' + unicode(i), item),
|
||||||
|
echoerr
|
||||||
|
)
|
||||||
|
if ihadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
if not proceed:
|
||||||
|
return False, hadproblem
|
||||||
|
return True, hadproblem
|
||||||
|
|
||||||
|
def type(self, *args):
|
||||||
|
'''Describe value that has one of the types given in arguments
|
||||||
|
|
||||||
|
:param args:
|
||||||
|
List of accepted types. Since :py:class:`Spec` is supposed to
|
||||||
|
describe JSON values only ``dict``, ``list``, ``unicode``, ``bool``,
|
||||||
|
``float`` and ``NoneType`` types make any sense.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.checks.append(('check_type', args))
|
||||||
|
return self
|
||||||
|
|
||||||
|
cmp_funcs = {
|
||||||
|
'le': lambda x, y: x <= y,
|
||||||
|
'lt': lambda x, y: x < y,
|
||||||
|
'ge': lambda x, y: x >= y,
|
||||||
|
'gt': lambda x, y: x > y,
|
||||||
|
'eq': lambda x, y: x == y,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmp_msgs = {
|
||||||
|
'le': 'lesser or equal to',
|
||||||
|
'lt': 'lesser then',
|
||||||
|
'ge': 'greater or equal to',
|
||||||
|
'gt': 'greater then',
|
||||||
|
'eq': 'equal to',
|
||||||
|
}
|
||||||
|
|
||||||
|
def len(self, comparison, cint, msg_func=None):
|
||||||
|
'''Describe value that has given length
|
||||||
|
|
||||||
|
:param str comparison:
|
||||||
|
Type of the comparison. Valid values: ``le``, ``lt``, ``ge``,
|
||||||
|
``gt``, ``eq``.
|
||||||
|
:param int cint:
|
||||||
|
Integer with which length is compared.
|
||||||
|
:param function msg_func:
|
||||||
|
Function that should accept checked value and return message that
|
||||||
|
describes the problem with this value. Default value will emit
|
||||||
|
something like “length of ['foo', 'bar'] is not greater then 10”.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
cmp_func = self.cmp_funcs[comparison]
|
||||||
|
msg_func = (
|
||||||
|
msg_func
|
||||||
|
or (lambda value: 'length of {0!r} is not {1} {2}'.format(
|
||||||
|
value, self.cmp_msgs[comparison], cint))
|
||||||
|
)
|
||||||
|
self.checks.append((
|
||||||
|
'check_func',
|
||||||
|
(lambda value, *args: (True, True, not cmp_func(len(value), cint))),
|
||||||
|
msg_func
|
||||||
|
))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def cmp(self, comparison, cint, msg_func=None):
|
||||||
|
'''Describe value that is a number or string that has given property
|
||||||
|
|
||||||
|
:param str comparison:
|
||||||
|
Type of the comparison. Valid values: ``le``, ``lt``, ``ge``,
|
||||||
|
``gt``, ``eq``. This argument will restrict the number or string to
|
||||||
|
emit True on the given comparison.
|
||||||
|
:param cint:
|
||||||
|
Number or string with which value is compared. Type of this
|
||||||
|
parameter affects required type of the checked value: ``str`` and
|
||||||
|
``unicode`` types imply ``unicode`` values, ``float`` type implies
|
||||||
|
that value can be either ``int`` or ``float``, ``int`` type implies
|
||||||
|
``int`` value and for any other type the behavior is undefined.
|
||||||
|
:param function msg_func:
|
||||||
|
Function that should accept checked value and return message that
|
||||||
|
describes the problem with this value. Default value will emit
|
||||||
|
something like “10 is not greater then 10”.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
if type(cint) is str:
|
||||||
|
self.type(unicode)
|
||||||
|
elif type(cint) is float:
|
||||||
|
self.type(int, float)
|
||||||
|
else:
|
||||||
|
self.type(type(cint))
|
||||||
|
cmp_func = self.cmp_funcs[comparison]
|
||||||
|
msg_func = msg_func or (lambda value: '{0} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint))
|
||||||
|
self.checks.append((
|
||||||
|
'check_func',
|
||||||
|
(lambda value, *args: (True, True, not cmp_func(value.value, cint))),
|
||||||
|
msg_func
|
||||||
|
))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def unsigned(self, msg_func=None):
|
||||||
|
'''Describe unsigned integer value
|
||||||
|
|
||||||
|
:param function msg_func:
|
||||||
|
Function that should accept checked value and return message that
|
||||||
|
describes the problem with this value.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.type(int)
|
||||||
|
self.checks.append((
|
||||||
|
'check_func',
|
||||||
|
(lambda value, *args: (True, True, value < 0)),
|
||||||
|
(lambda value: '{0} must be greater then zero'.format(value))
|
||||||
|
))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def list(self, item_func, msg_func=None):
|
||||||
|
'''Describe list with any number of elements, each matching given spec
|
||||||
|
|
||||||
|
:param item_func:
|
||||||
|
:py:class:`Spec` instance or a callable. Check out
|
||||||
|
:py:meth:`Spec.check_list` documentation for more details. Note that
|
||||||
|
in :py:meth:`Spec.check_list` description :py:class:`Spec` instance
|
||||||
|
is replaced with its index in ``self.specs``.
|
||||||
|
:param function msg_func:
|
||||||
|
Function that should accept checked value and return message that
|
||||||
|
describes the problem with this value. Default value will emit just
|
||||||
|
“failed check”, which is rather indescriptive.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.type(list)
|
||||||
|
if isinstance(item_func, Spec):
|
||||||
|
self.specs.append(item_func)
|
||||||
|
item_func = len(self.specs) - 1
|
||||||
|
self.checks.append(('check_list', item_func, msg_func or (lambda item: 'failed check')))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def tuple(self, *specs):
|
||||||
|
'''Describe list with the given number of elements, each matching corresponding spec
|
||||||
|
|
||||||
|
:param (Spec,) specs:
|
||||||
|
List of specifications. Last element(s) in this list may be
|
||||||
|
optional. Each element in this list describes element with the same
|
||||||
|
index in the checked value. Check out :py:meth:`Spec.check_tuple`
|
||||||
|
for more details, but note that there list of specifications is
|
||||||
|
replaced with start and end indicies in ``self.specs``.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.type(list)
|
||||||
|
|
||||||
|
max_len = len(specs)
|
||||||
|
min_len = max_len
|
||||||
|
for spec in reversed(specs):
|
||||||
|
if spec.isoptional:
|
||||||
|
min_len -= 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if max_len == min_len:
|
||||||
|
self.len('eq', len(specs))
|
||||||
|
else:
|
||||||
|
self.len('ge', min_len)
|
||||||
|
self.len('le', max_len)
|
||||||
|
|
||||||
|
start = len(self.specs)
|
||||||
|
for i, spec in zip(itertools.count(), specs):
|
||||||
|
self.specs.append(spec)
|
||||||
|
self.checks.append(('check_tuple', start, len(self.specs)))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def func(self, func, msg_func=None):
|
||||||
|
'''Describe value that is checked by the given function
|
||||||
|
|
||||||
|
Check out :py:meth:`Spec.check_func` documentation for more details.
|
||||||
|
'''
|
||||||
|
self.checks.append(('check_func', func, msg_func or (lambda value: 'failed check')))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def re(self, regex, msg_func=None):
|
||||||
|
'''Describe value that is a string that matches given regular expression
|
||||||
|
|
||||||
|
:param str regex:
|
||||||
|
Regular expression that should be matched by the value.
|
||||||
|
:param function msg_func:
|
||||||
|
Function that should accept checked value and return message that
|
||||||
|
describes the problem with this value. Default value will emit
|
||||||
|
something like “String "xyz" does not match "[a-f]+"”.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.type(unicode)
|
||||||
|
compiled = re.compile(regex)
|
||||||
|
msg_func = msg_func or (lambda value: 'String "{0}" does not match "{1}"'.format(value, regex))
|
||||||
|
self.checks.append((
|
||||||
|
'check_func',
|
||||||
|
(lambda value, *args: (True, True, not compiled.match(value.value))),
|
||||||
|
msg_func
|
||||||
|
))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def ident(self, msg_func=None):
|
||||||
|
'''Describe value that is an identifier like ``foo:bar`` or ``foo``
|
||||||
|
|
||||||
|
:param function msg_func:
|
||||||
|
Function that should accept checked value and return message that
|
||||||
|
describes the problem with this value. Default value will emit
|
||||||
|
something like “String "xyz" is not an … identifier”.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
msg_func = (
|
||||||
|
msg_func
|
||||||
|
or (lambda value: 'String "{0}" is not an alphanumeric/underscore colon-separated identifier'.format(value))
|
||||||
|
)
|
||||||
|
return self.re('^\w+(?::\w+)?$', msg_func)
|
||||||
|
|
||||||
|
def oneof(self, collection, msg_func=None):
|
||||||
|
'''Describe value that is equal to one of the value in the collection
|
||||||
|
|
||||||
|
:param set collection:
|
||||||
|
A collection of possible values.
|
||||||
|
:param function msg_func:
|
||||||
|
Function that should accept checked value and return message that
|
||||||
|
describes the problem with this value. Default value will emit
|
||||||
|
something like “"xyz" must be one of {'abc', 'def', 'ghi'}”.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
msg_func = msg_func or (lambda value: '"{0}" must be one of {1!r}'.format(value, list(collection)))
|
||||||
|
self.checks.append((
|
||||||
|
'check_func',
|
||||||
|
(lambda value, *args: (True, True, value not in collection)),
|
||||||
|
msg_func
|
||||||
|
))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
'''Describe value that must not be there
|
||||||
|
|
||||||
|
Useful for giving more descriptive errors for some specific keys then
|
||||||
|
just “found unknown key: shutdown_event” or for forbidding certain
|
||||||
|
values when :py:meth:`Spec.unknown_spec` was used.
|
||||||
|
|
||||||
|
:param str msg:
|
||||||
|
Message given for the offending value. It is formatted using
|
||||||
|
:py:meth:`str.format` with the only positional parameter which is
|
||||||
|
the value itself.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.checks.append((
|
||||||
|
'check_func',
|
||||||
|
(lambda *args: (True, True, True)),
|
||||||
|
(lambda value: msg.format(value))
|
||||||
|
))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def either(self, *specs):
|
||||||
|
'''Describes value that matches one of the given specs
|
||||||
|
|
||||||
|
Check out :py:meth:`Spec.check_either` method documentation for more
|
||||||
|
details, but note that there a list of specs was replaced by start and
|
||||||
|
end indicies in ``self.specs``.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
start = len(self.specs)
|
||||||
|
self.specs.extend(specs)
|
||||||
|
self.checks.append(('check_either', start, len(self.specs)))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def optional(self):
|
||||||
|
'''Mark value as optional
|
||||||
|
|
||||||
|
Only useful for key specs in :py:meth:`Spec.__init__` and
|
||||||
|
:py:meth:`Spec.update` and some last supplied to :py:meth:`Spec.tuple`.
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.isoptional = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
def required(self):
|
||||||
|
'''Mark value as required
|
||||||
|
|
||||||
|
Only useful for key specs in :py:meth:`Spec.__init__` and
|
||||||
|
:py:meth:`Spec.update` and some last supplied to :py:meth:`Spec.tuple`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Value is required by default. This method is only useful for
|
||||||
|
altering existing specification (or rather its copy).
|
||||||
|
|
||||||
|
:return: self.
|
||||||
|
'''
|
||||||
|
self.isoptional = False
|
||||||
|
return self
|
||||||
|
|
||||||
|
def match_checks(self, *args):
|
||||||
|
'''Process checks registered for the given value
|
||||||
|
|
||||||
|
Processes only “top-level” checks: key specifications given using at the
|
||||||
|
initialization or via :py:meth:`Spec.unknown_spec` are processed by
|
||||||
|
:py:meth:`Spec.match`.
|
||||||
|
|
||||||
|
:return: proceed, hadproblem.
|
||||||
|
'''
|
||||||
|
hadproblem = False
|
||||||
|
for check in self.checks:
|
||||||
|
proceed, chadproblem = getattr(self, check[0])(*(args + check[1:]))
|
||||||
|
if chadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
if not proceed:
|
||||||
|
return False, hadproblem
|
||||||
|
return True, hadproblem
|
||||||
|
|
||||||
|
def match(self, value, context_mark=None, data=None, context=(), echoerr=echoerr):
|
||||||
|
'''Check that given value matches this specification
|
||||||
|
|
||||||
|
:return: proceed, hadproblem.
|
||||||
|
'''
|
||||||
|
havemarks(value)
|
||||||
|
proceed, hadproblem = self.match_checks(value, context_mark, data, context, echoerr)
|
||||||
|
if proceed:
|
||||||
|
if self.keys or self.uspecs:
|
||||||
|
for key, vali in self.keys.items():
|
||||||
|
valspec = self.specs[vali]
|
||||||
|
if key in value:
|
||||||
|
proceed, mhadproblem = valspec.match(
|
||||||
|
value[key],
|
||||||
|
value.mark,
|
||||||
|
data,
|
||||||
|
context.enter_key(value, key),
|
||||||
|
echoerr
|
||||||
|
)
|
||||||
|
if mhadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
if not proceed:
|
||||||
|
return False, hadproblem
|
||||||
|
else:
|
||||||
|
if not valspec.isoptional:
|
||||||
|
hadproblem = True
|
||||||
|
echoerr(context=self.cmsg.format(key=context.key),
|
||||||
|
context_mark=None,
|
||||||
|
problem='required key is missing: {0}'.format(key),
|
||||||
|
problem_mark=value.mark)
|
||||||
|
for key in value.keys():
|
||||||
|
havemarks(key)
|
||||||
|
if key not in self.keys:
|
||||||
|
for keyfunc, vali in self.uspecs:
|
||||||
|
valspec = self.specs[vali]
|
||||||
|
if isinstance(keyfunc, int):
|
||||||
|
spec = self.specs[keyfunc]
|
||||||
|
proceed, khadproblem = spec.match(key, context_mark, data, context, echoerr)
|
||||||
|
else:
|
||||||
|
proceed, khadproblem = keyfunc(key, data, context, echoerr)
|
||||||
|
if khadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
if proceed:
|
||||||
|
proceed, vhadproblem = valspec.match(
|
||||||
|
value[key],
|
||||||
|
value.mark,
|
||||||
|
data,
|
||||||
|
context.enter_key(value, key),
|
||||||
|
echoerr
|
||||||
|
)
|
||||||
|
if vhadproblem:
|
||||||
|
hadproblem = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
hadproblem = True
|
||||||
|
if self.ufailmsg:
|
||||||
|
echoerr(context=self.cmsg.format(key=context.key),
|
||||||
|
context_mark=None,
|
||||||
|
problem=self.ufailmsg(key),
|
||||||
|
problem_mark=key.mark)
|
||||||
|
return True, hadproblem
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
'''Get specification for the given key
|
||||||
|
'''
|
||||||
|
return self.specs[self.keys[key]]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
'''Set specification for the given key
|
||||||
|
'''
|
||||||
|
self.update(**{key: value})
|
@ -3,7 +3,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from powerline.bindings.vim import vim_getbufoption
|
from powerline.bindings.vim import vim_getbufoption, buffer_name
|
||||||
|
|
||||||
|
|
||||||
def help(matcher_info):
|
def help(matcher_info):
|
||||||
@ -11,8 +11,8 @@ def help(matcher_info):
|
|||||||
|
|
||||||
|
|
||||||
def cmdwin(matcher_info):
|
def cmdwin(matcher_info):
|
||||||
name = matcher_info['buffer'].name
|
name = buffer_name(matcher_info)
|
||||||
return name and os.path.basename(name) == '[Command Line]'
|
return name and os.path.basename(name) == b'[Command Line]'
|
||||||
|
|
||||||
|
|
||||||
def quickfix(matcher_info):
|
def quickfix(matcher_info):
|
||||||
|
@ -3,10 +3,13 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from powerline.bindings.vim import buffer_name
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import vim
|
import vim
|
||||||
except ImportError:
|
except ImportError:
|
||||||
vim = object()
|
pass
|
||||||
else:
|
else:
|
||||||
vim.command('''
|
vim.command('''
|
||||||
function! Powerline_plugin_ctrlp_main(...)
|
function! Powerline_plugin_ctrlp_main(...)
|
||||||
@ -26,5 +29,5 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
def ctrlp(matcher_info):
|
def ctrlp(matcher_info):
|
||||||
name = matcher_info['buffer'].name
|
name = buffer_name(matcher_info)
|
||||||
return name and os.path.basename(name) == 'ControlP'
|
return name and os.path.basename(name) == b'ControlP'
|
||||||
|
@ -3,12 +3,14 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from powerline.bindings.vim import buffer_name
|
||||||
|
|
||||||
|
|
||||||
def gundo(matcher_info):
|
def gundo(matcher_info):
|
||||||
name = matcher_info['buffer'].name
|
name = buffer_name(matcher_info)
|
||||||
return name and os.path.basename(name) == '__Gundo__'
|
return name and os.path.basename(name) == b'__Gundo__'
|
||||||
|
|
||||||
|
|
||||||
def gundo_preview(matcher_info):
|
def gundo_preview(matcher_info):
|
||||||
name = matcher_info['buffer'].name
|
name = buffer_name(matcher_info)
|
||||||
return name and os.path.basename(name) == '__Gundo_Preview__'
|
return name and os.path.basename(name) == b'__Gundo_Preview__'
|
||||||
|
@ -4,7 +4,12 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from powerline.bindings.vim import buffer_name
|
||||||
|
|
||||||
|
|
||||||
|
NERD_TREE_RE = re.compile(b'NERD_TREE_\\d+')
|
||||||
|
|
||||||
|
|
||||||
def nerdtree(matcher_info):
|
def nerdtree(matcher_info):
|
||||||
name = matcher_info['buffer'].name
|
name = buffer_name(matcher_info)
|
||||||
return name and re.match(r'NERD_tree_\d+', os.path.basename(name))
|
return name and NERD_TREE_RE.match(os.path.basename(name))
|
||||||
|
@ -248,7 +248,7 @@ class Renderer(object):
|
|||||||
current_width = 0
|
current_width = 0
|
||||||
|
|
||||||
if not width:
|
if not width:
|
||||||
# No width specified, so we don't need to crop or pad anything
|
# No width specified, so we don’t need to crop or pad anything
|
||||||
if output_width:
|
if output_width:
|
||||||
current_width = self._render_length(theme, segments, self.compute_divider_widths(theme))
|
current_width = self._render_length(theme, segments, self.compute_divider_widths(theme))
|
||||||
return construct_returned_value(''.join([
|
return construct_returned_value(''.join([
|
||||||
|
@ -14,24 +14,24 @@ class I3barRenderer(Renderer):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hlstyle(*args, **kwargs):
|
def hlstyle(*args, **kwargs):
|
||||||
# We don't need to explicitly reset attributes, so skip those calls
|
# We don’t need to explicitly reset attributes, so skip those calls
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def hl(self, contents, fg=None, bg=None, attr=None):
|
def hl(self, contents, fg=None, bg=None, attr=None):
|
||||||
segment = {
|
segment = {
|
||||||
"full_text": contents,
|
'full_text': contents,
|
||||||
"separator": False,
|
'separator': False,
|
||||||
"separator_block_width": 0, # no seperators
|
'separator_block_width': 0, # no seperators
|
||||||
}
|
}
|
||||||
|
|
||||||
if fg is not None:
|
if fg is not None:
|
||||||
if fg is not False and fg[1] is not False:
|
if fg is not False and fg[1] is not False:
|
||||||
segment['color'] = "#{0:06x}".format(fg[1])
|
segment['color'] = '#{0:06x}'.format(fg[1])
|
||||||
if bg is not None:
|
if bg is not None:
|
||||||
if bg is not False and bg[1] is not False:
|
if bg is not False and bg[1] is not False:
|
||||||
segment['background_color'] = "#{0:06x}".format(bg[1])
|
segment['background_color'] = '#{0:06x}'.format(bg[1])
|
||||||
# i3bar "pseudo json" requires one line at a time
|
# i3bar “pseudo json” requires one line at a time
|
||||||
return json.dumps(segment) + ",\n"
|
return json.dumps(segment) + ',\n'
|
||||||
|
|
||||||
|
|
||||||
renderer = I3barRenderer
|
renderer = I3barRenderer
|
||||||
|
@ -12,7 +12,7 @@ class PangoMarkupRenderer(Renderer):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hlstyle(*args, **kwargs):
|
def hlstyle(*args, **kwargs):
|
||||||
# We don't need to explicitly reset attributes, so skip those calls
|
# We don’t need to explicitly reset attributes, so skip those calls
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def hl(self, contents, fg=None, bg=None, attr=None):
|
def hl(self, contents, fg=None, bg=None, attr=None):
|
||||||
|
@ -82,7 +82,7 @@ class ShellRenderer(Renderer):
|
|||||||
|
|
||||||
If an argument is None, the argument is ignored. If an argument is
|
If an argument is None, the argument is ignored. If an argument is
|
||||||
False, the argument is reset to the terminal defaults. If an argument
|
False, the argument is reset to the terminal defaults. If an argument
|
||||||
is a valid color or attribute, it's added to the ANSI escape code.
|
is a valid color or attribute, it’s added to the ANSI escape code.
|
||||||
'''
|
'''
|
||||||
ansi = [0]
|
ansi = [0]
|
||||||
if fg is not None:
|
if fg is not None:
|
||||||
|
@ -13,7 +13,7 @@ class TmuxRenderer(Renderer):
|
|||||||
|
|
||||||
def hlstyle(self, fg=None, bg=None, attr=None):
|
def hlstyle(self, fg=None, bg=None, attr=None):
|
||||||
'''Highlight a segment.'''
|
'''Highlight a segment.'''
|
||||||
# We don't need to explicitly reset attributes, so skip those calls
|
# We don’t need to explicitly reset attributes, so skip those calls
|
||||||
if not attr and not bg and not fg:
|
if not attr and not bg and not fg:
|
||||||
return ''
|
return ''
|
||||||
tmux_attr = []
|
tmux_attr = []
|
||||||
|
@ -9,10 +9,10 @@ from powerline.bindings.vim import vim_get_func, vim_getoption, environ, current
|
|||||||
from powerline.renderer import Renderer
|
from powerline.renderer import Renderer
|
||||||
from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
|
from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
|
||||||
from powerline.theme import Theme
|
from powerline.theme import Theme
|
||||||
from powerline.lib.unicode import unichr
|
from powerline.lib.unicode import unichr, register_strwidth_error
|
||||||
|
|
||||||
|
|
||||||
vim_mode = vim_get_func('mode', rettype=str)
|
vim_mode = vim_get_func('mode', rettype='unicode')
|
||||||
if int(vim.eval('v:version')) >= 702:
|
if int(vim.eval('v:version')) >= 702:
|
||||||
_vim_mode = vim_mode
|
_vim_mode = vim_mode
|
||||||
vim_mode = lambda: _vim_mode(1)
|
vim_mode = lambda: _vim_mode(1)
|
||||||
@ -41,6 +41,8 @@ class VimRenderer(Renderer):
|
|||||||
super(VimRenderer, self).__init__(*args, **kwargs)
|
super(VimRenderer, self).__init__(*args, **kwargs)
|
||||||
self.hl_groups = {}
|
self.hl_groups = {}
|
||||||
self.prev_highlight = None
|
self.prev_highlight = None
|
||||||
|
self.strwidth_error_name = register_strwidth_error(self.strwidth)
|
||||||
|
self.encoding = vim.eval('&encoding')
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.theme.shutdown()
|
self.theme.shutdown()
|
||||||
@ -71,11 +73,10 @@ class VimRenderer(Renderer):
|
|||||||
|
|
||||||
if hasattr(vim, 'strwidth'):
|
if hasattr(vim, 'strwidth'):
|
||||||
if sys.version_info < (3,):
|
if sys.version_info < (3,):
|
||||||
@staticmethod
|
def strwidth(self, string):
|
||||||
def strwidth(string):
|
|
||||||
# Does not work with tabs, but neither is strwidth from default
|
# Does not work with tabs, but neither is strwidth from default
|
||||||
# renderer
|
# renderer
|
||||||
return vim.strwidth(string.encode('utf-8'))
|
return vim.strwidth(string.encode(self.encoding, 'replace'))
|
||||||
else:
|
else:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def strwidth(string):
|
def strwidth(string):
|
||||||
@ -101,6 +102,7 @@ class VimRenderer(Renderer):
|
|||||||
winnr=winnr,
|
winnr=winnr,
|
||||||
buffer=window.buffer,
|
buffer=window.buffer,
|
||||||
tabpage=current_tabpage(),
|
tabpage=current_tabpage(),
|
||||||
|
encoding=self.encoding,
|
||||||
)
|
)
|
||||||
segment_info['tabnr'] = segment_info['tabpage'].number
|
segment_info['tabnr'] = segment_info['tabpage'].number
|
||||||
segment_info['bufnr'] = segment_info['buffer'].number
|
segment_info['bufnr'] = segment_info['buffer'].number
|
||||||
@ -115,6 +117,7 @@ class VimRenderer(Renderer):
|
|||||||
segment_info=segment_info,
|
segment_info=segment_info,
|
||||||
matcher_info=(None if is_tabline else segment_info),
|
matcher_info=(None if is_tabline else segment_info),
|
||||||
)
|
)
|
||||||
|
statusline = statusline.encode(self.encoding, self.strwidth_error_name)
|
||||||
return statusline
|
return statusline
|
||||||
|
|
||||||
def reset_highlight(self):
|
def reset_highlight(self):
|
||||||
@ -125,7 +128,7 @@ class VimRenderer(Renderer):
|
|||||||
|
|
||||||
If an argument is None, the argument is ignored. If an argument is
|
If an argument is None, the argument is ignored. If an argument is
|
||||||
False, the argument is reset to the terminal defaults. If an argument
|
False, the argument is reset to the terminal defaults. If an argument
|
||||||
is a valid color or attribute, it's added to the vim highlight group.
|
is a valid color or attribute, it’s added to the vim highlight group.
|
||||||
'''
|
'''
|
||||||
# In order not to hit E541 two consequent identical highlighting
|
# In order not to hit E541 two consequent identical highlighting
|
||||||
# specifiers may be squashed into one.
|
# specifiers may be squashed into one.
|
||||||
@ -134,7 +137,7 @@ class VimRenderer(Renderer):
|
|||||||
return ''
|
return ''
|
||||||
self.prev_highlight = (fg, bg, attr)
|
self.prev_highlight = (fg, bg, attr)
|
||||||
|
|
||||||
# We don't need to explicitly reset attributes in vim, so skip those
|
# We don’t need to explicitly reset attributes in vim, so skip those
|
||||||
# calls
|
# calls
|
||||||
if not attr and not bg and not fg:
|
if not attr and not bg and not fg:
|
||||||
return ''
|
return ''
|
||||||
|
File diff suppressed because it is too large
Load Diff
26
powerline/segments/common/__init__.py
Normal file
26
powerline/segments/common/__init__.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
|
||||||
|
# DEPRECATED MODULE. Do not add any segments below. Do not remove existing
|
||||||
|
# segments as well until next major release.
|
||||||
|
|
||||||
|
|
||||||
|
from powerline.segments.common.vcs import branch # NOQA
|
||||||
|
from powerline.segments.common.sys import cpu_load_percent # NOQA
|
||||||
|
from powerline.segments.common.sys import uptime # NOQA
|
||||||
|
from powerline.segments.common.sys import system_load # NOQA
|
||||||
|
from powerline.segments.common.net import hostname # NOQA
|
||||||
|
from powerline.segments.common.net import external_ip # NOQA
|
||||||
|
from powerline.segments.common.net import internal_ip # NOQA
|
||||||
|
from powerline.segments.common.net import network_load # NOQA
|
||||||
|
from powerline.segments.common.env import cwd # NOQA
|
||||||
|
from powerline.segments.common.env import user # NOQA
|
||||||
|
from powerline.segments.common.env import environment # NOQA
|
||||||
|
from powerline.segments.common.env import virtualenv # NOQA
|
||||||
|
from powerline.segments.common.bat import battery # NOQA
|
||||||
|
from powerline.segments.common.wthr import weather # NOQA
|
||||||
|
from powerline.segments.common.time import date # NOQA
|
||||||
|
from powerline.segments.common.time import fuzzy_time # NOQA
|
||||||
|
from powerline.segments.common.mail import email_imap_alert # NOQA
|
||||||
|
from powerline.segments.common.players import now_playing # NOQA
|
222
powerline/segments/common/bat.py
Normal file
222
powerline/segments/common/bat.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
from powerline.lib.shell import run_cmd
|
||||||
|
|
||||||
|
|
||||||
|
# XXX Warning: module name must not be equal to the segment name as long as this
|
||||||
|
# segment is imported into powerline.segments.common module.
|
||||||
|
|
||||||
|
|
||||||
|
def _get_battery(pl):
|
||||||
|
try:
|
||||||
|
import dbus
|
||||||
|
except ImportError:
|
||||||
|
pl.debug('Not using DBUS+UPower as dbus is not available')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
bus = dbus.SystemBus()
|
||||||
|
except Exception as e:
|
||||||
|
pl.exception('Failed to connect to system bus: {0}', str(e))
|
||||||
|
else:
|
||||||
|
interface = 'org.freedesktop.UPower'
|
||||||
|
try:
|
||||||
|
up = bus.get_object(interface, '/org/freedesktop/UPower')
|
||||||
|
except dbus.exceptions.DBusException as e:
|
||||||
|
if getattr(e, '_dbus_error_name', '').endswidth('ServiceUnknown'):
|
||||||
|
pl.debug('Not using DBUS+UPower as UPower is not available via dbus')
|
||||||
|
else:
|
||||||
|
pl.exception('Failed to get UPower service with dbus: {0}', str(e))
|
||||||
|
else:
|
||||||
|
devinterface = 'org.freedesktop.DBus.Properties'
|
||||||
|
devtype_name = interface + '.Device'
|
||||||
|
for devpath in up.EnumerateDevices(dbus_interface=interface):
|
||||||
|
dev = bus.get_object(interface, devpath)
|
||||||
|
devget = lambda what: dev.Get(
|
||||||
|
devtype_name,
|
||||||
|
what,
|
||||||
|
dbus_interface=devinterface
|
||||||
|
)
|
||||||
|
if int(devget('Type')) != 2:
|
||||||
|
pl.debug('Not using DBUS+UPower with {0}: invalid type', devpath)
|
||||||
|
continue
|
||||||
|
if not bool(devget('IsPresent')):
|
||||||
|
pl.debug('Not using DBUS+UPower with {0}: not present', devpath)
|
||||||
|
continue
|
||||||
|
if not bool(devget('PowerSupply')):
|
||||||
|
pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath)
|
||||||
|
continue
|
||||||
|
pl.debug('Using DBUS+UPower with {0}', devpath)
|
||||||
|
return lambda pl: float(
|
||||||
|
dbus.Interface(dev, dbus_interface=devinterface).Get(
|
||||||
|
devtype_name,
|
||||||
|
'Percentage'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pl.debug('Not using DBUS+UPower as no batteries were found')
|
||||||
|
|
||||||
|
if os.path.isdir('/sys/class/power_supply'):
|
||||||
|
linux_bat_fmt = '/sys/class/power_supply/{0}/capacity'
|
||||||
|
for linux_bat in os.listdir('/sys/class/power_supply'):
|
||||||
|
cap_path = linux_bat_fmt.format(linux_bat)
|
||||||
|
if linux_bat.startswith('BAT') and os.path.exists(cap_path):
|
||||||
|
pl.debug('Using /sys/class/power_supply with battery {0}', linux_bat)
|
||||||
|
|
||||||
|
def _get_capacity(pl):
|
||||||
|
with open(cap_path, 'r') as f:
|
||||||
|
return int(float(f.readline().split()[0]))
|
||||||
|
|
||||||
|
return _get_capacity
|
||||||
|
pl.debug('Not using /sys/class/power_supply as no batteries were found')
|
||||||
|
else:
|
||||||
|
pl.debug('Not using /sys/class/power_supply: no directory')
|
||||||
|
|
||||||
|
try:
|
||||||
|
from shutil import which # Python-3.3 and later
|
||||||
|
except ImportError:
|
||||||
|
pl.info('Using dumb “which” which only checks for file in /usr/bin')
|
||||||
|
which = lambda f: (lambda fp: os.path.exists(fp) and fp)(os.path.join('/usr/bin', f))
|
||||||
|
|
||||||
|
if which('pmset'):
|
||||||
|
pl.debug('Using pmset')
|
||||||
|
|
||||||
|
BATTERY_PERCENT_RE = re.compile(r'(\d+)%')
|
||||||
|
|
||||||
|
def _get_capacity(pl):
|
||||||
|
battery_summary = run_cmd(pl, ['pmset', '-g', 'batt'])
|
||||||
|
battery_percent = BATTERY_PERCENT_RE.search(battery_summary).group(1)
|
||||||
|
return int(battery_percent)
|
||||||
|
|
||||||
|
return _get_capacity
|
||||||
|
else:
|
||||||
|
pl.debug('Not using pmset: executable not found')
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
# From http://stackoverflow.com/a/21083571/273566, reworked
|
||||||
|
try:
|
||||||
|
from win32com.client import GetObject
|
||||||
|
except ImportError:
|
||||||
|
pl.debug('Not using win32com.client as it is not available')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
wmi = GetObject('winmgmts:')
|
||||||
|
except Exception as e:
|
||||||
|
pl.exception('Failed to run GetObject from win32com.client: {0}', str(e))
|
||||||
|
else:
|
||||||
|
for battery in wmi.InstancesOf('Win32_Battery'):
|
||||||
|
pl.debug('Using win32com.client with Win32_Battery')
|
||||||
|
|
||||||
|
def _get_capacity(pl):
|
||||||
|
# http://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx
|
||||||
|
return battery.EstimatedChargeRemaining
|
||||||
|
|
||||||
|
return _get_capacity
|
||||||
|
pl.debug('Not using win32com.client as no batteries were found')
|
||||||
|
|
||||||
|
from ctypes import Structure, c_byte, c_ulong, windll, byref
|
||||||
|
|
||||||
|
class PowerClass(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
('ACLineStatus', c_byte),
|
||||||
|
('BatteryFlag', c_byte),
|
||||||
|
('BatteryLifePercent', c_byte),
|
||||||
|
('Reserved1', c_byte),
|
||||||
|
('BatteryLifeTime', c_ulong),
|
||||||
|
('BatteryFullLifeTime', c_ulong)
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_capacity(pl):
|
||||||
|
powerclass = PowerClass()
|
||||||
|
result = windll.kernel32.GetSystemPowerStatus(byref(powerclass))
|
||||||
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa372693(v=vs.85).aspx
|
||||||
|
if result:
|
||||||
|
return None
|
||||||
|
return powerclass.BatteryLifePercent
|
||||||
|
|
||||||
|
if _get_capacity() is None:
|
||||||
|
pl.debug('Not using GetSystemPowerStatus because it failed')
|
||||||
|
else:
|
||||||
|
pl.debug('Using GetSystemPowerStatus')
|
||||||
|
|
||||||
|
return _get_capacity
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def _get_capacity(pl):
|
||||||
|
global _get_capacity
|
||||||
|
|
||||||
|
def _failing_get_capacity(pl):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
try:
|
||||||
|
_get_capacity = _get_battery(pl)
|
||||||
|
except NotImplementedError:
|
||||||
|
_get_capacity = _failing_get_capacity
|
||||||
|
except Exception as e:
|
||||||
|
pl.exception('Exception while obtaining battery capacity getter: {0}', str(e))
|
||||||
|
_get_capacity = _failing_get_capacity
|
||||||
|
return _get_capacity(pl)
|
||||||
|
|
||||||
|
|
||||||
|
def battery(pl, format='{capacity:3.0%}', steps=5, gamify=False, full_heart='O', empty_heart='O'):
|
||||||
|
'''Return battery charge status.
|
||||||
|
|
||||||
|
:param str format:
|
||||||
|
Percent format in case gamify is False.
|
||||||
|
:param int steps:
|
||||||
|
Number of discrete steps to show between 0% and 100% capacity if gamify
|
||||||
|
is True.
|
||||||
|
:param bool gamify:
|
||||||
|
Measure in hearts (♥) instead of percentages. For full hearts
|
||||||
|
``battery_full`` highlighting group is preferred, for empty hearts there
|
||||||
|
is ``battery_empty``.
|
||||||
|
:param str full_heart:
|
||||||
|
Heart displayed for “full” part of battery.
|
||||||
|
:param str empty_heart:
|
||||||
|
Heart displayed for “used” part of battery. It is also displayed using
|
||||||
|
another gradient level and highlighting group, so it is OK for it to be
|
||||||
|
the same as full_heart as long as necessary highlighting groups are
|
||||||
|
defined.
|
||||||
|
|
||||||
|
``battery_gradient`` and ``battery`` groups are used in any case, first is
|
||||||
|
preferred.
|
||||||
|
|
||||||
|
Highlight groups used: ``battery_full`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_empty`` or ``battery_gradient`` (gradient) or ``battery``.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
capacity = _get_capacity(pl)
|
||||||
|
except NotImplementedError:
|
||||||
|
pl.info('Unable to get battery capacity.')
|
||||||
|
return None
|
||||||
|
ret = []
|
||||||
|
if gamify:
|
||||||
|
denom = int(steps)
|
||||||
|
numer = int(denom * capacity / 100)
|
||||||
|
ret.append({
|
||||||
|
'contents': full_heart * numer,
|
||||||
|
'draw_inner_divider': False,
|
||||||
|
'highlight_group': ['battery_full', 'battery_gradient', 'battery'],
|
||||||
|
# Using zero as “nothing to worry about”: it is least alert color.
|
||||||
|
'gradient_level': 0,
|
||||||
|
})
|
||||||
|
ret.append({
|
||||||
|
'contents': empty_heart * (denom - numer),
|
||||||
|
'draw_inner_divider': False,
|
||||||
|
'highlight_group': ['battery_empty', 'battery_gradient', 'battery'],
|
||||||
|
# Using a hundred as it is most alert color.
|
||||||
|
'gradient_level': 100,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
ret.append({
|
||||||
|
'contents': format.format(capacity=(capacity / 100.0)),
|
||||||
|
'highlight_group': ['battery_gradient', 'battery'],
|
||||||
|
# Gradients are “least alert – most alert” by default, capacity has
|
||||||
|
# the opposite semantics.
|
||||||
|
'gradient_level': 100 - capacity,
|
||||||
|
})
|
||||||
|
return ret
|
168
powerline/segments/common/env.py
Normal file
168
powerline/segments/common/env.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from powerline.lib.unicode import out_u
|
||||||
|
from powerline.theme import requires_segment_info
|
||||||
|
from powerline.segments import Segment, with_docstring
|
||||||
|
|
||||||
|
|
||||||
|
@requires_segment_info
|
||||||
|
def environment(pl, segment_info, variable=None):
|
||||||
|
'''Return the value of any defined environment variable
|
||||||
|
|
||||||
|
:param string variable:
|
||||||
|
The environment variable to return if found
|
||||||
|
'''
|
||||||
|
return segment_info['environ'].get(variable, None)
|
||||||
|
|
||||||
|
|
||||||
|
@requires_segment_info
|
||||||
|
def virtualenv(pl, segment_info):
|
||||||
|
'''Return the name of the current Python virtualenv.'''
|
||||||
|
return os.path.basename(segment_info['environ'].get('VIRTUAL_ENV', '')) or None
|
||||||
|
|
||||||
|
|
||||||
|
@requires_segment_info
|
||||||
|
class CwdSegment(Segment):
|
||||||
|
def argspecobjs(self):
|
||||||
|
for obj in super(CwdSegment, self).argspecobjs():
|
||||||
|
yield obj
|
||||||
|
yield 'get_shortened_path', self.get_shortened_path
|
||||||
|
|
||||||
|
def omitted_args(self, name, method):
|
||||||
|
if method is self.get_shortened_path:
|
||||||
|
return (0, 1, 2)
|
||||||
|
else:
|
||||||
|
return super(CwdSegment, self).omitted_args(name, method)
|
||||||
|
|
||||||
|
def get_shortened_path(self, pl, segment_info, shorten_home=True, **kwargs):
|
||||||
|
try:
|
||||||
|
path = out_u(segment_info['getcwd']())
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 2:
|
||||||
|
# user most probably deleted the directory
|
||||||
|
# this happens when removing files from Mercurial repos for example
|
||||||
|
pl.warn('Current directory not found')
|
||||||
|
return '[not found]'
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
if shorten_home:
|
||||||
|
home = segment_info['home']
|
||||||
|
if home:
|
||||||
|
home = out_u(home)
|
||||||
|
if path.startswith(home):
|
||||||
|
path = '~' + path[len(home):]
|
||||||
|
return path
|
||||||
|
|
||||||
|
def __call__(self, pl, segment_info,
|
||||||
|
dir_shorten_len=None,
|
||||||
|
dir_limit_depth=None,
|
||||||
|
use_path_separator=False,
|
||||||
|
ellipsis='...',
|
||||||
|
**kwargs):
|
||||||
|
cwd = self.get_shortened_path(pl, segment_info, **kwargs)
|
||||||
|
cwd_split = cwd.split(os.sep)
|
||||||
|
cwd_split_len = len(cwd_split)
|
||||||
|
cwd = [i[0:dir_shorten_len] if dir_shorten_len and i else i for i in cwd_split[:-1]] + [cwd_split[-1]]
|
||||||
|
if dir_limit_depth and cwd_split_len > dir_limit_depth + 1:
|
||||||
|
del(cwd[0:-dir_limit_depth])
|
||||||
|
if ellipsis is not None:
|
||||||
|
cwd.insert(0, ellipsis)
|
||||||
|
ret = []
|
||||||
|
if not cwd[0]:
|
||||||
|
cwd[0] = '/'
|
||||||
|
draw_inner_divider = not use_path_separator
|
||||||
|
for part in cwd:
|
||||||
|
if not part:
|
||||||
|
continue
|
||||||
|
if use_path_separator:
|
||||||
|
part += os.sep
|
||||||
|
ret.append({
|
||||||
|
'contents': part,
|
||||||
|
'divider_highlight_group': 'cwd:divider',
|
||||||
|
'draw_inner_divider': draw_inner_divider,
|
||||||
|
})
|
||||||
|
ret[-1]['highlight_group'] = ['cwd:current_folder', 'cwd']
|
||||||
|
if use_path_separator:
|
||||||
|
ret[-1]['contents'] = ret[-1]['contents'][:-1]
|
||||||
|
if len(ret) > 1 and ret[0]['contents'][0] == os.sep:
|
||||||
|
ret[0]['contents'] = ret[0]['contents'][1:]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
cwd = with_docstring(CwdSegment(),
|
||||||
|
'''Return the current working directory.
|
||||||
|
|
||||||
|
Returns a segment list to create a breadcrumb-like effect.
|
||||||
|
|
||||||
|
:param int dir_shorten_len:
|
||||||
|
shorten parent directory names to this length (e.g.
|
||||||
|
:file:`/long/path/to/powerline` → :file:`/l/p/t/powerline`)
|
||||||
|
:param int dir_limit_depth:
|
||||||
|
limit directory depth to this number (e.g.
|
||||||
|
:file:`/long/path/to/powerline` → :file:`⋯/to/powerline`)
|
||||||
|
:param bool use_path_separator:
|
||||||
|
Use path separator in place of soft divider.
|
||||||
|
:param bool shorten_home:
|
||||||
|
Shorten home directory to ``~``.
|
||||||
|
:param str ellipsis:
|
||||||
|
Specifies what to use in place of omitted directories. Use None to not
|
||||||
|
show this subsegment at all.
|
||||||
|
|
||||||
|
Divider highlight group used: ``cwd:divider``.
|
||||||
|
|
||||||
|
Highlight groups used: ``cwd:current_folder`` or ``cwd``. It is recommended to define all highlight groups.
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
# psutil-2.0.0: psutil.Process.username is unbound method
|
||||||
|
if callable(psutil.Process.username):
|
||||||
|
def _get_user():
|
||||||
|
return psutil.Process(os.getpid()).username()
|
||||||
|
# pre psutil-2.0.0: psutil.Process.username has type property
|
||||||
|
else:
|
||||||
|
def _get_user():
|
||||||
|
return psutil.Process(os.getpid()).username
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import pwd
|
||||||
|
except ImportError:
|
||||||
|
from getpass import getuser as _get_user
|
||||||
|
else:
|
||||||
|
def _get_user():
|
||||||
|
return pwd.getpwuid(os.geteuid()).pw_name
|
||||||
|
|
||||||
|
|
||||||
|
username = False
|
||||||
|
# os.geteuid is not available on windows
|
||||||
|
_geteuid = getattr(os, 'geteuid', lambda: 1)
|
||||||
|
|
||||||
|
|
||||||
|
def user(pl, hide_user=None):
|
||||||
|
'''Return the current user.
|
||||||
|
|
||||||
|
:param str hide_user:
|
||||||
|
Omit showing segment for users with names equal to this string.
|
||||||
|
|
||||||
|
Highlights the user with the ``superuser`` if the effective user ID is 0.
|
||||||
|
|
||||||
|
Highlight groups used: ``superuser`` or ``user``. It is recommended to define all highlight groups.
|
||||||
|
'''
|
||||||
|
global username
|
||||||
|
if username is False:
|
||||||
|
username = _get_user()
|
||||||
|
if username is None:
|
||||||
|
pl.warn('Failed to get username')
|
||||||
|
return None
|
||||||
|
if username == hide_user:
|
||||||
|
return None
|
||||||
|
euid = _geteuid()
|
||||||
|
return [{
|
||||||
|
'contents': username,
|
||||||
|
'highlight_group': ['user'] if euid != 0 else ['superuser', 'user'],
|
||||||
|
}]
|
74
powerline/segments/common/mail.py
Normal file
74
powerline/segments/common/mail.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from powerline.lib.threaded import KwThreadedSegment
|
||||||
|
from powerline.segments import with_docstring
|
||||||
|
|
||||||
|
|
||||||
|
_IMAPKey = namedtuple('Key', 'username password server port folder')
|
||||||
|
|
||||||
|
|
||||||
|
class EmailIMAPSegment(KwThreadedSegment):
|
||||||
|
interval = 60
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def key(username, password, server='imap.gmail.com', port=993, folder='INBOX', **kwargs):
|
||||||
|
return _IMAPKey(username, password, server, port, folder)
|
||||||
|
|
||||||
|
def compute_state(self, key):
|
||||||
|
if not key.username or not key.password:
|
||||||
|
self.warn('Username and password are not configured')
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
import imaplib
|
||||||
|
except imaplib.IMAP4.error as e:
|
||||||
|
unread_count = str(e)
|
||||||
|
else:
|
||||||
|
mail = imaplib.IMAP4_SSL(key.server, key.port)
|
||||||
|
mail.login(key.username, key.password)
|
||||||
|
rc, message = mail.status(key.folder, '(UNSEEN)')
|
||||||
|
unread_str = message[0].decode('utf-8')
|
||||||
|
unread_count = int(re.search('UNSEEN (\d+)', unread_str).group(1))
|
||||||
|
return unread_count
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def render_one(unread_count, max_msgs=None, **kwargs):
|
||||||
|
if not unread_count:
|
||||||
|
return None
|
||||||
|
elif type(unread_count) != int or not max_msgs:
|
||||||
|
return [{
|
||||||
|
'contents': str(unread_count),
|
||||||
|
'highlight_group': ['email_alert'],
|
||||||
|
}]
|
||||||
|
else:
|
||||||
|
return [{
|
||||||
|
'contents': str(unread_count),
|
||||||
|
'highlight_group': ['email_alert_gradient', 'email_alert'],
|
||||||
|
'gradient_level': min(unread_count * 100.0 / max_msgs, 100),
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
email_imap_alert = with_docstring(EmailIMAPSegment(),
|
||||||
|
'''Return unread e-mail count for IMAP servers.
|
||||||
|
|
||||||
|
:param str username:
|
||||||
|
login username
|
||||||
|
:param str password:
|
||||||
|
login password
|
||||||
|
:param str server:
|
||||||
|
e-mail server
|
||||||
|
:param int port:
|
||||||
|
e-mail server port
|
||||||
|
:param str folder:
|
||||||
|
folder to check for e-mails
|
||||||
|
:param int max_msgs:
|
||||||
|
Maximum number of messages. If there are more messages then max_msgs then it
|
||||||
|
will use gradient level equal to 100, otherwise gradient level is equal to
|
||||||
|
``100 * msgs_num / max_msgs``. If not present gradient is not computed.
|
||||||
|
|
||||||
|
Highlight groups used: ``email_alert_gradient`` (gradient), ``email_alert``.
|
||||||
|
''')
|
287
powerline/segments/common/net.py
Normal file
287
powerline/segments/common/net.py
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from powerline.lib.url import urllib_read
|
||||||
|
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
|
||||||
|
from powerline.lib.monotonic import monotonic
|
||||||
|
from powerline.lib.humanize_bytes import humanize_bytes
|
||||||
|
from powerline.segments import with_docstring
|
||||||
|
from powerline.theme import requires_segment_info
|
||||||
|
|
||||||
|
|
||||||
|
@requires_segment_info
|
||||||
|
def hostname(pl, segment_info, only_if_ssh=False, exclude_domain=False):
|
||||||
|
'''Return the current hostname.
|
||||||
|
|
||||||
|
:param bool only_if_ssh:
|
||||||
|
only return the hostname if currently in an SSH session
|
||||||
|
:param bool exclude_domain:
|
||||||
|
return the hostname without domain if there is one
|
||||||
|
'''
|
||||||
|
if only_if_ssh and not segment_info['environ'].get('SSH_CLIENT'):
|
||||||
|
return None
|
||||||
|
if exclude_domain:
|
||||||
|
return socket.gethostname().split('.')[0]
|
||||||
|
return socket.gethostname()
|
||||||
|
|
||||||
|
|
||||||
|
def _external_ip(query_url='http://ipv4.icanhazip.com/'):
|
||||||
|
return urllib_read(query_url).strip()
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalIpSegment(ThreadedSegment):
|
||||||
|
interval = 300
|
||||||
|
|
||||||
|
def set_state(self, query_url='http://ipv4.icanhazip.com/', **kwargs):
|
||||||
|
self.query_url = query_url
|
||||||
|
super(ExternalIpSegment, self).set_state(**kwargs)
|
||||||
|
|
||||||
|
def update(self, old_ip):
|
||||||
|
return _external_ip(query_url=self.query_url)
|
||||||
|
|
||||||
|
def render(self, ip, **kwargs):
|
||||||
|
if not ip:
|
||||||
|
return None
|
||||||
|
return [{'contents': ip, 'divider_highlight_group': 'background:divider'}]
|
||||||
|
|
||||||
|
|
||||||
|
external_ip = with_docstring(ExternalIpSegment(),
|
||||||
|
'''Return external IP address.
|
||||||
|
|
||||||
|
:param str query_url:
|
||||||
|
URI to query for IP address, should return only the IP address as a text string
|
||||||
|
|
||||||
|
Suggested URIs:
|
||||||
|
|
||||||
|
* http://ipv4.icanhazip.com/
|
||||||
|
* http://ipv6.icanhazip.com/
|
||||||
|
* http://icanhazip.com/ (returns IPv6 address if available, else IPv4)
|
||||||
|
|
||||||
|
Divider highlight group used: ``background:divider``.
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import netifaces
|
||||||
|
except ImportError:
|
||||||
|
def internal_ip(pl, interface='detect', ipv=4):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
_interface_starts = {
|
||||||
|
'eth': 10, # Regular ethernet adapters : eth1
|
||||||
|
'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0
|
||||||
|
'ath': 9, # Atheros WiFi adapters : ath0
|
||||||
|
'wlan': 9, # Other WiFi adapters : wlan1
|
||||||
|
'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0
|
||||||
|
'teredo': 1, # miredo interface : teredo
|
||||||
|
'lo': -10, # Loopback interface : lo
|
||||||
|
}
|
||||||
|
|
||||||
|
_interface_start_re = re.compile(r'^([a-z]+?)(\d|$)')
|
||||||
|
|
||||||
|
def _interface_key(interface):
|
||||||
|
match = _interface_start_re.match(interface)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
base = _interface_starts[match.group(1)] * 100
|
||||||
|
except KeyError:
|
||||||
|
base = 500
|
||||||
|
if match.group(2):
|
||||||
|
return base - int(match.group(2))
|
||||||
|
else:
|
||||||
|
return base
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def internal_ip(pl, interface='detect', ipv=4):
|
||||||
|
if interface == 'detect':
|
||||||
|
try:
|
||||||
|
interface = next(iter(sorted(netifaces.interfaces(), key=_interface_key, reverse=True)))
|
||||||
|
except StopIteration:
|
||||||
|
pl.info('No network interfaces found')
|
||||||
|
return None
|
||||||
|
addrs = netifaces.ifaddresses(interface)
|
||||||
|
try:
|
||||||
|
return addrs[netifaces.AF_INET6 if ipv == 6 else netifaces.AF_INET][0]['addr']
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
internal_ip = with_docstring(internal_ip,
|
||||||
|
'''Return internal IP address
|
||||||
|
|
||||||
|
Requires ``netifaces`` module to work properly.
|
||||||
|
|
||||||
|
:param str interface:
|
||||||
|
Interface on which IP will be checked. Use ``detect`` to automatically
|
||||||
|
detect interface. In this case interfaces with lower numbers will be
|
||||||
|
preferred over interfaces with similar names. Order of preference based on
|
||||||
|
names:
|
||||||
|
|
||||||
|
#. ``eth`` and ``enp`` followed by number or the end of string.
|
||||||
|
#. ``ath``, ``wlan`` and ``wlp`` followed by number or the end of string.
|
||||||
|
#. ``teredo`` followed by number or the end of string.
|
||||||
|
#. Any other interface that is not ``lo*``.
|
||||||
|
#. ``lo`` followed by number or the end of string.
|
||||||
|
:param int ipv:
|
||||||
|
4 or 6 for ipv4 and ipv6 respectively, depending on which IP address you
|
||||||
|
need exactly.
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
def _get_bytes(interface):
|
||||||
|
try:
|
||||||
|
io_counters = psutil.net_io_counters(pernic=True)
|
||||||
|
except AttributeError:
|
||||||
|
io_counters = psutil.network_io_counters(pernic=True)
|
||||||
|
if_io = io_counters.get(interface)
|
||||||
|
if not if_io:
|
||||||
|
return None
|
||||||
|
return if_io.bytes_recv, if_io.bytes_sent
|
||||||
|
|
||||||
|
def _get_interfaces():
|
||||||
|
io_counters = psutil.network_io_counters(pernic=True)
|
||||||
|
for interface, data in io_counters.items():
|
||||||
|
if data:
|
||||||
|
yield interface, data.bytes_recv, data.bytes_sent
|
||||||
|
except ImportError:
|
||||||
|
def _get_bytes(interface):
|
||||||
|
with open('/sys/class/net/{interface}/statistics/rx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||||
|
rx = int(file_obj.read())
|
||||||
|
with open('/sys/class/net/{interface}/statistics/tx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||||
|
tx = int(file_obj.read())
|
||||||
|
return (rx, tx)
|
||||||
|
|
||||||
|
def _get_interfaces():
|
||||||
|
for interface in os.listdir('/sys/class/net'):
|
||||||
|
x = _get_bytes(interface)
|
||||||
|
if x is not None:
|
||||||
|
yield interface, x[0], x[1]
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkLoadSegment(KwThreadedSegment):
|
||||||
|
interfaces = {}
|
||||||
|
replace_num_pat = re.compile(r'[a-zA-Z]+')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def key(interface='detect', **kwargs):
|
||||||
|
return interface
|
||||||
|
|
||||||
|
def compute_state(self, interface):
|
||||||
|
if interface == 'detect':
|
||||||
|
proc_exists = getattr(self, 'proc_exists', None)
|
||||||
|
if proc_exists is None:
|
||||||
|
proc_exists = self.proc_exists = os.path.exists('/proc/net/route')
|
||||||
|
if proc_exists:
|
||||||
|
# Look for default interface in routing table
|
||||||
|
with open('/proc/net/route', 'rb') as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) > 1:
|
||||||
|
iface, destination = parts[:2]
|
||||||
|
if not destination.replace(b'0', b''):
|
||||||
|
interface = iface.decode('utf-8')
|
||||||
|
break
|
||||||
|
if interface == 'detect':
|
||||||
|
# Choose interface with most total activity, excluding some
|
||||||
|
# well known interface names
|
||||||
|
interface, total = 'eth0', -1
|
||||||
|
for name, rx, tx in _get_interfaces():
|
||||||
|
base = self.replace_num_pat.match(name)
|
||||||
|
if None in (base, rx, tx) or base.group() in ('lo', 'vmnet', 'sit'):
|
||||||
|
continue
|
||||||
|
activity = rx + tx
|
||||||
|
if activity > total:
|
||||||
|
total = activity
|
||||||
|
interface = name
|
||||||
|
|
||||||
|
try:
|
||||||
|
idata = self.interfaces[interface]
|
||||||
|
try:
|
||||||
|
idata['prev'] = idata['last']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
idata = {}
|
||||||
|
if self.run_once:
|
||||||
|
idata['prev'] = (monotonic(), _get_bytes(interface))
|
||||||
|
self.shutdown_event.wait(self.interval)
|
||||||
|
self.interfaces[interface] = idata
|
||||||
|
|
||||||
|
idata['last'] = (monotonic(), _get_bytes(interface))
|
||||||
|
return idata.copy()
|
||||||
|
|
||||||
|
def render_one(self, idata, recv_format='DL {value:>8}', sent_format='UL {value:>8}', suffix='B/s', si_prefix=False, **kwargs):
|
||||||
|
if not idata or 'prev' not in idata:
|
||||||
|
return None
|
||||||
|
|
||||||
|
t1, b1 = idata['prev']
|
||||||
|
t2, b2 = idata['last']
|
||||||
|
measure_interval = t2 - t1
|
||||||
|
|
||||||
|
if None in (b1, b2):
|
||||||
|
return None
|
||||||
|
|
||||||
|
r = []
|
||||||
|
for i, key in zip((0, 1), ('recv', 'sent')):
|
||||||
|
format = locals()[key + '_format']
|
||||||
|
try:
|
||||||
|
value = (b2[i] - b1[i]) / measure_interval
|
||||||
|
except ZeroDivisionError:
|
||||||
|
self.warn('Measure interval zero.')
|
||||||
|
value = 0
|
||||||
|
max_key = key + '_max'
|
||||||
|
is_gradient = max_key in kwargs
|
||||||
|
hl_groups = ['network_load_' + key, 'network_load']
|
||||||
|
if is_gradient:
|
||||||
|
hl_groups[:0] = (group + '_gradient' for group in hl_groups)
|
||||||
|
r.append({
|
||||||
|
'contents': format.format(value=humanize_bytes(value, suffix, si_prefix)),
|
||||||
|
'divider_highlight_group': 'background:divider',
|
||||||
|
'highlight_group': hl_groups,
|
||||||
|
})
|
||||||
|
if is_gradient:
|
||||||
|
max = kwargs[max_key]
|
||||||
|
if value >= max:
|
||||||
|
r[-1]['gradient_level'] = 100
|
||||||
|
else:
|
||||||
|
r[-1]['gradient_level'] = value * 100.0 / max
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
network_load = with_docstring(NetworkLoadSegment(),
|
||||||
|
'''Return the network load.
|
||||||
|
|
||||||
|
Uses the ``psutil`` module if available for multi-platform compatibility,
|
||||||
|
falls back to reading
|
||||||
|
:file:`/sys/class/net/{interface}/statistics/{rx,tx}_bytes`.
|
||||||
|
|
||||||
|
:param str interface:
|
||||||
|
network interface to measure (use the special value "detect" to have powerline try to auto-detect the network interface)
|
||||||
|
:param str suffix:
|
||||||
|
string appended to each load string
|
||||||
|
:param bool si_prefix:
|
||||||
|
use SI prefix, e.g. MB instead of MiB
|
||||||
|
:param str recv_format:
|
||||||
|
format string, receives ``value`` as argument
|
||||||
|
:param str sent_format:
|
||||||
|
format string, receives ``value`` as argument
|
||||||
|
:param float recv_max:
|
||||||
|
maximum number of received bytes per second. Is only used to compute
|
||||||
|
gradient level
|
||||||
|
:param float sent_max:
|
||||||
|
maximum number of sent bytes per second. Is only used to compute gradient
|
||||||
|
level
|
||||||
|
|
||||||
|
Divider highlight group used: ``background:divider``.
|
||||||
|
|
||||||
|
Highlight groups used: ``network_load_sent_gradient`` (gradient) or ``network_load_recv_gradient`` (gradient) or ``network_load_gradient`` (gradient), ``network_load_sent`` or ``network_load_recv`` or ``network_load``.
|
||||||
|
''')
|
270
powerline/segments/common/players.py
Normal file
270
powerline/segments/common/players.py
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from powerline.lib.shell import asrun, run_cmd
|
||||||
|
from powerline.lib.unicode import out_u
|
||||||
|
from powerline.segments import Segment
|
||||||
|
|
||||||
|
|
||||||
|
STATE_SYMBOLS = {
|
||||||
|
'fallback': '',
|
||||||
|
'play': '>',
|
||||||
|
'pause': '~',
|
||||||
|
'stop': 'X',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NowPlayingSegment(Segment):
|
||||||
|
def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs):
|
||||||
|
player_func = getattr(self, 'player_{0}'.format(player))
|
||||||
|
stats = {
|
||||||
|
'state': 'fallback',
|
||||||
|
'album': None,
|
||||||
|
'artist': None,
|
||||||
|
'title': None,
|
||||||
|
'elapsed': None,
|
||||||
|
'total': None,
|
||||||
|
}
|
||||||
|
func_stats = player_func(**kwargs)
|
||||||
|
if not func_stats:
|
||||||
|
return None
|
||||||
|
stats.update(func_stats)
|
||||||
|
stats['state_symbol'] = state_symbols.get(stats['state'])
|
||||||
|
return format.format(**stats)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_state(state):
|
||||||
|
state = state.lower()
|
||||||
|
if 'play' in state:
|
||||||
|
return 'play'
|
||||||
|
if 'pause' in state:
|
||||||
|
return 'pause'
|
||||||
|
if 'stop' in state:
|
||||||
|
return 'stop'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_seconds(seconds):
|
||||||
|
return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60))
|
||||||
|
|
||||||
|
def player_cmus(self, pl):
|
||||||
|
'''Return cmus player information.
|
||||||
|
|
||||||
|
cmus-remote -Q returns data with multi-level information i.e.
|
||||||
|
status playing
|
||||||
|
file <file_name>
|
||||||
|
tag artist <artist_name>
|
||||||
|
tag title <track_title>
|
||||||
|
tag ..
|
||||||
|
tag n
|
||||||
|
set continue <true|false>
|
||||||
|
set repeat <true|false>
|
||||||
|
set ..
|
||||||
|
set n
|
||||||
|
|
||||||
|
For the information we are looking for we don’t really care if we’re on
|
||||||
|
the tag level or the set level. The dictionary comprehension in this
|
||||||
|
method takes anything in ignore_levels and brings the key inside that
|
||||||
|
to the first level of the dictionary.
|
||||||
|
'''
|
||||||
|
now_playing_str = run_cmd(pl, ['cmus-remote', '-Q'])
|
||||||
|
if not now_playing_str:
|
||||||
|
return
|
||||||
|
ignore_levels = ('tag', 'set',)
|
||||||
|
now_playing = dict(((token[0] if token[0] not in ignore_levels else token[1],
|
||||||
|
(' '.join(token[1:]) if token[0] not in ignore_levels else
|
||||||
|
' '.join(token[2:]))) for token in [line.split(' ') for line in now_playing_str.split('\n')[:-1]]))
|
||||||
|
state = self._convert_state(now_playing.get('status'))
|
||||||
|
return {
|
||||||
|
'state': state,
|
||||||
|
'album': now_playing.get('album'),
|
||||||
|
'artist': now_playing.get('artist'),
|
||||||
|
'title': now_playing.get('title'),
|
||||||
|
'elapsed': self._convert_seconds(now_playing.get('position', 0)),
|
||||||
|
'total': self._convert_seconds(now_playing.get('duration', 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
def player_mpd(self, pl, host='localhost', port=6600):
|
||||||
|
try:
|
||||||
|
import mpd
|
||||||
|
except ImportError:
|
||||||
|
now_playing = run_cmd(pl, ['mpc', 'current', '-f', '%album%\n%artist%\n%title%\n%time%', '-h', str(host), '-p', str(port)])
|
||||||
|
if not now_playing:
|
||||||
|
return
|
||||||
|
now_playing = now_playing.split('\n')
|
||||||
|
return {
|
||||||
|
'album': now_playing[0],
|
||||||
|
'artist': now_playing[1],
|
||||||
|
'title': now_playing[2],
|
||||||
|
'total': now_playing[3],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
client = mpd.MPDClient()
|
||||||
|
client.connect(host, port)
|
||||||
|
now_playing = client.currentsong()
|
||||||
|
if not now_playing:
|
||||||
|
return
|
||||||
|
status = client.status()
|
||||||
|
client.close()
|
||||||
|
client.disconnect()
|
||||||
|
return {
|
||||||
|
'state': status.get('state'),
|
||||||
|
'album': now_playing.get('album'),
|
||||||
|
'artist': now_playing.get('artist'),
|
||||||
|
'title': now_playing.get('title'),
|
||||||
|
'elapsed': self._convert_seconds(now_playing.get('elapsed', 0)),
|
||||||
|
'total': self._convert_seconds(now_playing.get('time', 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
def player_dbus(self, player_name, bus_name, player_path, iface_prop, iface_player):
|
||||||
|
try:
|
||||||
|
import dbus
|
||||||
|
except ImportError:
|
||||||
|
self.exception('Could not add {0} segment: requires dbus module', player_name)
|
||||||
|
return
|
||||||
|
bus = dbus.SessionBus()
|
||||||
|
try:
|
||||||
|
player = bus.get_object(bus_name, player_path)
|
||||||
|
iface = dbus.Interface(player, iface_prop)
|
||||||
|
info = iface.Get(iface_player, 'Metadata')
|
||||||
|
status = iface.Get(iface_player, 'PlaybackStatus')
|
||||||
|
except dbus.exceptions.DBusException:
|
||||||
|
return
|
||||||
|
if not info:
|
||||||
|
return
|
||||||
|
album = out_u(info.get('xesam:album'))
|
||||||
|
title = out_u(info.get('xesam:title'))
|
||||||
|
artist = info.get('xesam:artist')
|
||||||
|
state = self._convert_state(status)
|
||||||
|
if artist:
|
||||||
|
artist = out_u(artist[0])
|
||||||
|
return {
|
||||||
|
'state': state,
|
||||||
|
'album': album,
|
||||||
|
'artist': artist,
|
||||||
|
'title': title,
|
||||||
|
'total': self._convert_seconds(info.get('mpris:length') / 1e6),
|
||||||
|
}
|
||||||
|
|
||||||
|
def player_spotify_dbus(self, pl):
|
||||||
|
return self.player_dbus(
|
||||||
|
player_name='Spotify',
|
||||||
|
bus_name='com.spotify.qt',
|
||||||
|
player_path='/',
|
||||||
|
iface_prop='org.freedesktop.DBus.Properties',
|
||||||
|
iface_player='org.freedesktop.MediaPlayer2',
|
||||||
|
)
|
||||||
|
|
||||||
|
def player_clementine(self, pl):
|
||||||
|
return self.player_dbus(
|
||||||
|
player_name='Clementine',
|
||||||
|
bus_name='org.mpris.MediaPlayer2.clementine',
|
||||||
|
player_path='/org/mpris/MediaPlayer2',
|
||||||
|
iface_prop='org.freedesktop.DBus.Properties',
|
||||||
|
iface_player='org.mpris.MediaPlayer2.Player',
|
||||||
|
)
|
||||||
|
|
||||||
|
def player_spotify_apple_script(self, pl):
|
||||||
|
status_delimiter = '-~`/='
|
||||||
|
ascript = '''
|
||||||
|
tell application "System Events"
|
||||||
|
set process_list to (name of every process)
|
||||||
|
end tell
|
||||||
|
|
||||||
|
if process_list contains "Spotify" then
|
||||||
|
tell application "Spotify"
|
||||||
|
if player state is playing or player state is paused then
|
||||||
|
set track_name to name of current track
|
||||||
|
set artist_name to artist of current track
|
||||||
|
set album_name to album of current track
|
||||||
|
set track_length to duration of current track
|
||||||
|
set now_playing to "" & player state & "{0}" & album_name & "{0}" & artist_name & "{0}" & track_name & "{0}" & track_length
|
||||||
|
return now_playing
|
||||||
|
else
|
||||||
|
return player state
|
||||||
|
end if
|
||||||
|
|
||||||
|
end tell
|
||||||
|
else
|
||||||
|
return "stopped"
|
||||||
|
end if
|
||||||
|
'''.format(status_delimiter)
|
||||||
|
|
||||||
|
spotify = asrun(pl, ascript)
|
||||||
|
if not asrun:
|
||||||
|
return None
|
||||||
|
|
||||||
|
spotify_status = spotify.split(status_delimiter)
|
||||||
|
state = self._convert_state(spotify_status[0])
|
||||||
|
if state == 'stop':
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
'state': state,
|
||||||
|
'album': spotify_status[1],
|
||||||
|
'artist': spotify_status[2],
|
||||||
|
'title': spotify_status[3],
|
||||||
|
'total': self._convert_seconds(int(spotify_status[4]))
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
__import__('dbus')
|
||||||
|
except ImportError:
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
player_spotify = player_spotify_apple_script
|
||||||
|
else:
|
||||||
|
player_spotify = player_spotify_dbus
|
||||||
|
else:
|
||||||
|
player_spotify = player_spotify_dbus
|
||||||
|
|
||||||
|
def player_rhythmbox(self, pl):
|
||||||
|
now_playing = run_cmd(pl, ['rhythmbox-client', '--no-start', '--no-present', '--print-playing-format', '%at\n%aa\n%tt\n%te\n%td'])
|
||||||
|
if not now_playing:
|
||||||
|
return
|
||||||
|
now_playing = now_playing.split('\n')
|
||||||
|
return {
|
||||||
|
'album': now_playing[0],
|
||||||
|
'artist': now_playing[1],
|
||||||
|
'title': now_playing[2],
|
||||||
|
'elapsed': now_playing[3],
|
||||||
|
'total': now_playing[4],
|
||||||
|
}
|
||||||
|
|
||||||
|
def player_rdio(self, pl):
|
||||||
|
status_delimiter = '-~`/='
|
||||||
|
ascript = '''
|
||||||
|
tell application "System Events"
|
||||||
|
set rdio_active to the count(every process whose name is "Rdio")
|
||||||
|
if rdio_active is 0 then
|
||||||
|
return
|
||||||
|
end if
|
||||||
|
end tell
|
||||||
|
tell application "Rdio"
|
||||||
|
set rdio_name to the name of the current track
|
||||||
|
set rdio_artist to the artist of the current track
|
||||||
|
set rdio_album to the album of the current track
|
||||||
|
set rdio_duration to the duration of the current track
|
||||||
|
set rdio_state to the player state
|
||||||
|
set rdio_elapsed to the player position
|
||||||
|
return rdio_name & "{0}" & rdio_artist & "{0}" & rdio_album & "{0}" & rdio_elapsed & "{0}" & rdio_duration & "{0}" & rdio_state
|
||||||
|
end tell
|
||||||
|
'''.format(status_delimiter)
|
||||||
|
now_playing = asrun(pl, ascript)
|
||||||
|
if not now_playing:
|
||||||
|
return
|
||||||
|
now_playing = now_playing.split('\n')
|
||||||
|
if len(now_playing) != 6:
|
||||||
|
return
|
||||||
|
state = self._convert_state(now_playing[5])
|
||||||
|
total = self._convert_seconds(now_playing[4])
|
||||||
|
elapsed = self._convert_seconds(float(now_playing[3]) * float(now_playing[4]) / 100)
|
||||||
|
return {
|
||||||
|
'title': now_playing[0],
|
||||||
|
'artist': now_playing[1],
|
||||||
|
'album': now_playing[2],
|
||||||
|
'elapsed': elapsed,
|
||||||
|
'total': total,
|
||||||
|
'state': state,
|
||||||
|
'state_symbol': self.STATE_SYMBOLS.get(state)
|
||||||
|
}
|
||||||
|
now_playing = NowPlayingSegment()
|
174
powerline/segments/common/sys.py
Normal file
174
powerline/segments/common/sys.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from multiprocessing import cpu_count as _cpu_count
|
||||||
|
|
||||||
|
from powerline.lib.threaded import ThreadedSegment
|
||||||
|
from powerline.lib import add_divider_highlight_group
|
||||||
|
from powerline.segments import with_docstring
|
||||||
|
|
||||||
|
|
||||||
|
cpu_count = None
|
||||||
|
|
||||||
|
|
||||||
|
def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track_cpu_count=False):
|
||||||
|
'''Return system load average.
|
||||||
|
|
||||||
|
Highlights using ``system_load_good``, ``system_load_bad`` and
|
||||||
|
``system_load_ugly`` highlighting groups, depending on the thresholds
|
||||||
|
passed to the function.
|
||||||
|
|
||||||
|
:param str format:
|
||||||
|
format string, receives ``avg`` as an argument
|
||||||
|
:param float threshold_good:
|
||||||
|
threshold for gradient level 0: any normalized load average below this
|
||||||
|
value will have this gradient level.
|
||||||
|
:param float threshold_bad:
|
||||||
|
threshold for gradient level 100: any normalized load average above this
|
||||||
|
value will have this gradient level. Load averages between
|
||||||
|
``threshold_good`` and ``threshold_bad`` receive gradient level that
|
||||||
|
indicates relative position in this interval:
|
||||||
|
(``100 * (cur-good) / (bad-good)``).
|
||||||
|
Note: both parameters are checked against normalized load averages.
|
||||||
|
:param bool track_cpu_count:
|
||||||
|
if True powerline will continuously poll the system to detect changes
|
||||||
|
in the number of CPUs.
|
||||||
|
|
||||||
|
Divider highlight group used: ``background:divider``.
|
||||||
|
|
||||||
|
Highlight groups used: ``system_load_gradient`` (gradient) or ``system_load``.
|
||||||
|
'''
|
||||||
|
global cpu_count
|
||||||
|
try:
|
||||||
|
cpu_num = cpu_count = _cpu_count() if cpu_count is None or track_cpu_count else cpu_count
|
||||||
|
except NotImplementedError:
|
||||||
|
pl.warn('Unable to get CPU count: method is not implemented')
|
||||||
|
return None
|
||||||
|
ret = []
|
||||||
|
for avg in os.getloadavg():
|
||||||
|
normalized = avg / cpu_num
|
||||||
|
if normalized < threshold_good:
|
||||||
|
gradient_level = 0
|
||||||
|
elif normalized < threshold_bad:
|
||||||
|
gradient_level = (normalized - threshold_good) * 100.0 / (threshold_bad - threshold_good)
|
||||||
|
else:
|
||||||
|
gradient_level = 100
|
||||||
|
ret.append({
|
||||||
|
'contents': format.format(avg=avg),
|
||||||
|
'highlight_group': ['system_load_gradient', 'system_load'],
|
||||||
|
'divider_highlight_group': 'background:divider',
|
||||||
|
'gradient_level': gradient_level,
|
||||||
|
})
|
||||||
|
ret[0]['contents'] += ' '
|
||||||
|
ret[1]['contents'] += ' '
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
class CPULoadPercentSegment(ThreadedSegment):
|
||||||
|
interval = 1
|
||||||
|
|
||||||
|
def update(self, old_cpu):
|
||||||
|
return psutil.cpu_percent(interval=None)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self.shutdown_event.is_set():
|
||||||
|
try:
|
||||||
|
self.update_value = psutil.cpu_percent(interval=self.interval)
|
||||||
|
except Exception as e:
|
||||||
|
self.exception('Exception while calculating cpu_percent: {0}', str(e))
|
||||||
|
|
||||||
|
def render(self, cpu_percent, format='{0:.0f}%', **kwargs):
|
||||||
|
if not cpu_percent:
|
||||||
|
return None
|
||||||
|
return [{
|
||||||
|
'contents': format.format(cpu_percent),
|
||||||
|
'gradient_level': cpu_percent,
|
||||||
|
'highlight_group': ['cpu_load_percent_gradient', 'cpu_load_percent'],
|
||||||
|
}]
|
||||||
|
except ImportError:
|
||||||
|
class CPULoadPercentSegment(ThreadedSegment):
|
||||||
|
interval = 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def startup(**kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def start():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def shutdown():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def render(cpu_percent, pl, format='{0:.0f}%', **kwargs):
|
||||||
|
pl.warn('Module “psutil” is not installed, thus CPU load is not available')
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
cpu_load_percent = with_docstring(CPULoadPercentSegment(),
|
||||||
|
'''Return the average CPU load as a percentage.
|
||||||
|
|
||||||
|
Requires the ``psutil`` module.
|
||||||
|
|
||||||
|
:param str format:
|
||||||
|
Output format. Accepts measured CPU load as the first argument.
|
||||||
|
|
||||||
|
Highlight groups used: ``cpu_load_percent_gradient`` (gradient) or ``cpu_load_percent``.
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.exists('/proc/uptime'):
|
||||||
|
def _get_uptime():
|
||||||
|
with open('/proc/uptime', 'r') as f:
|
||||||
|
return int(float(f.readline().split()[0]))
|
||||||
|
elif 'psutil' in globals():
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
def _get_uptime():
|
||||||
|
# psutil.BOOT_TIME is not subject to clock adjustments, but time() is.
|
||||||
|
# Thus it is a fallback to /proc/uptime reading and not the reverse.
|
||||||
|
return int(time() - psutil.BOOT_TIME)
|
||||||
|
else:
|
||||||
|
def _get_uptime():
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@add_divider_highlight_group('background:divider')
|
||||||
|
def uptime(pl, days_format='{days:d}d', hours_format=' {hours:d}h', minutes_format=' {minutes:d}m', seconds_format=' {seconds:d}s', shorten_len=3):
|
||||||
|
'''Return system uptime.
|
||||||
|
|
||||||
|
:param str days_format:
|
||||||
|
day format string, will be passed ``days`` as the argument
|
||||||
|
:param str hours_format:
|
||||||
|
hour format string, will be passed ``hours`` as the argument
|
||||||
|
:param str minutes_format:
|
||||||
|
minute format string, will be passed ``minutes`` as the argument
|
||||||
|
:param str seconds_format:
|
||||||
|
second format string, will be passed ``seconds`` as the argument
|
||||||
|
:param int shorten_len:
|
||||||
|
shorten the amount of units (days, hours, etc.) displayed
|
||||||
|
|
||||||
|
Divider highlight group used: ``background:divider``.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
seconds = _get_uptime()
|
||||||
|
except NotImplementedError:
|
||||||
|
pl.warn('Unable to get uptime. You should install psutil module')
|
||||||
|
return None
|
||||||
|
minutes, seconds = divmod(seconds, 60)
|
||||||
|
hours, minutes = divmod(minutes, 60)
|
||||||
|
days, hours = divmod(hours, 24)
|
||||||
|
time_formatted = list(filter(None, [
|
||||||
|
days_format.format(days=days) if days and days_format else None,
|
||||||
|
hours_format.format(hours=hours) if hours and hours_format else None,
|
||||||
|
minutes_format.format(minutes=minutes) if minutes and minutes_format else None,
|
||||||
|
seconds_format.format(seconds=seconds) if seconds and seconds_format else None,
|
||||||
|
]))[0:shorten_len]
|
||||||
|
return ''.join(time_formatted).strip()
|
89
powerline/segments/common/time.py
Normal file
89
powerline/segments/common/time.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def date(pl, format='%Y-%m-%d', istime=False):
|
||||||
|
'''Return the current date.
|
||||||
|
|
||||||
|
:param str format:
|
||||||
|
strftime-style date format string
|
||||||
|
:param bool istime:
|
||||||
|
If true then segment uses ``time`` highlight group.
|
||||||
|
|
||||||
|
Divider highlight group used: ``time:divider``.
|
||||||
|
|
||||||
|
Highlight groups used: ``time`` or ``date``.
|
||||||
|
'''
|
||||||
|
return [{
|
||||||
|
'contents': datetime.now().strftime(format),
|
||||||
|
'highlight_group': (['time'] if istime else []) + ['date'],
|
||||||
|
'divider_highlight_group': 'time:divider' if istime else None,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
UNICODE_TEXT_TRANSLATION = {
|
||||||
|
ord('\''): '’',
|
||||||
|
ord('-'): '‐',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fuzzy_time(pl, unicode_text=False):
|
||||||
|
'''Display the current time as fuzzy time, e.g. "quarter past six".
|
||||||
|
|
||||||
|
:param bool unicode_text:
|
||||||
|
If true then hyphenminuses (regular ASCII ``-``) and single quotes are
|
||||||
|
replaced with unicode dashes and apostrophes.
|
||||||
|
'''
|
||||||
|
hour_str = ['twelve', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven']
|
||||||
|
minute_str = {
|
||||||
|
5: 'five past',
|
||||||
|
10: 'ten past',
|
||||||
|
15: 'quarter past',
|
||||||
|
20: 'twenty past',
|
||||||
|
25: 'twenty-five past',
|
||||||
|
30: 'half past',
|
||||||
|
35: 'twenty-five to',
|
||||||
|
40: 'twenty to',
|
||||||
|
45: 'quarter to',
|
||||||
|
50: 'ten to',
|
||||||
|
55: 'five to',
|
||||||
|
}
|
||||||
|
special_case_str = {
|
||||||
|
(23, 58): 'round about midnight',
|
||||||
|
(23, 59): 'round about midnight',
|
||||||
|
(0, 0): 'midnight',
|
||||||
|
(0, 1): 'round about midnight',
|
||||||
|
(0, 2): 'round about midnight',
|
||||||
|
(12, 0): 'noon',
|
||||||
|
}
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return special_case_str[(now.hour, now.minute)]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
hour = now.hour
|
||||||
|
if now.minute > 32:
|
||||||
|
if hour == 23:
|
||||||
|
hour = 0
|
||||||
|
else:
|
||||||
|
hour += 1
|
||||||
|
if hour > 11:
|
||||||
|
hour = hour - 12
|
||||||
|
hour = hour_str[hour]
|
||||||
|
|
||||||
|
minute = int(round(now.minute / 5.0) * 5)
|
||||||
|
if minute == 60 or minute == 0:
|
||||||
|
result = ' '.join([hour, 'o\'clock'])
|
||||||
|
else:
|
||||||
|
minute = minute_str[minute]
|
||||||
|
result = ' '.join([minute, hour])
|
||||||
|
|
||||||
|
if unicode_text:
|
||||||
|
result = result.translate(UNICODE_TEXT_TRANSLATION)
|
||||||
|
|
||||||
|
return result
|
33
powerline/segments/common/vcs.py
Normal file
33
powerline/segments/common/vcs.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
from powerline.lib.vcs import guess, tree_status
|
||||||
|
from powerline.theme import requires_segment_info, requires_filesystem_watcher
|
||||||
|
|
||||||
|
|
||||||
|
@requires_filesystem_watcher
|
||||||
|
@requires_segment_info
|
||||||
|
def branch(pl, segment_info, create_watcher, status_colors=False):
|
||||||
|
'''Return the current VCS branch.
|
||||||
|
|
||||||
|
:param bool status_colors:
|
||||||
|
determines whether repository status will be used to determine highlighting. Default: False.
|
||||||
|
|
||||||
|
Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``.
|
||||||
|
'''
|
||||||
|
name = segment_info['getcwd']()
|
||||||
|
repo = guess(path=name, create_watcher=create_watcher)
|
||||||
|
if repo is not None:
|
||||||
|
branch = repo.branch()
|
||||||
|
scol = ['branch']
|
||||||
|
if status_colors:
|
||||||
|
try:
|
||||||
|
status = tree_status(repo, pl)
|
||||||
|
except Exception as e:
|
||||||
|
pl.exception('Failed to compute tree status: {0}', str(e))
|
||||||
|
status = '?'
|
||||||
|
scol.insert(0, 'branch_dirty' if status and status.strip() else 'branch_clean')
|
||||||
|
return [{
|
||||||
|
'contents': branch,
|
||||||
|
'highlight_group': scol,
|
||||||
|
}]
|
229
powerline/segments/common/wthr.py
Normal file
229
powerline/segments/common/wthr.py
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
# vim:fileencoding=utf-8:noet
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from powerline.lib.url import urllib_read, urllib_urlencode
|
||||||
|
from powerline.lib.threaded import KwThreadedSegment
|
||||||
|
from powerline.segments import with_docstring
|
||||||
|
|
||||||
|
|
||||||
|
# XXX Warning: module name must not be equal to the segment name as long as this
|
||||||
|
# segment is imported into powerline.segments.common module.
|
||||||
|
|
||||||
|
|
||||||
|
# Weather condition code descriptions available at
|
||||||
|
# http://developer.yahoo.com/weather/#codes
|
||||||
|
weather_conditions_codes = (
|
||||||
|
('tornado', 'stormy'), # 0
|
||||||
|
('tropical_storm', 'stormy'), # 1
|
||||||
|
('hurricane', 'stormy'), # 2
|
||||||
|
('severe_thunderstorms', 'stormy'), # 3
|
||||||
|
('thunderstorms', 'stormy'), # 4
|
||||||
|
('mixed_rain_and_snow', 'rainy' ), # 5
|
||||||
|
('mixed_rain_and_sleet', 'rainy' ), # 6
|
||||||
|
('mixed_snow_and_sleet', 'snowy' ), # 7
|
||||||
|
('freezing_drizzle', 'rainy' ), # 8
|
||||||
|
('drizzle', 'rainy' ), # 9
|
||||||
|
('freezing_rain', 'rainy' ), # 10
|
||||||
|
('showers', 'rainy' ), # 11
|
||||||
|
('showers', 'rainy' ), # 12
|
||||||
|
('snow_flurries', 'snowy' ), # 13
|
||||||
|
('light_snow_showers', 'snowy' ), # 14
|
||||||
|
('blowing_snow', 'snowy' ), # 15
|
||||||
|
('snow', 'snowy' ), # 16
|
||||||
|
('hail', 'snowy' ), # 17
|
||||||
|
('sleet', 'snowy' ), # 18
|
||||||
|
('dust', 'foggy' ), # 19
|
||||||
|
('fog', 'foggy' ), # 20
|
||||||
|
('haze', 'foggy' ), # 21
|
||||||
|
('smoky', 'foggy' ), # 22
|
||||||
|
('blustery', 'foggy' ), # 23
|
||||||
|
('windy', ), # 24
|
||||||
|
('cold', 'day' ), # 25
|
||||||
|
('clouds', 'cloudy'), # 26
|
||||||
|
('mostly_cloudy_night', 'cloudy'), # 27
|
||||||
|
('mostly_cloudy_day', 'cloudy'), # 28
|
||||||
|
('partly_cloudy_night', 'cloudy'), # 29
|
||||||
|
('partly_cloudy_day', 'cloudy'), # 30
|
||||||
|
('clear_night', 'night' ), # 31
|
||||||
|
('sun', 'sunny' ), # 32
|
||||||
|
('fair_night', 'night' ), # 33
|
||||||
|
('fair_day', 'day' ), # 34
|
||||||
|
('mixed_rain_and_hail', 'rainy' ), # 35
|
||||||
|
('hot', 'sunny' ), # 36
|
||||||
|
('isolated_thunderstorms', 'stormy'), # 37
|
||||||
|
('scattered_thunderstorms', 'stormy'), # 38
|
||||||
|
('scattered_thunderstorms', 'stormy'), # 39
|
||||||
|
('scattered_showers', 'rainy' ), # 40
|
||||||
|
('heavy_snow', 'snowy' ), # 41
|
||||||
|
('scattered_snow_showers', 'snowy' ), # 42
|
||||||
|
('heavy_snow', 'snowy' ), # 43
|
||||||
|
('partly_cloudy', 'cloudy'), # 44
|
||||||
|
('thundershowers', 'rainy' ), # 45
|
||||||
|
('snow_showers', 'snowy' ), # 46
|
||||||
|
('isolated_thundershowers', 'rainy' ), # 47
|
||||||
|
)
|
||||||
|
# ('day', (25, 34)),
|
||||||
|
# ('rainy', (5, 6, 8, 9, 10, 11, 12, 35, 40, 45, 47)),
|
||||||
|
# ('cloudy', (26, 27, 28, 29, 30, 44)),
|
||||||
|
# ('snowy', (7, 13, 14, 15, 16, 17, 18, 41, 42, 43, 46)),
|
||||||
|
# ('stormy', (0, 1, 2, 3, 4, 37, 38, 39)),
|
||||||
|
# ('foggy', (19, 20, 21, 22, 23)),
|
||||||
|
# ('sunny', (32, 36)),
|
||||||
|
# ('night', (31, 33))):
|
||||||
|
weather_conditions_icons = {
|
||||||
|
'day': 'DAY',
|
||||||
|
'blustery': 'WIND',
|
||||||
|
'rainy': 'RAIN',
|
||||||
|
'cloudy': 'CLOUDS',
|
||||||
|
'snowy': 'SNOW',
|
||||||
|
'stormy': 'STORM',
|
||||||
|
'foggy': 'FOG',
|
||||||
|
'sunny': 'SUN',
|
||||||
|
'night': 'NIGHT',
|
||||||
|
'windy': 'WINDY',
|
||||||
|
'not_available': 'NA',
|
||||||
|
'unknown': 'UKN',
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_conversions = {
|
||||||
|
'C': lambda temp: temp,
|
||||||
|
'F': lambda temp: (temp * 9 / 5) + 32,
|
||||||
|
'K': lambda temp: temp + 273.15,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note: there are also unicode characters for units: ℃, ℉ and K
|
||||||
|
temp_units = {
|
||||||
|
'C': '°C',
|
||||||
|
'F': '°F',
|
||||||
|
'K': 'K',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WeatherSegment(KwThreadedSegment):
|
||||||
|
interval = 600
|
||||||
|
default_location = None
|
||||||
|
location_urls = {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def key(location_query=None, **kwargs):
|
||||||
|
return location_query
|
||||||
|
|
||||||
|
def get_request_url(self, location_query):
|
||||||
|
try:
|
||||||
|
return self.location_urls[location_query]
|
||||||
|
except KeyError:
|
||||||
|
if location_query is None:
|
||||||
|
location_data = json.loads(urllib_read('http://freegeoip.net/json/'))
|
||||||
|
location = ','.join((
|
||||||
|
location_data['city'],
|
||||||
|
location_data['region_name'],
|
||||||
|
location_data['country_code']
|
||||||
|
))
|
||||||
|
self.info('Location returned by freegeoip is {0}', location)
|
||||||
|
else:
|
||||||
|
location = location_query
|
||||||
|
query_data = {
|
||||||
|
'q':
|
||||||
|
'use "https://raw.githubusercontent.com/yql/yql-tables/master/weather/weather.bylocation.xml" as we;'
|
||||||
|
'select * from we where location="{0}" and unit="c"'.format(location).encode('utf-8'),
|
||||||
|
'format': 'json',
|
||||||
|
}
|
||||||
|
self.location_urls[location_query] = url = (
|
||||||
|
'http://query.yahooapis.com/v1/public/yql?' + urllib_urlencode(query_data))
|
||||||
|
return url
|
||||||
|
|
||||||
|
def compute_state(self, location_query):
|
||||||
|
url = self.get_request_url(location_query)
|
||||||
|
raw_response = urllib_read(url)
|
||||||
|
if not raw_response:
|
||||||
|
self.error('Failed to get response')
|
||||||
|
return
|
||||||
|
response = json.loads(raw_response)
|
||||||
|
condition = response['query']['results']['weather']['rss']['channel']['item']['condition']
|
||||||
|
condition_code = int(condition['code'])
|
||||||
|
temp = float(condition['temp'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
icon_names = weather_conditions_codes[condition_code]
|
||||||
|
except IndexError:
|
||||||
|
if condition_code == 3200:
|
||||||
|
icon_names = ('not_available',)
|
||||||
|
self.warn('Weather is not available for location {0}', self.location)
|
||||||
|
else:
|
||||||
|
icon_names = ('unknown',)
|
||||||
|
self.error('Unknown condition code: {0}', condition_code)
|
||||||
|
|
||||||
|
return (temp, icon_names)
|
||||||
|
|
||||||
|
def render_one(self, weather, icons=None, unit='C', temp_format=None, temp_coldest=-30, temp_hottest=40, **kwargs):
|
||||||
|
if not weather:
|
||||||
|
return None
|
||||||
|
|
||||||
|
temp, icon_names = weather
|
||||||
|
|
||||||
|
for icon_name in icon_names:
|
||||||
|
if icons:
|
||||||
|
if icon_name in icons:
|
||||||
|
icon = icons[icon_name]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
icon = weather_conditions_icons[icon_names[-1]]
|
||||||
|
|
||||||
|
temp_format = temp_format or ('{temp:.0f}' + temp_units[unit])
|
||||||
|
converted_temp = temp_conversions[unit](temp)
|
||||||
|
if temp <= temp_coldest:
|
||||||
|
gradient_level = 0
|
||||||
|
elif temp >= temp_hottest:
|
||||||
|
gradient_level = 100
|
||||||
|
else:
|
||||||
|
gradient_level = (temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest)
|
||||||
|
groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather']
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'contents': icon + ' ',
|
||||||
|
'highlight_group': groups,
|
||||||
|
'divider_highlight_group': 'background:divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'contents': temp_format.format(temp=converted_temp),
|
||||||
|
'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'],
|
||||||
|
'divider_highlight_group': 'background:divider',
|
||||||
|
'gradient_level': gradient_level,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
weather = with_docstring(WeatherSegment(),
|
||||||
|
'''Return weather from Yahoo! Weather.
|
||||||
|
|
||||||
|
Uses GeoIP lookup from http://freegeoip.net/ to automatically determine
|
||||||
|
your current location. This should be changed if you’re in a VPN or if your
|
||||||
|
IP address is registered at another location.
|
||||||
|
|
||||||
|
Returns a list of colorized icon and temperature segments depending on
|
||||||
|
weather conditions.
|
||||||
|
|
||||||
|
:param str unit:
|
||||||
|
temperature unit, can be one of ``F``, ``C`` or ``K``
|
||||||
|
:param str location_query:
|
||||||
|
location query for your current location, e.g. ``oslo, norway``
|
||||||
|
:param dict icons:
|
||||||
|
dict for overriding default icons, e.g. ``{'heavy_snow' : u'❆'}``
|
||||||
|
:param str temp_format:
|
||||||
|
format string, receives ``temp`` as an argument. Should also hold unit.
|
||||||
|
:param float temp_coldest:
|
||||||
|
coldest temperature. Any temperature below it will have gradient level equal
|
||||||
|
to zero.
|
||||||
|
:param float temp_hottest:
|
||||||
|
hottest temperature. Any temperature above it will have gradient level equal
|
||||||
|
to 100. Temperatures between ``temp_coldest`` and ``temp_hottest`` receive
|
||||||
|
gradient level that indicates relative position in this interval
|
||||||
|
(``100 * (cur-coldest) / (hottest-coldest)``).
|
||||||
|
|
||||||
|
Divider highlight group used: ``background:divider``.
|
||||||
|
|
||||||
|
Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_gradient`` (gradient) or ``weather``.
|
||||||
|
Also uses ``weather_conditions_{condition}`` for all weather conditions supported by Yahoo.
|
||||||
|
''')
|
@ -3,7 +3,8 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
|||||||
|
|
||||||
from powerline.theme import requires_segment_info
|
from powerline.theme import requires_segment_info
|
||||||
from powerline.segments import with_docstring
|
from powerline.segments import with_docstring
|
||||||
from powerline.segments.common import CwdSegment
|
from powerline.segments.common.env import CwdSegment
|
||||||
|
from powerline.lib.unicode import out_u
|
||||||
|
|
||||||
|
|
||||||
@requires_segment_info
|
@requires_segment_info
|
||||||
@ -136,7 +137,7 @@ class ShellCwdSegment(CwdSegment):
|
|||||||
def get_shortened_path(self, pl, segment_info, use_shortened_path=True, **kwargs):
|
def get_shortened_path(self, pl, segment_info, use_shortened_path=True, **kwargs):
|
||||||
if use_shortened_path:
|
if use_shortened_path:
|
||||||
try:
|
try:
|
||||||
return segment_info['shortened_path']
|
return out_u(segment_info['shortened_path'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return super(ShellCwdSegment, self).get_shortened_path(pl, segment_info, **kwargs)
|
return super(ShellCwdSegment, self).get_shortened_path(pl, segment_info, **kwargs)
|
||||||
|
@ -28,13 +28,11 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
vim_funcs = {
|
vim_funcs = {
|
||||||
'virtcol': vim_get_func('virtcol', rettype=int),
|
'virtcol': vim_get_func('virtcol', rettype='int'),
|
||||||
'getpos': vim_get_func('getpos'),
|
'getpos': vim_get_func('getpos'),
|
||||||
'fnamemodify': vim_get_func('fnamemodify'),
|
'fnamemodify': vim_get_func('fnamemodify', rettype='bytes'),
|
||||||
'expand': vim_get_func('expand'),
|
'line2byte': vim_get_func('line2byte', rettype='int'),
|
||||||
'bufnr': vim_get_func('bufnr', rettype=int),
|
'line': vim_get_func('line', rettype='int'),
|
||||||
'line2byte': vim_get_func('line2byte', rettype=int),
|
|
||||||
'line': vim_get_func('line', rettype=int),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vim_modes = {
|
vim_modes = {
|
||||||
@ -135,8 +133,8 @@ def visual_range(pl, segment_info, CTRL_V_text='{rows} x {vcols}', v_text_onelin
|
|||||||
vcols Number of virtual columns in the selection
|
vcols Number of virtual columns in the selection
|
||||||
========= =============================================================
|
========= =============================================================
|
||||||
'''
|
'''
|
||||||
sline, scol, soff = [int(v) for v in vim_funcs['getpos']("v")[1:]]
|
sline, scol, soff = [int(v) for v in vim_funcs['getpos']('v')[1:]]
|
||||||
eline, ecol, eoff = [int(v) for v in vim_funcs['getpos'](".")[1:]]
|
eline, ecol, eoff = [int(v) for v in vim_funcs['getpos']('.')[1:]]
|
||||||
svcol = vim_funcs['virtcol']([sline, scol, soff])
|
svcol = vim_funcs['virtcol']([sline, scol, soff])
|
||||||
evcol = vim_funcs['virtcol']([eline, ecol, eoff])
|
evcol = vim_funcs['virtcol']([eline, ecol, eoff])
|
||||||
rows = abs(eline - sline) + 1
|
rows = abs(eline - sline) + 1
|
||||||
@ -225,7 +223,7 @@ def file_scheme(pl, segment_info):
|
|||||||
name will look like :file:`zipfile:/path/to/archive.zip::file.txt`.
|
name will look like :file:`zipfile:/path/to/archive.zip::file.txt`.
|
||||||
``file_scheme`` segment will catch ``zipfile`` part here.
|
``file_scheme`` segment will catch ``zipfile`` part here.
|
||||||
'''
|
'''
|
||||||
name = buffer_name(segment_info['buffer'])
|
name = buffer_name(segment_info)
|
||||||
if not name:
|
if not name:
|
||||||
return None
|
return None
|
||||||
match = SCHEME_RE.match(name)
|
match = SCHEME_RE.match(name)
|
||||||
@ -254,7 +252,7 @@ def file_directory(pl, segment_info, remove_scheme=True, shorten_user=True, shor
|
|||||||
Shorten all directories in :file:`/home/` to :file:`~user/` instead of
|
Shorten all directories in :file:`/home/` to :file:`~user/` instead of
|
||||||
:file:`/home/user/`. Does not work for files with scheme present.
|
:file:`/home/user/`. Does not work for files with scheme present.
|
||||||
'''
|
'''
|
||||||
name = buffer_name(segment_info['buffer'])
|
name = buffer_name(segment_info)
|
||||||
if not name:
|
if not name:
|
||||||
return None
|
return None
|
||||||
match = SCHEME_RE.match(name)
|
match = SCHEME_RE.match(name)
|
||||||
@ -271,7 +269,7 @@ def file_directory(pl, segment_info, remove_scheme=True, shorten_user=True, shor
|
|||||||
return None
|
return None
|
||||||
if shorten_home and file_directory.startswith('/home/'):
|
if shorten_home and file_directory.startswith('/home/'):
|
||||||
file_directory = b'~' + file_directory[6:]
|
file_directory = b'~' + file_directory[6:]
|
||||||
file_directory = file_directory.decode('utf-8', 'powerline_vim_strtrans_error')
|
file_directory = file_directory.decode(segment_info['encoding'], 'powerline_vim_strtrans_error')
|
||||||
return file_directory + os.sep
|
return file_directory + os.sep
|
||||||
|
|
||||||
|
|
||||||
@ -286,7 +284,7 @@ def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]')
|
|||||||
|
|
||||||
Highlight groups used: ``file_name_no_file`` or ``file_name``, ``file_name``.
|
Highlight groups used: ``file_name_no_file`` or ``file_name``, ``file_name``.
|
||||||
'''
|
'''
|
||||||
name = buffer_name(segment_info['buffer'])
|
name = buffer_name(segment_info)
|
||||||
if not name:
|
if not name:
|
||||||
if display_no_file:
|
if display_no_file:
|
||||||
return [{
|
return [{
|
||||||
@ -295,7 +293,7 @@ def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]')
|
|||||||
}]
|
}]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
return os.path.basename(name).decode('utf-8', 'powerline_vim_strtrans_error')
|
return os.path.basename(name).decode(segment_info['encoding'], 'powerline_vim_strtrans_error')
|
||||||
|
|
||||||
|
|
||||||
@window_cached
|
@window_cached
|
||||||
@ -306,7 +304,7 @@ def file_size(pl, suffix='B', si_prefix=False):
|
|||||||
string appended to the file size
|
string appended to the file size
|
||||||
:param bool si_prefix:
|
:param bool si_prefix:
|
||||||
use SI prefix, e.g. MB instead of MiB
|
use SI prefix, e.g. MB instead of MiB
|
||||||
:return: file size or None if the file isn't saved or if the size is too big to fit in a number
|
:return: file size or None if the file isn’t saved or if the size is too big to fit in a number
|
||||||
'''
|
'''
|
||||||
# Note: returns file size in &encoding, not in &fileencoding. But returned
|
# Note: returns file size in &encoding, not in &fileencoding. But returned
|
||||||
# size is updated immediately; and it is valid for any buffer
|
# size is updated immediately; and it is valid for any buffer
|
||||||
@ -470,10 +468,13 @@ def modified_buffers(pl, text='+ ', join_str=','):
|
|||||||
:param str join_str:
|
:param str join_str:
|
||||||
string to use for joining the modified buffer list
|
string to use for joining the modified buffer list
|
||||||
'''
|
'''
|
||||||
buffer_len = vim_funcs['bufnr']('$')
|
buffer_mod_text = join_str.join((
|
||||||
buffer_mod = [str(bufnr) for bufnr in range(1, buffer_len + 1) if int(getbufvar(bufnr, '&modified') or 0)]
|
str(buffer.number)
|
||||||
if buffer_mod:
|
for buffer in vim.buffers
|
||||||
return text + join_str.join(buffer_mod)
|
if int(vim_getbufoption({'buffer': buffer, 'bufnr': buffer.number}, 'modified'))
|
||||||
|
))
|
||||||
|
if buffer_mod_text:
|
||||||
|
return text + buffer_mod_text
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -489,7 +490,7 @@ def branch(pl, segment_info, create_watcher, status_colors=False):
|
|||||||
|
|
||||||
Divider highlight group used: ``branch:divider``.
|
Divider highlight group used: ``branch:divider``.
|
||||||
'''
|
'''
|
||||||
name = segment_info['buffer'].name
|
name = buffer_name(segment_info)
|
||||||
skip = not (name and (not vim_getbufoption(segment_info, 'buftype')))
|
skip = not (name and (not vim_getbufoption(segment_info, 'buftype')))
|
||||||
if not skip:
|
if not skip:
|
||||||
repo = guess(path=name, create_watcher=create_watcher)
|
repo = guess(path=name, create_watcher=create_watcher)
|
||||||
@ -513,7 +514,7 @@ def file_vcs_status(pl, segment_info, create_watcher):
|
|||||||
|
|
||||||
Highlight groups used: ``file_vcs_status``.
|
Highlight groups used: ``file_vcs_status``.
|
||||||
'''
|
'''
|
||||||
name = segment_info['buffer'].name
|
name = buffer_name(segment_info)
|
||||||
skip = not (name and (not vim_getbufoption(segment_info, 'buftype')))
|
skip = not (name and (not vim_getbufoption(segment_info, 'buftype')))
|
||||||
if not skip:
|
if not skip:
|
||||||
repo = guess(path=name, create_watcher=create_watcher)
|
repo = guess(path=name, create_watcher=create_watcher)
|
||||||
|
@ -10,7 +10,21 @@ from powerline.segments.vim import window_cached
|
|||||||
|
|
||||||
|
|
||||||
@window_cached
|
@window_cached
|
||||||
def current_tag(pl):
|
def current_tag(pl, flags='s'):
|
||||||
|
'''Return tag that is near the cursor.
|
||||||
|
|
||||||
|
:param str flags:
|
||||||
|
Specifies additional properties of the displayed tag. Supported values:
|
||||||
|
|
||||||
|
* s - display complete signature
|
||||||
|
* f - display the full hierarchy of the tag
|
||||||
|
* p - display the raw prototype
|
||||||
|
|
||||||
|
More info in the `official documentation`_ (search for
|
||||||
|
“tagbar#currenttag”).
|
||||||
|
|
||||||
|
.. _`official documentation`: https://github.com/majutsushi/tagbar/blob/master/doc/tagbar.txt
|
||||||
|
'''
|
||||||
if not int(vim.eval('exists(":Tagbar")')):
|
if not int(vim.eval('exists(":Tagbar")')):
|
||||||
return
|
return None
|
||||||
return vim.eval('tagbar#currenttag("%s", "")')
|
return vim.eval('tagbar#currenttag("%s", "", "{0}")'.format(flags))
|
||||||
|
@ -163,6 +163,6 @@ class Theme(object):
|
|||||||
elif segment['align'] == 'c':
|
elif segment['align'] == 'c':
|
||||||
segment['contents'] = segment['contents'].center(segment['width'])
|
segment['contents'] = segment['contents'].center(segment['width'])
|
||||||
# We need to yield a copy of the segment, or else mode-dependent
|
# We need to yield a copy of the segment, or else mode-dependent
|
||||||
# segment contents can't be cached correctly e.g. when caching
|
# segment contents can’t be cached correctly e.g. when caching
|
||||||
# non-current window contents for vim statuslines
|
# non-current window contents for vim statuslines
|
||||||
yield segment.copy()
|
yield segment.copy()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
from itertools import count
|
from itertools import count
|
||||||
|
|
||||||
@ -11,9 +12,6 @@ from powerline.bindings.vim import vim_get_func, vim_getvar
|
|||||||
from powerline import Powerline, FailedUnicode
|
from powerline import Powerline, FailedUnicode
|
||||||
from powerline.lib import mergedicts
|
from powerline.lib import mergedicts
|
||||||
|
|
||||||
if not hasattr(vim, 'bindeval'):
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def _override_from(config, override_varname):
|
def _override_from(config, override_varname):
|
||||||
try:
|
try:
|
||||||
@ -134,7 +132,7 @@ class VimPowerline(Powerline):
|
|||||||
set_pycmd(pycmd)
|
set_pycmd(pycmd)
|
||||||
|
|
||||||
# pyeval() and vim.bindeval were both introduced in one patch
|
# pyeval() and vim.bindeval were both introduced in one patch
|
||||||
if not hasattr(vim, 'bindeval') and can_replace_pyeval:
|
if (not hasattr(vim, 'bindeval') and can_replace_pyeval) or pyeval == 'PowerlinePyeval':
|
||||||
vim.command(('''
|
vim.command(('''
|
||||||
function! PowerlinePyeval(e)
|
function! PowerlinePyeval(e)
|
||||||
{pycmd} powerline.do_pyeval()
|
{pycmd} powerline.do_pyeval()
|
||||||
@ -148,23 +146,30 @@ class VimPowerline(Powerline):
|
|||||||
self.update_renderer()
|
self.update_renderer()
|
||||||
__main__.powerline = self
|
__main__.powerline = self
|
||||||
|
|
||||||
if (
|
try:
|
||||||
bool(int(vim.eval("has('gui_running') && argc() == 0")))
|
if (
|
||||||
and not vim.current.buffer.name
|
bool(int(vim.eval('has(\'gui_running\') && argc() == 0')))
|
||||||
and len(vim.windows) == 1
|
and not vim.current.buffer.name
|
||||||
):
|
and len(vim.windows) == 1
|
||||||
# Hack to show startup screen. Problems in GUI:
|
):
|
||||||
# - Defining local value of &statusline option while computing global
|
# Hack to show startup screen. Problems in GUI:
|
||||||
# value purges startup screen.
|
# - Defining local value of &statusline option while computing
|
||||||
# - Defining highlight group while computing statusline purges startup
|
# global value purges startup screen.
|
||||||
# screen.
|
# - Defining highlight group while computing statusline purges
|
||||||
# This hack removes the “while computing statusline” part: both things
|
# startup screen.
|
||||||
# are defined, but they are defined right now.
|
# This hack removes the “while computing statusline” part: both
|
||||||
#
|
# things are defined, but they are defined right now.
|
||||||
# The above condition disables this hack if no GUI is running, Vim did
|
#
|
||||||
# not open any files and there is only one window. Without GUI
|
# The above condition disables this hack if no GUI is running,
|
||||||
# everything works, in other cases startup screen is not shown.
|
# Vim did not open any files and there is only one window.
|
||||||
self.new_window()
|
# Without GUI everything works, in other cases startup screen is
|
||||||
|
# not shown.
|
||||||
|
self.new_window()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# vim.current.buffer.name may raise UnicodeDecodeError when using
|
||||||
|
# Python-3*. Fortunately, this means that current buffer is not
|
||||||
|
# empty buffer, so the above condition should be False.
|
||||||
|
pass
|
||||||
|
|
||||||
# Cannot have this in one line due to weird newline handling (in :execute
|
# Cannot have this in one line due to weird newline handling (in :execute
|
||||||
# context newline is considered part of the command in just the same cases
|
# context newline is considered part of the command in just the same cases
|
||||||
@ -245,14 +250,15 @@ class VimPowerline(Powerline):
|
|||||||
def new_window(self):
|
def new_window(self):
|
||||||
return self.render(*self.win_idx(None))
|
return self.render(*self.win_idx(None))
|
||||||
|
|
||||||
if not hasattr(vim, 'bindeval'):
|
@staticmethod
|
||||||
# Method for PowerlinePyeval function. Is here to reduce the number of
|
def do_pyeval():
|
||||||
# requirements to __main__ globals to just one powerline object
|
'''Evaluate python string passed to PowerlinePyeval
|
||||||
# (previously it required as well vim and json)
|
|
||||||
@staticmethod
|
Is here to reduce the number of requirements to __main__ globals to just
|
||||||
def do_pyeval():
|
one powerline object (previously it required as well vim and json).
|
||||||
import __main__
|
'''
|
||||||
vim.command('return ' + json.dumps(eval(vim.eval('a:e'), __main__.__dict__)))
|
import __main__
|
||||||
|
vim.command('return ' + json.dumps(eval(vim.eval('a:e'), __main__.__dict__)))
|
||||||
|
|
||||||
def setup_components(self, components):
|
def setup_components(self, components):
|
||||||
if components is None:
|
if components is None:
|
||||||
|
@ -12,11 +12,11 @@ from select import select
|
|||||||
from signal import signal, SIGTERM
|
from signal import signal, SIGTERM
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from locale import getpreferredencoding
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from powerline.shell import get_argparser, finish_args, ShellPowerline, write_output
|
from powerline.shell import get_argparser, finish_args, ShellPowerline, write_output
|
||||||
from powerline.lib.monotonic import monotonic
|
from powerline.lib.monotonic import monotonic
|
||||||
|
from powerline.lib.encoding import get_preferred_output_encoding
|
||||||
|
|
||||||
|
|
||||||
is_daemon = False
|
is_daemon = False
|
||||||
@ -74,6 +74,7 @@ def render(args, environ, cwd):
|
|||||||
args.renderer_module,
|
args.renderer_module,
|
||||||
tuple(args.config) if args.config else None,
|
tuple(args.config) if args.config else None,
|
||||||
tuple(args.theme_option) if args.theme_option else None,
|
tuple(args.theme_option) if args.theme_option else None,
|
||||||
|
tuple(args.config_path) if args.config_path else None,
|
||||||
)
|
)
|
||||||
finish_args(args)
|
finish_args(args)
|
||||||
powerline = None
|
powerline = None
|
||||||
@ -143,7 +144,7 @@ def do_write(conn, result):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
encoding = getpreferredencoding() or 'UTF-8'
|
encoding = get_preferred_output_encoding()
|
||||||
|
|
||||||
|
|
||||||
def safe_bytes(o, encoding=encoding):
|
def safe_bytes(o, encoding=encoding):
|
||||||
@ -397,7 +398,7 @@ def main():
|
|||||||
daemonize()
|
daemonize()
|
||||||
|
|
||||||
if use_filesystem:
|
if use_filesystem:
|
||||||
# Create a locked pid file containing the daemon's PID
|
# Create a locked pid file containing the daemon’s PID
|
||||||
if lockpidfile() is None:
|
if lockpidfile() is None:
|
||||||
if not args.quiet:
|
if not args.quiet:
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
|
@ -8,14 +8,14 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output
|
from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output
|
||||||
except ImportError:
|
except ImportError:
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))))
|
||||||
from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output
|
from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output
|
||||||
|
|
||||||
|
from powerline.lib.unicode import get_preferred_output_encoding
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3,):
|
if sys.version_info < (3,):
|
||||||
write = sys.stdout.write
|
write = sys.stdout.write
|
||||||
@ -28,4 +28,4 @@ if __name__ == '__main__':
|
|||||||
finish_args(args)
|
finish_args(args)
|
||||||
powerline = ShellPowerline(args, run_once=True)
|
powerline = ShellPowerline(args, run_once=True)
|
||||||
segment_info = {'args': args, 'environ': os.environ}
|
segment_info = {'args': args, 'environ': os.environ}
|
||||||
write_output(args, powerline, segment_info, write, getpreferredencoding())
|
write_output(args, powerline, segment_info, write, get_preferred_output_encoding())
|
||||||
|
4
setup.py
4
setup.py
@ -54,7 +54,7 @@ else:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='powerline-status',
|
name='powerline-status',
|
||||||
version='1.0',
|
version='1.1',
|
||||||
description='The ultimate statusline/prompt utility.',
|
description='The ultimate statusline/prompt utility.',
|
||||||
long_description=README,
|
long_description=README,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
@ -79,7 +79,7 @@ setup(
|
|||||||
author_email='kim.silkebaekken+vim@gmail.com',
|
author_email='kim.silkebaekken+vim@gmail.com',
|
||||||
url='https://github.com/Lokaltog/powerline',
|
url='https://github.com/Lokaltog/powerline',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
# XXX Python 3 doesn't allow compiled C files to be included in the scripts
|
# XXX Python 3 doesn’t allow compiled C files to be included in the scripts
|
||||||
# list below. This is because Python 3 distutils tries to decode the file to
|
# list below. This is because Python 3 distutils tries to decode the file to
|
||||||
# ASCII, and fails when powerline-client is a binary.
|
# ASCII, and fails when powerline-client is a binary.
|
||||||
#
|
#
|
||||||
|
@ -42,7 +42,10 @@ def urllib_read(query_url):
|
|||||||
elif query_url.startswith('http://freegeoip.net/json/'):
|
elif query_url.startswith('http://freegeoip.net/json/'):
|
||||||
return '{"city": "Meppen", "region_code": "06", "region_name": "Niedersachsen", "areacode": "", "ip": "82.145.55.16", "zipcode": "49716", "longitude": 7.3167, "country_name": "Germany", "country_code": "DE", "metrocode": "", "latitude": 52.6833}'
|
return '{"city": "Meppen", "region_code": "06", "region_name": "Niedersachsen", "areacode": "", "ip": "82.145.55.16", "zipcode": "49716", "longitude": 7.3167, "country_name": "Germany", "country_code": "DE", "metrocode": "", "latitude": 52.6833}'
|
||||||
elif query_url.startswith('http://query.yahooapis.com/v1/public/'):
|
elif query_url.startswith('http://query.yahooapis.com/v1/public/'):
|
||||||
return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"-9","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
if 'Meppen' in query_url:
|
||||||
|
return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"-9","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
||||||
|
elif 'Moscow' in query_url:
|
||||||
|
return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"19","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
41
tests/run_daemon_tests.sh
Executable file
41
tests/run_daemon_tests.sh
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
FAILED=0
|
||||||
|
export ADDRESS="powerline-ipc-test-$$"
|
||||||
|
echo "Powerline address: $ADDRESS"
|
||||||
|
if $PYTHON scripts/powerline-daemon -s$ADDRESS ; then
|
||||||
|
sleep 1
|
||||||
|
if ! ( \
|
||||||
|
$PYTHON client/powerline.py --socket $ADDRESS -p/dev/null shell left | \
|
||||||
|
grep 'file not found'
|
||||||
|
) ; then
|
||||||
|
echo "-p/dev/null argument ignored or not treated properly"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
if ( \
|
||||||
|
$PYTHON client/powerline.py --socket $ADDRESS \
|
||||||
|
-p$PWD/powerline/config_files shell left | \
|
||||||
|
grep 'file not found'
|
||||||
|
) ; then
|
||||||
|
echo "-p/dev/null argument remembered while it should not"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
if ! ( \
|
||||||
|
cd tests && \
|
||||||
|
$PYTHON ../client/powerline.py --socket $ADDRESS \
|
||||||
|
-p$PWD/../powerline/config_files shell left | \
|
||||||
|
grep 'tests'
|
||||||
|
) ; then
|
||||||
|
echo "Output lacks string “tests”"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Daemon exited with status $?"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
if $PYTHON scripts/powerline-daemon -s$ADDRESS -k ; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo "powerline-daemon -k failed with exit code $?"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
exit $FAILED
|
@ -653,9 +653,9 @@ class TestVim(TestCase):
|
|||||||
window = vim_module.current.window
|
window = vim_module.current.window
|
||||||
window_id = 1
|
window_id = 1
|
||||||
winnr = window.number
|
winnr = window.number
|
||||||
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0abc%#Pl_4_192_NONE_None_NONE#>>')
|
self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_3_8404992_4_192_underline#\xc2\xa0abc%#Pl_4_192_NONE_None_NONE#>>')
|
||||||
vim_module._environ['TEST'] = 'def'
|
vim_module._environ['TEST'] = 'def'
|
||||||
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0def%#Pl_4_192_NONE_None_NONE#>>')
|
self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_3_8404992_4_192_underline#\xc2\xa0def%#Pl_4_192_NONE_None_NONE#>>')
|
||||||
|
|
||||||
def test_local_themes(self):
|
def test_local_themes(self):
|
||||||
# Regression test: VimPowerline.add_local_theme did not work properly.
|
# Regression test: VimPowerline.add_local_theme did not work properly.
|
||||||
@ -682,7 +682,7 @@ class TestVim(TestCase):
|
|||||||
window = vim_module.current.window
|
window = vim_module.current.window
|
||||||
window_id = 1
|
window_id = 1
|
||||||
winnr = window.number
|
winnr = window.number
|
||||||
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_5_12583104_6_32896_NONE#\xa0\u201cbar\u201d%#Pl_6_32896_NONE_None_NONE#>>')
|
self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_5_12583104_6_32896_NONE#\xc2\xa0\xe2\x80\x9cbar\xe2\x80\x9d%#Pl_6_32896_NONE_None_NONE#>>')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user