diff options
Diffstat (limited to '')
376 files changed, 40043 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..94eab89 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# editorconfig ini file +# Check out http://editorconfig.org for a list of plugins for different +# IDEs/text editors that support this file. Vim plugin to support this: +# +# http://www.vim.org/scripts/script.php?script_id=3934 +# https://github.com/editorconfig/editorconfig-vim +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = tab +# Despite promise somewhere alignment is done only using tabs. Thus setting +# indent_size and tab_width is a requirement. +indent_size = 4 +tab_width = 4 +charset = utf-8 + +[*.rst] +indent_style = space diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e307485 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.rst whitespace=-blank-at-eol diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6491a77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +tags + +*.py[co] +__pycache__ + +*.egg +*.egg-info +dist +build + +message.fail + +/client/powerline + +/tests/tmp +/tests/status diff --git a/.local.vimrc b/.local.vimrc new file mode 100644 index 0000000..c8e1ef3 --- /dev/null +++ b/.local.vimrc @@ -0,0 +1,11 @@ +" Project vimrc file. To be sourced each time you open any file in this +" repository. You may use [vimscript #3393][1] [(homepage)][2] to do this +" automatically. +" +" [1]: http://www.vim.org/scripts/script.php?script_id=3393 +" [2]: https://github.com/thinca/vim-localrc +let g:syntastic_python_flake8_args = '--ignore=W191,E501,E128,W291,E126,E101' +let b:syntastic_checkers = ['flake8'] +unlet! g:python_space_error_highlight +let g:pymode_syntax_indent_errors = 0 +let g:pymode_syntax_space_errors = 0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cf7c407 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false +dist: trusty +cache: + directories: + - $HOME/.cache/pip + - tests/bot-ci +addons: + apt: + packages: + - libssl1.0.0 + - zsh + - tcsh + - mksh + - busybox + # - rc + - socat + - bc +language: python +install: tests/install.sh +script: tests/test.sh +jobs: + include: + - stage: UCS2 python + python: "2.7" + env: >- + USE_UCS2_PYTHON=1 + UCS2_PYTHON_VARIANT="2.7" + - stage: Old Python + python: "2.6" + - python: "3.2" + - stage: PyPy + python: "pypy" + - python: "pypy3" + - stage: Latest Python + python: "2.7" + - python: "3.6" + - stage: Intermediate versions + python: "3.3" + - python: "3.4" + - python: "3.5" + +# vim: et diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..3792b70 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,138 @@ +***************** +How to contribute +***************** + +So you want to contribute to the Powerline project? Awesome! This document +describes the guidelines you should follow when making contributions to the +project. + +**Please note that these guidelines aren't mandatory in any way, but your +pull request will be merged a lot faster if you follow them.** + +Getting started +=============== + +* Make sure you have a `GitHub account <https://github.com/signup/free>`_. +* Submit an `issue on GitHub <https://github.com/powerline/powerline/issues>`_, + assuming one does not already exist. + + * Clearly describe the issue. + * If the issue is a bug: make sure you include steps to reproduce, and + include the earliest revision that you know has the issue. + +* Fork the repository on GitHub. + +Making changes +============== + +* Create a topic branch from where you want to base your work. + + * Powerline uses the `Git Flow + <http://nvie.com/posts/a-successful-git-branching-model/>`_ branching + model. + * Most contributions should be based off the ``develop`` branch. + * Prefix your branch with ``feature/`` if you're working on a new feature. + * Include the issue number in your topic branch, e.g. + ``321-fix-some-error`` or ``feature/123-a-cool-feature``. + +* Make commits of logical units. +* Run your code through ``flake8`` and fix any programming style errors. Use + common sense regarding whitespace warnings, not all warnings need to be + fixed. +* Make sure your commit messages are in the `proper format + <http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_. + The summary must be no longer than 70 characters. Refer to any related + issues with e.g. ``Ref #123`` or ``Fixes #234`` at the bottom of the + commit message. Commit messages can use Markdown with the following + exceptions: + + * No HTML extensions. + * Only indented code blocks (no ``````` blocks). + * Long links should be moved to the bottom if they make the text wrap or + extend past 72 columns. + +* Make sure you have added the necessary tests for your changes. +* Run *all* the tests to assure nothing else was accidentally broken. + +Programming style +----------------- + +* The project uses *tabs for indentation* and *spaces for alignment*, this + is also included in a vim modeline on top of every script file. +* Run your code through ``flake8 --ignore=W191,E501,E128,W291,E126,E101`` to fix + any style errors. Use common sense regarding whitespace warnings, not all + ``flake8`` warnings need to be fixed. +* Trailing whitespace to indicate a continuing paragraph is OK in comments, + documentation and commit messages. +* It is allowed to have too long lines. It is advised though to avoid lines + wider then a hundred of characters. +* Imports have the following structure: + + 1. Shebang and modeline in a form + + .. code-block:: python + + #!/usr/bin/env python + # vim:fileencoding=utf-8:noet + + . Modeline is required, shebang is not. If shebang is present file must end + with + + .. code-block:: python + + if __name__ == '__main__': + # Actual script here + + 2. Module docstring. + 3. ``__future__`` import exactly in a form + + .. code-block:: python + + from __future__ import (unicode_literals, division, absolute_import, print_function) + + (powerline.shell is the only exception due to problems with argparse). It + is not separated by newline with shebang and modeline, but is with + docstring. + 4. Standard python library imports in a form ``import X``. + 5. Standard python library imports in a form ``from X import Y``. + 6. Third-party (non-python and non-powerline) library imports in a form + ``import X``. + 7. Third-party library imports in a form ``from X import Y``. + 8. Powerline non-test imports in a form ``from powerline.X import Y``. + 9. Powerline test imports in a form ``import tests.vim as vim_module``. + 10. Powerline test imports in a form ``from tests.X import Y``. + + Each entry is separated by newline from another entry. Any entry except for + the first and third ones is optional. Example with all entries: + + .. code-block:: python + + #!/usr/bin/env python + # vim:fileencoding=utf-8:noet + + '''Powerline super module''' + + from __future__ import (unicode_literals, division, absolute_import, print_function) + + import sys + + from argparse import ArgumentParser + + import psutil + + from colormath.color_diff import delta_e_cie2000 + + from powerline.lib.unicode import u + + import tests.vim as vim_module + + from tests import TestCase + +Submitting changes +================== + +* Push your changes to a topic branch in your fork of the repository. +* If necessary, use ``git rebase -i <revision>`` to squash or reword commits + before submitting a pull request. +* Submit a pull request to `powerline repository + <https://github.com/powerline/powerline>`_. @@ -0,0 +1,21 @@ +Copyright 2013 Kim Silkebækken and other contributors +https://github.com/powerline/powerline + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..14acc93 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +recursive-include powerline *.json *.vim +recursive-include powerline/bindings *.* +recursive-exclude powerline/bindings *.pyc *.pyo +recursive-include powerline/dist *.* +recursive-include client *.* +recursive-include docs/source *.rst *.py +include docs/Makefile diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..8500db3 --- /dev/null +++ b/README.rst @@ -0,0 +1,95 @@ +Powerline +========= + +:Author: Kim Silkebækken (kim.silkebaekken+vim@gmail.com) +:Source: https://github.com/powerline/powerline +:Version: beta + +**Powerline is a statusline plugin for vim, and provides statuslines and +prompts for several other applications, including zsh, bash, fish, tmux, +IPython, Awesome, i3 and Qtile.** + +* `Support forum`_ (powerline-support@googlegroups.com) +* `Development discussion`_ (powerline-dev@googlegroups.com) + +.. image:: https://api.travis-ci.org/powerline/powerline.svg?branch=develop + :target: `travis-build-status`_ + :alt: Build status + +.. _travis-build-status: https://travis-ci.org/powerline/powerline +.. _`Support forum`: https://groups.google.com/forum/#!forum/powerline-support +.. _`Development discussion`: https://groups.google.com/forum/#!forum/powerline-dev + +Features +-------- + +* **Extensible and feature rich, written in Python.** Powerline was + completely rewritten in Python to get rid of as much vimscript as + possible. This has allowed much better extensibility, leaner and better + config files, and a structured, object-oriented codebase with no mandatory + third-party dependencies other than a Python interpreter. +* **Stable and testable code base.** Using Python has allowed unit testing + of all the project code. The code is tested to work in Python 2.6+ and + Python 3. +* **Support for prompts and statuslines in many applications.** Originally + created exclusively for vim statuslines, the project has evolved to + 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 + other applications that Powerline doesn’t yet support. +* **Configuration and colorschemes written in JSON.** JSON is + a standardized, simple and easy to use file format that allows for easy + user configuration across all of Powerline’s supported applications. +* **Fast and lightweight, with daemon support for even better performance.** + 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 + 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 + launched for prompts and statuslines, which provides excellent + performance. + +*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 / …* + +You should check out some of the Powerline derivatives. The most lightweight +and feature-rich alternative is currently Bailey Ling’s `vim-airline +<https://github.com/vim-airline/vim-airline>`_ project. + +------ + +* Consult the `documentation + <https://powerline.readthedocs.org/en/latest/>`_ for more information and + installation instructions. +* Check out `powerline-fonts <https://github.com/powerline/fonts>`_ for + pre-patched versions of popular, open source coding fonts. + +Screenshots +----------- + +Vim statusline +^^^^^^^^^^^^^^ + +**Mode-dependent highlighting** + +* .. image:: https://raw.github.com/powerline/powerline/develop/docs/source/_static/img/pl-mode-normal.png + :alt: Normal mode +* .. image:: https://raw.github.com/powerline/powerline/develop/docs/source/_static/img/pl-mode-insert.png + :alt: Insert mode +* .. image:: https://raw.github.com/powerline/powerline/develop/docs/source/_static/img/pl-mode-visual.png + :alt: Visual mode +* .. image:: https://raw.github.com/powerline/powerline/develop/docs/source/_static/img/pl-mode-replace.png + :alt: Replace mode + +**Automatic truncation of segments in small windows** + +* .. image:: https://raw.github.com/powerline/powerline/develop/docs/source/_static/img/pl-truncate1.png + :alt: Truncation illustration +* .. image:: https://raw.github.com/powerline/powerline/develop/docs/source/_static/img/pl-truncate2.png + :alt: Truncation illustration +* .. image:: https://raw.github.com/powerline/powerline/develop/docs/source/_static/img/pl-truncate3.png + :alt: Truncation illustration + +---- + +The font in the screenshots is `Pragmata Pro`_ by Fabrizio Schiavi. + +.. _`Pragmata Pro`: http://www.fsd.it/shop/fonts/pragmatapro diff --git a/client/powerline.c b/client/powerline.c new file mode 100644 index 0000000..ff107ec --- /dev/null +++ b/client/powerline.c @@ -0,0 +1,164 @@ +/* vim:fileencoding=utf-8:noet + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <sys/un.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#define HANDLE_ERROR(msg) \ + do { \ + perror(msg); \ + exit(EXIT_FAILURE); \ + } while (0) + +#define TEMP_FAILURE_RETRY(var, expression) \ + do { \ + ptrdiff_t __result; \ + do { \ + __result = (expression); \ + } while (__result == -1L && errno == EINTR); \ + var = __result; \ + } while (0) + +extern char **environ; + +void do_write(int sd, const char *raw, size_t len) { + size_t written = 0; + ptrdiff_t n = -1; + + while (written < len) { + TEMP_FAILURE_RETRY(n, write(sd, raw + written, len - written)); + if (n == -1) { + close(sd); + HANDLE_ERROR("write() failed"); + } + written += (size_t) n; + } +} + +static inline size_t true_sun_len(const struct sockaddr_un *ptr) { +#ifdef __linux__ + /* Because SUN_LEN uses strlen and abstract namespace paths begin + * with a null byte, SUN_LEN is broken for these. Passing the full + * struct size also fails on Linux, so compute manually. The + * abstract namespace is Linux-only. */ + if (ptr->sun_path[0] == '\0') { + return sizeof(ptr->sun_family) + strlen(ptr->sun_path + 1) + 1; + } +#endif +#ifdef SUN_LEN + /* If the vendor provided SUN_LEN, we may as well use it. */ + return SUN_LEN(ptr); +#else + /* SUN_LEN is not POSIX, so if it was not provided, use the struct + * size as a fallback. */ + return sizeof(struct sockaddr_un); +#endif +} + +#ifdef __linux__ +# define ADDRESS_TEMPLATE "powerline-ipc-%d" +# define A +1 +#else +# define ADDRESS_TEMPLATE "/tmp/powerline-ipc-%d" +# define A +#endif + +#define ADDRESS_SIZE sizeof(ADDRESS_TEMPLATE) + (sizeof(uid_t) * 4) +#define NUM_ARGS_SIZE (sizeof(int) * 2 + 1) +#define BUF_SIZE 4096 +#define NEW_ARGV_SIZE 200 + +int main(int argc, char *argv[]) { + int sd = -1; + int i; + ptrdiff_t read_size; + struct sockaddr_un server; + char address_buf[ADDRESS_SIZE]; + const char eof[2] = "\0\0"; + char num_args[NUM_ARGS_SIZE]; + char buf[BUF_SIZE]; + char *newargv[NEW_ARGV_SIZE]; + char *wd = NULL; + char **envp; + const char *address; + int len; + + if (argc < 2) { + printf("Must provide at least one argument.\n"); + return EXIT_FAILURE; + } + + if (argc > 3 && strcmp(argv[1], "--socket") == 0) { + address = argv[2]; + argv += 2; + argc -= 2; + } else { + snprintf(address_buf, ADDRESS_SIZE, ADDRESS_TEMPLATE, getuid()); + address = &(address_buf[0]); + } + + sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sd == -1) + HANDLE_ERROR("socket() failed"); + + memset(&server, 0, sizeof(struct sockaddr_un)); + server.sun_family = AF_UNIX; + strncpy(server.sun_path A, address, strlen(address)); + + if (connect(sd, (struct sockaddr *) &server, true_sun_len(&server)) < 0) { + close(sd); + /* We failed to connect to the daemon, execute powerline instead */ + argc = (argc < NEW_ARGV_SIZE - 1) ? argc : NEW_ARGV_SIZE - 1; + for (i = 1; i < argc; i++) + newargv[i] = argv[i]; + newargv[0] = "powerline-render"; + newargv[argc] = NULL; + execvp("powerline-render", newargv); + } + + len = snprintf(num_args, NUM_ARGS_SIZE, "%x", argc - 1); + do_write(sd, num_args, len); + do_write(sd, eof, 1); + + for (i = 1; i < argc; i++) { + do_write(sd, argv[i], strlen(argv[i])); + do_write(sd, eof, 1); + } + + wd = getcwd(NULL, 0); + if (wd != NULL) { + do_write(sd, wd, strlen(wd)); + free(wd); + wd = NULL; + } + do_write(sd, eof, 1); + + for(envp=environ; *envp; envp++) { + do_write(sd, *envp, strlen(*envp)); + do_write(sd, eof, 1); + } + + do_write(sd, eof, 2); + + read_size = -1; + while (read_size != 0) { + TEMP_FAILURE_RETRY(read_size, read(sd, buf, BUF_SIZE)); + if (read_size == -1) { + close(sd); + HANDLE_ERROR("read() failed"); + } else if (read_size > 0) { + do_write(STDOUT_FILENO, buf, (size_t) read_size); + } + } + + close(sd); + + return 0; +} diff --git a/client/powerline.py b/client/powerline.py new file mode 100755 index 0000000..28492c1 --- /dev/null +++ b/client/powerline.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import socket +import errno +import os + +try: + from posix import environ +except ImportError: + 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: + print('Must provide at least one argument.', file=sys.stderr) + raise SystemExit(1) + +use_filesystem = not sys.platform.lower().startswith('linux') + +if sys.argv[1] == '--socket': + address = sys.argv[2] + if not use_filesystem: + address = '\0' + address + del sys.argv[1:3] +else: + address = ('/tmp/powerline-ipc-%d' if use_filesystem else '\0powerline-ipc-%d') % os.getuid() + +sock = socket.socket(family=socket.AF_UNIX) + + +def eintr_retry_call(func, *args, **kwargs): + while True: + try: + return func(*args, **kwargs) + except EnvironmentError as e: + if getattr(e, 'errno', None) == errno.EINTR: + continue + raise + + +try: + eintr_retry_call(sock.connect, address) +except Exception: + # Run the powerline renderer + args = ['powerline-render'] + sys.argv[1:] + os.execvp('powerline-render', args) + +fenc = get_preferred_output_encoding() + + +def tobytes(s): + if isinstance(s, bytes): + return s + else: + return s.encode(fenc) + + +args = [tobytes('%x' % (len(sys.argv) - 1))] +args.extend((tobytes(s) for s in sys.argv[1:])) + + +try: + cwd = os.getcwd() +except EnvironmentError: + pass +else: + if not isinstance(cwd, bytes): + cwd = cwd.encode(fenc) + args.append(cwd) + + +args.extend((tobytes(k) + b'=' + tobytes(v) for k, v in environ.items())) + +EOF = b'\0\0' + +for a in args: + eintr_retry_call(sock.sendall, a + b'\0') + +eintr_retry_call(sock.sendall, EOF) + +received = [] +while True: + r = sock.recv(4096) + if not r: + break + received.append(r) + +sock.close() + +if sys.version_info < (3,): + sys.stdout.write(b''.join(received)) +else: + sys.stdout.buffer.write(b''.join(received)) diff --git a/client/powerline.sh b/client/powerline.sh new file mode 100755 index 0000000..8bcec22 --- /dev/null +++ b/client/powerline.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +use_filesystem=1 +darwin= +if test -n "$OSTYPE" ; then + # OSTYPE variable is a shell feature. supported by bash and zsh, but not + # dash, busybox or (m)ksh. + if test "${OSTYPE#linux}" '!=' "${OSTYPE}" ; then + use_filesystem= + elif test "${OSTYPE#darwin}" ; then + darwin=1 + fi +elif which uname >/dev/null ; then + if uname -o | grep -iqF linux ; then + use_filesystem= + elif uname -o | grep -iqF darwin ; then + darwin=1 + fi +fi + +if test "$1" = "--socket" ; then + shift + ADDRESS="$1" + shift +else + ADDRESS="powerline-ipc-${UID:-`id -u`}" + test -n "$use_filesystem" && ADDRESS="/tmp/$ADDRESS" +fi + +if test -n "$darwin" ; then + ENV=genv +else + ENV=env +fi + +if test -z "$use_filesystem" ; then + ADDRESS="abstract-client:$ADDRESS" +fi + +# Warning: env -0 does not work in busybox. Consider switching to parsing +# `set` output in this case +( + printf '%x\0' "$#" + for argv in "$@" ; do + printf '%s\0' "$argv" + done + printf '%s\0' "$PWD" + $ENV -0 +) 2>/dev/null | socat -lf/dev/null -t 10 - "$ADDRESS" + +if test $? -ne 0 ; then + powerline-render "$@" +fi diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..e35d885 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +_build diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..3b41221 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,39 @@ +# Makefile for Sphinx documentation +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -T -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +GH_PAGES_SOURCES = source Makefile +GH_SOURCE_BRANCH = develop + +.PHONY: html clean html latexpdf + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + +clean: + -rm -rf $(BUILDDIR)/* + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." diff --git a/docs/source/_static/css/theme_overrides.css b/docs/source/_static/css/theme_overrides.css new file mode 100644 index 0000000..7e39e34 --- /dev/null +++ b/docs/source/_static/css/theme_overrides.css @@ -0,0 +1,6 @@ +.wy-table-responsive > table > tbody > tr > td { + white-space: unset; +} +.wy-table-responsive > table > tbody > tr > td:first-child { + vertical-align: top; +} diff --git a/docs/source/_static/img/icons/cross.png b/docs/source/_static/img/icons/cross.png Binary files differnew file mode 100644 index 0000000..33a3837 --- /dev/null +++ b/docs/source/_static/img/icons/cross.png diff --git a/docs/source/_static/img/icons/error.png b/docs/source/_static/img/icons/error.png Binary files differnew file mode 100644 index 0000000..dbfda22 --- /dev/null +++ b/docs/source/_static/img/icons/error.png diff --git a/docs/source/_static/img/icons/tick.png b/docs/source/_static/img/icons/tick.png Binary files differnew file mode 100644 index 0000000..c277e6b --- /dev/null +++ b/docs/source/_static/img/icons/tick.png diff --git a/docs/source/_static/img/pl-mode-insert.png b/docs/source/_static/img/pl-mode-insert.png Binary files differnew file mode 100644 index 0000000..9b09e18 --- /dev/null +++ b/docs/source/_static/img/pl-mode-insert.png diff --git a/docs/source/_static/img/pl-mode-normal.png b/docs/source/_static/img/pl-mode-normal.png Binary files differnew file mode 100644 index 0000000..29d3716 --- /dev/null +++ b/docs/source/_static/img/pl-mode-normal.png diff --git a/docs/source/_static/img/pl-mode-replace.png b/docs/source/_static/img/pl-mode-replace.png Binary files differnew file mode 100644 index 0000000..d7c89a4 --- /dev/null +++ b/docs/source/_static/img/pl-mode-replace.png diff --git a/docs/source/_static/img/pl-mode-visual.png b/docs/source/_static/img/pl-mode-visual.png Binary files differnew file mode 100644 index 0000000..d654763 --- /dev/null +++ b/docs/source/_static/img/pl-mode-visual.png diff --git a/docs/source/_static/img/pl-truncate1.png b/docs/source/_static/img/pl-truncate1.png Binary files differnew file mode 100644 index 0000000..c687502 --- /dev/null +++ b/docs/source/_static/img/pl-truncate1.png diff --git a/docs/source/_static/img/pl-truncate2.png b/docs/source/_static/img/pl-truncate2.png Binary files differnew file mode 100644 index 0000000..1630f1d --- /dev/null +++ b/docs/source/_static/img/pl-truncate2.png diff --git a/docs/source/_static/img/pl-truncate3.png b/docs/source/_static/img/pl-truncate3.png Binary files differnew file mode 100644 index 0000000..83e5b21 --- /dev/null +++ b/docs/source/_static/img/pl-truncate3.png diff --git a/docs/source/commands.rst b/docs/source/commands.rst new file mode 100644 index 0000000..a35d05f --- /dev/null +++ b/docs/source/commands.rst @@ -0,0 +1,9 @@ +************************************** +Powerline shell commands’ manual pages +************************************** + +.. toctree:: + :maxdepth: 1 + :glob: + + commands/* diff --git a/docs/source/commands/config.rst b/docs/source/commands/config.rst new file mode 100644 index 0000000..3fc25aa --- /dev/null +++ b/docs/source/commands/config.rst @@ -0,0 +1,12 @@ +:orphan: + +powerline-config manual page +============================ + +.. automan:: powerline.commands.config + :prog: powerline-config + +See also +-------- + +:manpage:`powerline(1)` diff --git a/docs/source/commands/daemon.rst b/docs/source/commands/daemon.rst new file mode 100644 index 0000000..d899c73 --- /dev/null +++ b/docs/source/commands/daemon.rst @@ -0,0 +1,12 @@ +:orphan: + +powerline-daemon manual page +============================ + +.. automan:: powerline.commands.daemon + :prog: powerline-daemon + +See also +-------- + +:manpage:`powerline(1)` diff --git a/docs/source/commands/lint.rst b/docs/source/commands/lint.rst new file mode 100644 index 0000000..92d676d --- /dev/null +++ b/docs/source/commands/lint.rst @@ -0,0 +1,14 @@ +:orphan: + +.. _command-powerline-lint: + +powerline-lint manual page +========================== + +.. automan:: powerline.commands.lint + :prog: powerline-lint + +See also +-------- + +:manpage:`powerline(1)`, :manpage:`powerline-config(1)` diff --git a/docs/source/commands/main.rst b/docs/source/commands/main.rst new file mode 100644 index 0000000..178bcb9 --- /dev/null +++ b/docs/source/commands/main.rst @@ -0,0 +1,12 @@ +:orphan: + +powerline manual page +===================== + +.. automan:: powerline.commands.main + :prog: powerline + +See also +-------- + +:manpage:`powerline-daemon(1)`, :manpage:`powerline-config(1)` diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..5577ef6 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,70 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys + + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(os.getcwd())))) +sys.path.insert(0, os.path.abspath(os.getcwd())) + +extensions = [ + 'powerline_autodoc', 'powerline_automan', + 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', +] +source_suffix = '.rst' +master_doc = 'index' +project = 'Powerline' +version = 'beta' +release = 'beta' +exclude_patterns = ['_build'] +pygments_style = 'sphinx' + +html_theme = 'default' +html_static_path = ['_static'] +html_show_copyright = False + +latex_show_urls = 'footnote' +latex_elements = { + 'preamble': ''' + \\DeclareUnicodeCharacter{22EF}{$\\cdots$} % Dots + \\DeclareUnicodeCharacter{2665}{\\ding{170}} % Heart + \\DeclareUnicodeCharacter{2746}{\\ding{105}} % Snow + \\usepackage{pifont} + ''', +} + +man_pages = [] +for doc in os.listdir(os.path.join(os.path.dirname(__file__), 'commands')): + if doc.endswith('.rst'): + name = doc[:-4] + module = 'powerline.commands.{0}'.format(name) + get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser + parser = get_argparser() + description = parser.description + man_pages.append([ + 'commands/' + name, + 'powerline' if name == 'main' else 'powerline-' + name, + description, + '', + 1 + ]) + +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we’re building docs locally + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + except ImportError: + pass + +if on_rtd or html_theme == 'sphinx_rtd_theme': + html_context = { + 'css_files': [ + 'https://media.readthedocs.org/css/sphinx_rtd_theme.css', + 'https://media.readthedocs.org/css/readthedocs-doc-embed.css', + '_static/css/theme_overrides.css', + ], + } diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst new file mode 100644 index 0000000..412fb6a --- /dev/null +++ b/docs/source/configuration.rst @@ -0,0 +1,146 @@ +******************************* +Configuration and customization +******************************* + +.. note:: + **Forking the main GitHub repo is not needed to personalize Powerline + configuration!** Please read through the :ref:`quick-guide` for a quick + introduction to user configuration. + +Powerline is configured with one main configuration file, and with separate +configuration files for themes and colorschemes. All configuration files are +written in JSON, with the exception of segment definitions, which are +written in Python. + +Powerline provides default configurations in the following locations: + +:ref:`Main configuration <config-main>` + :file:`{powerline}/config.json` +:ref:`Colorschemes <config-colorschemes>` + :file:`{powerline}/colorschemes/{name}.json`, + :file:`{powerline}/colorschemes/{extension}/__main__.json`, + :file:`{powerline}/colorschemes/{extension}/{name}.json` +:ref:`Themes <config-themes>` + :file:`{powerline}/themes/{top_theme}.json`, + :file:`{powerline}/themes/{extension}/__main__.json`, + :file:`{powerline}/themes/{extension}/default.json` + +Here `{powerline}` is one of the following: + +#. The default configuration directory located in the main package: + :file:`{powerline_root}/powerline/config_files`. May be absent in some + packages (e.g. when installing via Gentoo ebuilds). +#. If variable ``$XDG_CONFIG_DIRS`` is set and non-empty then to any + :file:`{directory}/powerline` where `{directory}` is a directory listed in + a colon-separated ``$XDG_CONFIG_DIRS`` list. Directories are checked in + reverse order. +#. User configuration directory located in :file:`$XDG_CONFIG_HOME/powerline`. + This usually corresponds to :file:`~/.config/powerline` on all platforms. + +If per-instance configuration is needed please refer to :ref:`Local +configuration overrides <local-configuration-overrides>`. + +.. _configuration-merging: + +.. note:: + Existing multiple configuration files that have the same name, but are placed + in different directories, will be merged. Merging happens in the order given + in the above list of possible `{powerline}` meanings. + + When merging configuration only dictionaries are merged and they are merged + recursively: keys from next file overrule those from the previous unless + corresponding values are both dictionaries in which case these dictionaries + are merged and key is assigned the result of the merge. + +.. note:: Some configuration files (i.e. themes and colorschemes) have two level + of merging: first happens merging described above, second theme- or + colorscheme-specific merging happens. + +.. _quick-guide: + +Quick setup guide +================= + +This guide will help you with the initial configuration of Powerline. + +Look at configuration in :file:`{powerline_root}/powerline/config_files`. If you +want to modify some file you can create :file:`~/.config/powerline` directory +and put modifications there: all configuration files are :ref:`merged +<configuration-merging>` with each other. + +Each extension (vim, tmux, etc.) has its own theme, and they are located in +:file:`{config directory}/themes/{extension}/default.json`. Best way to modify +it is to copy this theme as a whole, remove ``segment_data`` key with +corresponding value if present (unless you need to modify it, in which case only +modifications must be left) and do necessary modifications in the list of +segments (lists are not subject to merging: this is why you need a copy). + +If you want to move, remove or customize any of the provided segments in the +copy, you can do that by updating the segment dictionary in the theme you want +to customize. A segment dictionary looks like this: + +.. code-block:: javascript + + { + "name": "segment_name" + ... + } + +You can move the segment dictionaries around to change the segment +positions, or remove the entire dictionary to remove the segment from the +prompt or statusline. + +.. note:: It’s essential that the contents of all your configuration files + is valid JSON! It’s strongly recommended that you run your configuration + files through ``jsonlint`` after changing them. + +.. note:: + If your modifications appear not to work, run :ref:`powerline-lint script + <command-powerline-lint>`. This script should show you the location of the + error. + +Some segments need a user configuration to work properly. Here’s a couple of +segments that you may want to customize right away: + +**E-mail alert segment** + 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 + that you `generate an application-specific password + <https://accounts.google.com/IssuedAuthSubTokens>`_ for this purpose. + + Open a theme file, scroll down to the ``email_imap_alert`` segment and + 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 + a ``port`` argument. +**Weather segment** + 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 + query. + + 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 + the weather segment and update it to include unit/location query + arguments: + + .. code-block:: javascript + + { + "name": "weather", + "priority": 50, + "args": { + "unit": "F", + "location_query": "oslo, norway" + } + }, + +References +========== + +.. toctree:: + :glob: + + configuration/reference + configuration/segments + configuration/listers + configuration/selectors + configuration/local diff --git a/docs/source/configuration/listers.rst b/docs/source/configuration/listers.rst new file mode 100644 index 0000000..7aaaabc --- /dev/null +++ b/docs/source/configuration/listers.rst @@ -0,0 +1,35 @@ +.. _config-listers: + +**************** +Lister reference +**************** + +Listers are special segment collections which allow to show some list of +segments for each entity in the list of entities (multiply their segments list +by a list of entities). E.g. ``powerline.listers.vim.tablister`` presented with +``powerline.segments.vim.tabnr`` and ``….file_name`` as segments will emit +segments with buffer names and tabpage numbers for each tabpage shown by vim. + +Listers appear in configuration as irregular segments having ``segment_list`` as +their type and ``segments`` key with a list of segments (a bit more details in +:ref:`Themes section of configuration reference <config-themes-segments>`). + +More information in :ref:`Writing listers <dev-listers>` section. + +Vim listers +----------- + +.. automodule:: powerline.listers.vim + :members: + +Pdb listers +----------- + +.. automodule:: powerline.listers.pdb + :members: + +i3wm listers +------------ + +.. automodule:: powerline.listers.i3wm + :members: diff --git a/docs/source/configuration/local.rst b/docs/source/configuration/local.rst new file mode 100644 index 0000000..0f3d110 --- /dev/null +++ b/docs/source/configuration/local.rst @@ -0,0 +1,260 @@ +.. _local-configuration-overrides: + +***************************** +Local configuration overrides +***************************** + +Depending on the application used it is possible to override configuration. Here +is the list: + +Vim overrides +============= + +Vim configuration can be overridden using the following options: + +.. _local-configuration-overrides-vim-config: + +``g:powerline_config_overrides`` + Dictionary, recursively merged with contents of + :file:`powerline/config.json`. + +``g:powerline_theme_overrides`` + Dictionary mapping theme names to theme overrides, recursively merged with + contents of :file:`powerline/themes/vim/{key}.json`. Note that this way some + value (e.g. segment) in a list cannot be redefined, only the whole list + itself: only dictionaries are merged recursively. + +``g:powerline_config_paths`` + Paths list (each path must be expanded, ``~`` shortcut is not supported). + Points to the list of directories which will be searched for configuration. + When this option is present, none of the other locations are searched. + +``g:powerline_no_python_error`` + If this variable is set to a true value it will prevent Powerline from reporting + an error when loaded in a copy of vim without the necessary Python support. + +``g:powerline_use_var_handler`` + This variable may be set to either 0 or 1. If it is set to 1 then Vim will + save log in ``g:powerline_log_messages`` variable in addition to whatever + was configured in :ref:`log_* options <config-common-log>`. Level is always + :ref:`log_level <config-common-log_level>`, same for format. + + .. warning:: + This variable is deprecated. Use :ref:`log_file option + <config-common-log>` in conjunction with + :py:class:`powerline.vim.VimVarHandler` class and :ref:`Vim config + overrides variable <local-configuration-overrides-vim-config>`. Using + this is also the only variant to make saving into the environment + variable the *only* place where log is saved or save into different + variable. + + .. autoclass:: powerline.vim.VimVarHandler + +.. _local-configuration-overrides-script: + +Powerline script overrides +========================== + +Powerline script has a number of options controlling powerline behavior. Here +``VALUE`` always means “some JSON object”. + +``-c KEY.NESTED_KEY=VALUE`` or ``--config-override=KEY.NESTED_KEY=VALUE`` + Overrides options from :file:`powerline/config.json`. + ``KEY.KEY2.KEY3=VALUE`` is a shortcut for ``KEY={"KEY2": {"KEY3": VALUE}}``. + Multiple options (i.e. ``-c K1=V1 -c K2=V2``) are allowed, result (in the + example: ``{"K1": V1, "K2": V2}``) is recursively merged with the contents + of the file. + + If ``VALUE`` is omitted then corresponding key will be removed from the + configuration (if it was present). + +``-t THEME_NAME.KEY.NESTED_KEY=VALUE`` or ``--theme-override=THEME_NAME.KEY.NESTED_KEY=VALUE`` + Overrides options from :file:`powerline/themes/{ext}/{THEME_NAME}.json`. + ``KEY.NESTED_KEY=VALUE`` is processed like described above, ``{ext}`` is the + first argument to powerline script. May be passed multiple times. + + If ``VALUE`` is omitted then corresponding key will be removed from the + configuration (if it was present). + +``-p PATH`` or ``--config-path=PATH`` + Sets directory where configuration should be read from. If present, no + default locations are searched for configuration. No expansions are + performed by powerline script itself, but ``-p ~/.powerline`` will likely be + expanded by the shell to something like ``-p /home/user/.powerline``. + +.. warning:: + Such overrides are suggested for testing purposes only. Use + :ref:`Environment variables overrides <local-configuration-overrides-env>` + for other purposes. + +.. _local-configuration-overrides-env: + +Environment variables overrides +=============================== + +All bindings that use ``POWERLINE_COMMAND`` environment variable support taking +overrides from environment variables. In this case overrides should look like +the following:: + + OVERRIDE='key1.key2.key3=value;key4.key5={"value":1};key6=true;key1.key7=10' + +. This will be parsed into + +.. code-block:: Python + + { + "key1": { + "key2": { + "key3": "value" + }, + "key7": 10, + }, + "key4": { + "key5": { + "value": 1, + }, + }, + "key6": True, + } + +. Rules: + +#. Environment variable must form a semicolon-separated list of key-value pairs: + ``key=value;key2=value2``. +#. Keys are always dot-separated strings that must not contain equals sign (as + well as semicolon) or start with an underscore. They are interpreted + literally and create a nested set of dictionaries: ``k1.k2.k3`` creates + ``{"k1":{"k2":{}}}`` and inside the innermost dictionary last key (``k3`` in + the example) is contained with its value. +#. Value may be empty in which case they are interpreted as an order to remove + some value: ``k1.k2=`` will form ``{"k1":{"k2":REMOVE_THIS_KEY}}`` nested + dictionary where ``k2`` value is a special value that tells + dictionary-merging function to remove ``k2`` rather then replace it with + something. +#. Value may be a JSON strings like ``{"a":1}`` (JSON dictionary), ``["a",1]`` + (JSON list), ``1`` or ``-1`` (JSON number), ``"abc"`` (JSON string) or + ``true``, ``false`` and ``null`` (JSON boolean objects and ``Null`` object + from JSON). General rule is that anything starting with a digit (U+0030 till + U+0039, inclusive), a hyphenminus (U+002D), a quotation mark (U+0022), a left + curly bracket (U+007B) or a left square bracket (U+005B) is considered to be + some JSON object, same for *exact* values ``true``, ``false`` and ``null``. +#. Any other value is considered to be literal string: ``k1=foo:bar`` parses to + ``{"k1": "foo:bar"}``. + +The following environment variables may be used for overrides according to the +above rules: + +``POWERLINE_CONFIG_OVERRIDES`` + Overrides values from :file:`powerline/config.json`. + +``POWERLINE_THEME_OVERRIDES`` + Overrides values from :file:`powerline/themes/{ext}/{key}.json`. Top-level + key is treated as a name of the theme for which overrides are used: e.g. to + disable cwd segment defined in :file:`powerline/themes/shell/default.json` + one needs to use:: + + POWERLINE_THEME_OVERRIDES=default.segment_data.cwd.display=false + +Additionally one environment variable is a usual *colon*-separated list of +directories: ``POWERLINE_CONFIG_PATHS``. This one defines paths which will be +searched for configuration. Empty paths in ``POWERLINE_CONFIG_PATHS`` are +ignored. + +.. note:: + Overrides from environment variables have lower priority then + :ref:`Powerline script overrides <local-configuration-overrides-script>`. + Latter are suggested for tests only. + +Zsh/zpython overrides +===================== + +Here overrides are controlled by similarly to the powerline script, but values +are taken from zsh variables. :ref:`Environment variable overrides +<local-configuration-overrides-env>` are also supported: if variable is a string +this variant is used. + +``POWERLINE_CONFIG_OVERRIDES`` + Overrides options from :file:`powerline/config.json`. Should be a zsh + associative array with keys equal to ``KEY.NESTED_KEY`` and values being + JSON strings. Pair ``KEY.KEY1 VALUE`` is equivalent to ``{"KEY": {"KEY1": + VALUE}}``. All pairs are then recursively merged into one dictionary and + this dictionary is recursively merged with the contents of the file. + +``POWERLINE_THEME_OVERRIDES`` + Overrides options from :file:`powerline/themes/shell/*.json`. Should be + a zsh associative array with keys equal to ``THEME_NAME.KEY.NESTED_KEY`` and + values being JSON strings. Is processed like the above + ``POWERLINE_CONFIG_OVERRIDES``, but only subdictionaries for ``THEME_NAME`` + key are merged with theme configuration when theme with given name is + requested. + +``POWERLINE_CONFIG_PATHS`` + Sets directories where configuration should be read from. If present, no + default locations are searched for configuration. No expansions are + performed by powerline script itself, but zsh usually performs them on its + own if variable without is set without quotes: ``POWERLINE_CONFIG_PATHS=( + ~/example )``. In addition to arrays usual colon-separated “array” string + can be used: ``POWERLINE_CONFIG_PATHS=$HOME/path1:$HOME/path2``. + +Ipython overrides +================= + +Ipython overrides depend on ipython version. Before ipython-0.11 additional +keyword arguments should be passed to setup() function. After ipython-0.11 +``c.Powerline.KEY`` should be used. Supported ``KEY`` strings or keyword +argument names: + +``config_overrides`` + Overrides options from :file:`powerline/config.json`. Should be a dictionary + that will be recursively merged with the contents of the file. + +``theme_overrides`` + Overrides options from :file:`powerline/themes/ipython/*.json`. Should be + a dictionary where keys are theme names and values are dictionaries which + will be recursively merged with the contents of the given theme. + +``config_paths`` + Sets directories where configuration should be read from. If present, no + default locations are searched for configuration. No expansions are + performed thus paths starting with ``~/`` cannot be used: use + :py:func:`os.path.expanduser`. + +Prompt command +============== + +In addition to the above configuration options ``$POWERLINE_COMMAND`` +environment variable can be used to tell shell or tmux to use specific powerline +implementation and ``$POWERLINE_CONFIG_COMMAND`` to tell zsh or tmux where +``powerline-config`` script is located. This is mostly useful for putting +powerline into different directory. + +.. note:: + + ``$POWERLINE_COMMAND`` is always treated as one path in shell bindings, so + path with spaces in it may be used. To specify additional arguments one may + use ``$POWERLINE_COMMAND_ARGS``, but note that this variable exists for + testing purposes only and may be removed. One should use :ref:`Environment + variable overrides <local-configuration-overrides-env>` instead. + +To disable prompt in shell, but still have tmux support or to disable tmux +support environment variables ``$POWERLINE_NO_{SHELL}_PROMPT`` and +``$POWERLINE_NO_{SHELL}_TMUX_SUPPORT`` can be used (substitute ``{SHELL}`` with +the name of the shell (all-caps) that should be affected (e.g. ``BASH``) or use +all-inclusive ``SHELL`` that will disable support for all shells). These +variables have no effect after configuration script was sourced (in fish case: +after ``powerline-setup`` function was run). To disable specific feature support +set one of these variables to some non-empty value. + +In order to keep shell prompt, but avoid launching Python twice to get unused +:ref:`above <config-themes-above>` lines in tcsh ``$POWERLINE_NO_TCSH_ABOVE`` or +``$POWERLINE_NO_SHELL_ABOVE`` variable should be set. + +In order to remove additional space from the end of the right prompt in fish +that was added in order to support multiline prompt ``$POWERLINE_NO_FISH_ABOVE`` +or ``$POWERLINE_NO_SHELL_ABOVE`` variable should be set. + +PDB overrides +============= + +Like shell bindings :ref:`PDB bindings <pdb-prompt>` take overrides from +:ref:`environment variables <local-configuration-overrides-env>`. diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst new file mode 100644 index 0000000..9ea60fc --- /dev/null +++ b/docs/source/configuration/reference.rst @@ -0,0 +1,602 @@ +*********************** +Configuration reference +*********************** + +.. _config-main: + +Main configuration +================== + +:Location: :file:`powerline/config.json` + +The main configuration file defines some common options that applies to all +extensions, as well as some extension-specific options like themes and +colorschemes. + +Common configuration +-------------------- + +Common configuration is a subdictionary that is a value of ``common`` key in +:file:`powerline/config.json` file. + +.. _config-common-term_truecolor: + +``term_truecolor`` + Defines whether to output cterm indices (8-bit) or RGB colors (24-bit) + to the terminal emulator. See the :ref:`term-feature-support-matrix` for + information on whether used terminal emulator supports 24-bit colors. + + This variable is forced to be ``false`` if :ref:`term_escape_style + <config-common-term_escape_style>` option is set to ``"fbterm"`` or if it is + set to ``"auto"`` and powerline detected fbterm. + +.. _config-common-term_escape_style: + +``term_escape_style`` + Defines what escapes sequences should be used. Accepts three variants: + + ======= =================================================================== + Variant Description + ======= =================================================================== + auto ``xterm`` or ``fbterm`` depending on ``$TERM`` variable value: + ``TERM=fbterm`` implies ``fbterm`` escaping style, all other values + select ``xterm`` escaping. + xterm Uses ``\e[{fb};5;{color}m`` for colors (``{fb}`` is either ``38`` + (foreground) or ``48`` (background)). Should be used for most + terminals. + fbterm Uses ``\e[{fb};{color}}`` for colors (``{fb}`` is either ``1`` + (foreground) or ``2`` (background)). Should be used for fbterm: + framebuffer terminal. + ======= =================================================================== + +.. _config-common-ambiwidth: + +``ambiwidth`` + Tells powerline what to do with characters with East Asian Width Class + Ambigious (such as Euro, Registered Sign, Copyright Sign, Greek + letters, Cyrillic letters). Valid values: any positive integer; it is + suggested that this option is only set it to 1 (default) or 2. + +.. _config-common-watcher: + +``watcher`` + Select filesystem watcher. Variants are + + ======= =================================== + Variant Description + ======= =================================== + auto Selects most performant watcher. + inotify Select inotify watcher. Linux only. + stat Select stat-based polling watcher. + uv Select libuv-based watcher. + ======= =================================== + + Default is ``auto``. + +.. _config-common-additional_escapes: + +``additional_escapes`` + Valid for shell extensions, makes sense only if :ref:`term_truecolor + <config-common-term_truecolor>` is enabled. Is to be set from command-line. + Controls additional escaping that is needed for tmux/screen to work with + terminal true color escape codes: normally tmux/screen prevent terminal + emulator from receiving these control codes thus rendering powerline prompt + colorless. Valid values: ``"tmux"``, ``"screen"``, ``null`` (default). + +.. _config-common-paths: + +``paths`` + Defines additional paths which will be searched for modules when using + :ref:`function segment option <config-themes-seg-function>` or :ref:`Vim + local_themes option <config-ext-local_themes>`. Paths defined here have + priority when searching for modules. + +.. _config-common-log: + +``log_file`` + Defines how logs will be handled. There are three variants here: + + #. Absent. In this case logging will be done to stderr: equivalent to + ``[["logging.StreamHandler", []]]`` or ``[null]``. + #. Plain string. In this case logging will be done to the given file: + ``"/file/name"`` is equivalent to ``[["logging.FileHandler", + [["/file/name"]]]]`` or ``["/file/name"]``. Leading ``~/`` is expanded in + the file name, so using ``"~/.log/foo"`` is permitted. If directory + pointed by the option is absent, it will be created, but not its parent. + #. List of handler definitions. Handler definition may either be ``null``, + a string or a list with two or three elements: + + #. Logging class name and module. If module name is absent, it is + equivalent to ``logging.handlers``. + #. Class constructor arguments in a form ``[[args[, kwargs]]]``: accepted + variants are ``[]`` (no arguments), ``[args]`` (e.g. + ``[["/file/name"]]``: only positional arguments) or ``[args, kwargs]`` + (e.g. ``[[], {"host": "localhost", "port": 6666}]``: positional and + keyword arguments, but no positional arguments in the example). + #. Optional logging level. Overrides :ref:`log_level key + <config-common-log_level>` and has the same format. + #. Optional format string. Partially overrides :ref:`log_format key + <config-common-log_format>` and has the same format. “Partially” here + means that it may only specify more critical level. + +.. _config-common-log_level: + +``log_level`` + String, determines logging level. Defaults to ``WARNING``. + +.. _config-common-log_format: + +``log_format`` + String, determines format of the log messages. Defaults to + ``'%(asctime)s:%(level)s:%(message)s'``. + +``interval`` + Number, determines time (in seconds) between checks for changed + configuration. Checks are done in a seprate thread. Use ``null`` to check + for configuration changes on ``.render()`` call in main thread. + Defaults to ``None``. + +``reload_config`` + Boolean, determines whether configuration should be reloaded at all. + Defaults to ``True``. + +.. _config-common-default_top_theme: + +``default_top_theme`` + String, determines which top-level theme will be used as the default. + Defaults to ``powerline_terminus`` in unicode locales and ``ascii`` in + non-unicode locales. See `Themes`_ section for more details. + +Extension-specific configuration +-------------------------------- + +Common configuration is a subdictionary that is a value of ``ext`` key in +:file:`powerline/config.json` file. + +``colorscheme`` + Defines the colorscheme used for this extension. + +.. _config-ext-theme: + +``theme`` + Defines the theme used for this extension. + +.. _config-ext-top_theme: + +``top_theme`` + Defines the top-level theme used for this extension. See `Themes`_ section + for more details. + +.. _config-ext-local_themes: + +``local_themes`` + Defines themes used when certain conditions are met, e.g. for + buffer-specific statuslines in vim. Value depends on extension used. For vim + it is a dictionary ``{matcher_name : theme_name}``, where ``matcher_name`` + is either ``matcher_module.module_attribute`` or ``module_attribute`` + (``matcher_module`` defaults to ``powerline.matchers.vim``) and + ``module_attribute`` should point to a function that returns boolean value + indicating that current buffer has (not) matched conditions. There is an + exception for ``matcher_name`` though: if it is ``__tabline__`` no functions + are loaded. This special theme is used for ``tabline`` Vim option. + + For shell and ipython it is a simple ``{prompt_type : theme_name}``, where + ``prompt_type`` is a string with no special meaning (specifically it does + not refer to any Python function). Shell has ``continuation``, and + ``select`` prompts with rather self-explanatory names, IPython has ``in2``, + ``out`` and ``rewrite`` prompts (refer to IPython documentation for more + details) while ``in`` prompt is the default. + + For wm (:ref:`lemonbar <lemonbar-usage>` only) it is a dictionary + ``{output : theme_name}`` that maps the ``xrandr`` output names to the + local themes to use on that output. + +.. _config-ext-components: + +``components`` + Determines which extension components should be enabled. This key is highly + extension-specific, here is the table of extensions and corresponding + components: + + +---------+----------+-----------------------------------------------------+ + |Extension|Component |Description | + +---------+----------+-----------------------------------------------------+ + |vim |statusline|Makes Vim use powerline statusline. | + | +----------+-----------------------------------------------------+ + | |tabline |Makes Vim use powerline tabline. | + +---------+----------+-----------------------------------------------------+ + |shell |prompt |Makes shell display powerline prompt. | + | +----------+-----------------------------------------------------+ + | |tmux |Makes shell report its current working directory | + | | |and screen width to tmux for tmux powerline | + | | |bindings. | + | | | | + +---------+----------+-----------------------------------------------------+ + + All components are enabled by default. + +.. _config-ext-update_interval: + +``update_interval`` + Determines how often WM status bars need to be updated, in seconds. Only + valid for WM extensions which use ``powerline-daemon``. Defaults to + 2 seconds. + +.. _config-colors: + +Color definitions +================= + +:Location: :file:`powerline/colors.json` + +.. _config-colors-colors: + +``colors`` + Color definitions, consisting of a dict where the key is the name of the + color, and the value is one of the following: + + * A cterm color index. + * 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 + aren’t available in color terminals. + +``gradients`` + Gradient definitions, consisting of a dict where the key is the name of the + gradient, and the value is a list containing one or two items, second item + is optional: + + * A list of cterm color indicies. + * A list of hex color strings. + + It is expected that gradients are defined from least alert color to most + alert or non-alert colors are used. + +.. _config-colorschemes: + +Colorschemes +============ + +:Location: :file:`powerline/colorschemes/{name}.json`, + :file:`powerline/colorschemes/__main__.json`, + :file:`powerline/colorschemes/{extension}/{name}.json` + +Colorscheme files are processed in order given: definitions from each next file +override those from each previous file. It is required that either +:file:`powerline/colorschemes/{name}.json`, or +:file:`powerline/colorschemes/{extension}/{name}.json` exists. + +``name`` + Name of the colorscheme. + +.. _config-colorschemes-groups: + +``groups`` + Segment highlighting groups, consisting of a dict where the key is the + name of the highlighting group (usually the function name for function + segments), and the value is either + + #) a dict that defines the foreground color, background color and + attributes: + + ``fg`` + Foreground color. Must be defined in :ref:`colors + <config-colors-colors>`. + + ``bg`` + Background color. Must be defined in :ref:`colors + <config-colors-colors>`. + + ``attrs`` + List of attributes. Valid values are one or more of ``bold``, + ``italic`` and ``underline``. Note that some attributes may be + unavailable in some applications or terminal emulators. If no + attributes are needed this list should be left empty. + + #) a string (an alias): a name of existing group. This group’s definition + will be used when this color is requested. + +``mode_translations`` + 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. + Consists of a dict where the key is the mode and the value is a dict + with the following options: + + ``colors`` + A dict where the key is the color to be translated in this mode, and + the value is the new color. Both the key and the value must be defined + in :ref:`colors <config-colors-colors>`. + + ``groups`` + Segment highlighting groups for this mode. Same syntax as the main + :ref:`groups <config-colorschemes-groups>` option. + +.. _config-themes: + +Themes +====== + +:Location: :file:`powerline/themes/{top_theme}.json`, + :file:`powerline/themes/{extension}/__main__.json`, + :file:`powerline/themes/{extension}/{name}.json` + +Theme files are processed in order given: definitions from each next file +override those from each previous file. It is required that file +:file:`powerline/themes/{extension}/{name}.json` exists. + +`{top_theme}` component of the file name is obtained either from :ref:`top_theme +extension-specific key <config-ext-top_theme>` or from :ref:`default_top_theme +common configuration key <config-common-default_top_theme>`. Powerline ships +with the following top themes: + +.. _config-top_themes-list: + +========================== ==================================================== +Theme Description +========================== ==================================================== +powerline Default powerline theme with fancy powerline symbols +powerline_unicode7 Theme with powerline dividers and unicode-7 symbols +unicode Theme without any symbols from private use area +unicode_terminus Theme containing only symbols from terminus PCF font +unicode_terminus_condensed Like above, but occupies as less space as possible +powerline_terminus Like unicode_terminus, but with powerline symbols +ascii Theme without any unicode characters at all +========================== ==================================================== + +``name`` + Name of the theme. + +.. _config-themes-default_module: + +``default_module`` + Python module where segments will be looked by default. Defaults to + ``powerline.segments.{ext}``. + +``spaces`` + Defines number of spaces just before the divider (on the right side) or just + after it (on the left side). These spaces will not be added if divider is + not drawn. + +``use_non_breaking_spaces`` + Determines whether non-breaking spaces should be used in place of the + regular ones. This option is needed because regular spaces are not displayed + properly when using powerline with some font configuration. Defaults to + ``True``. + + .. note:: + Unlike all other options this one is only checked once at startup using + whatever theme is :ref:`the default <config-ext-theme>`. If this option + is set in the local themes it will be ignored. This option may also be + ignored in some bindings. + +``outer_padding`` + Defines number of spaces at the end of output (on the right side) or at + the start of output (on the left side). Defaults to ``1``. + + +``dividers`` + Defines the dividers used in all Powerline extensions. + + The ``hard`` dividers are used to divide segments with different + background colors, while the ``soft`` dividers are used to divide + segments with the same background color. + +.. _config-themes-cursor_space: + +``cursor_space`` + Space reserved for user input in shell bindings. It is measured in per + cents. + +``cursor_columns`` + Space reserved for user input in shell bindings. Unlike :ref:`cursor_space + <config-themes-cursor_space>` it is measured in absolute amout of columns. + +.. _config-themes-segment_data: + +``segment_data`` + A dict where keys are segment names or strings ``{module}.{function}``. Used + to specify default values for various keys: + :ref:`after <config-themes-seg-after>`, + :ref:`before <config-themes-seg-before>`, + :ref:`contents <config-themes-seg-contents>` (only for string segments + if :ref:`name <config-themes-seg-name>` is defined), + :ref:`display <config-themes-seg-display>`. + + Key :ref:`args <config-themes-seg-args>` (only for function and + segment_list segments) is handled specially: unlike other values it is + merged with all other values, except that a single ``{module}.{function}`` + key if found prevents merging all ``{function}`` values. + + When using :ref:`local themes <config-ext-local_themes>` values of these + keys are first searched in the segment description, then in ``segment_data`` + key of a local theme, then in ``segment_data`` key of a :ref:`default theme + <config-ext-theme>`. For the :ref:`default theme <config-ext-theme>` itself + step 2 is obviously avoided. + + .. note:: Top-level themes are out of equation here: they are merged + before the above merging process happens. + +.. _config-themes-segments: + +``segments`` + A dict with a ``left`` and a ``right`` lists, consisting of segment + dictionaries. Shell themes may also contain ``above`` list of dictionaries. + Each item in ``above`` list may have ``left`` and ``right`` keys like this + dictionary, but no ``above`` key. + + .. _config-themes-above: + + ``above`` list is used for multiline shell configurations. + + ``left`` and ``right`` lists are used for segments that should be put on the + left or right side in the output. Actual mechanizm of putting segments on + the left or the right depends on used renderer, but most renderers require + one to specify segment with :ref:`width <config-themes-seg-width>` ``auto`` + on either side to make generated line fill all of the available width. + + Each segment dictionary has the following options: + + .. _config-themes-seg-type: + + ``type`` + The segment type. Can be one of ``function`` (default), ``string`` or + ``segment_list``: + + ``function`` + The segment contents is the return value of the function defined in + the :ref:`function option <config-themes-seg-function>`. + + List of function segments is available in :ref:`Segment reference + <config-segments>` section. + + ``string`` + A static string segment where the contents is defined in the + :ref:`contents option <config-themes-seg-contents>`, and the + highlighting group is defined in the :ref:`highlight_groups option + <config-themes-seg-highlight_groups>`. + + ``segment_list`` + Sub-list of segments. This list only allows :ref:`function + <config-themes-seg-function>`, :ref:`segments + <config-themes-seg-segments>` and :ref:`args + <config-themes-seg-args>` options. + + List of lister segments is available in :ref:`Lister reference + <config-listers>` section. + + .. _config-themes-seg-name: + + ``name`` + Segment name. If present allows referring to this segment in + :ref:`segment_data <config-themes-segment_data>` dictionary by this + name. If not ``string`` segments may not be referred there at all and + ``function`` and ``segment_list`` segments may be referred there using + either ``{module}.{function_name}`` or ``{function_name}``, whichever + will be found first. Function name is taken from :ref:`function key + <config-themes-seg-function>`. + + .. note:: + If present prevents ``function`` key from acting as a segment name. + + .. _config-themes-seg-function: + + ``function`` + Function used to get segment contents, in format ``{module}.{function}`` + or ``{function}``. If ``{module}`` is omitted :ref:`default_module + option <config-themes-default_module>` is used. + + .. _config-themes-seg-highlight_groups: + + ``highlight_groups`` + Highlighting group for this segment. Consists of a prioritized list of + highlighting groups, where the first highlighting group that is + available in the colorscheme is used. + + Ignored for segments that have ``function`` type. + + .. _config-themes-seg-before: + + ``before`` + A string which will be prepended to the segment contents. + + .. _config-themes-seg-after: + + ``after`` + A string which will be appended to the segment contents. + + .. _config-themes-seg-contents: + + ``contents`` + Segment contents, only required for ``string`` segments. + + .. _config-themes-seg-args: + + ``args`` + A dict of arguments to be passed to a ``function`` segment. + + .. _config-themes-seg-align: + + ``align`` + Aligns the segments contents to the left (``l``), center (``c``) or + right (``r``). Has no sense if ``width`` key was not specified or if + segment provides its own function for ``auto`` ``width`` handling and + does not care about this option. + + .. _config-themes-seg-width: + + ``width`` + Enforces a specific width for this segment. + + This segment will work as a spacer if the width is set to ``auto``. + Several spacers may be used, and the space will be distributed + equally among all the spacer segments. Spacers may have contents, + either returned by a function or a static string, and the contents + can be aligned with the ``align`` property. + + .. _config-themes-seg-priority: + + ``priority`` + Optional segment priority. Segments with priority ``None`` (the default + priority, represented by ``null`` in json) will always be included, + regardless of the width of the prompt/statusline. + + If the priority is any number, the segment may be removed if the + prompt/statusline width is too small for all the segments to be + rendered. A lower number means that the segment has a higher priority. + + Segments are removed according to their priority, with low priority + segments (i.e. with a greater priority number) being removed first. + + .. _config-themes-seg-draw_divider: + + ``draw_hard_divider``, ``draw_soft_divider`` + Whether to draw a divider between this and the adjacent segment. The + adjacent segment is to the *right* for segments on the *left* side, and + vice versa. Hard dividers are used between segments with different + background colors, soft ones are used between segments with same + background. Both options default to ``True``. + + .. _config-themes-seg-draw_inner_divider: + + ``draw_inner_divider`` + Determines whether inner soft dividers are to be drawn for function + segments. Only applicable for functions returning multiple segments. + Defaults to ``False``. + + .. _config-themes-seg-exclude_modes: + + ``exclude_modes``, ``include_modes`` + A list of modes where this segment will be excluded: the segment is not + included or is included in all modes, *except* for the modes in one of + these lists respectively. If ``exclude_modes`` is not present then it + acts like an empty list (segment is not excluded from any modes). + Without ``include_modes`` it acts like a list with all possible modes + (segment is included in all modes). When there are both + ``exclude_modes`` overrides ``include_modes``. + + .. _config-themes-seg-exclude_function: + + ``exclude_function``, ``include_function`` + Function name in a form ``{name}`` or ``{module}.{name}`` (in the first + form ``{module}`` defaults to ``powerline.selectors.{ext}``). Determines + under which condition specific segment will be included or excluded. By + default segment is always included and never excluded. + ``exclude_function`` overrides ``include_function``. + + .. note:: + Options :ref:`exclude_/include_modes + <config-themes-seg-exclude_modes>` complement + ``exclude_/include_functions``: segment will be included if it is + included by either ``include_mode`` or ``include_function`` and will + be excluded if it is excluded by either ``exclude_mode`` or + ``exclude_function``. + + .. _config-themes-seg-display: + + ``display`` + Boolean. If false disables displaying of the segment. + Defaults to ``True``. + + .. _config-themes-seg-segments: + + ``segments`` + A list of subsegments. diff --git a/docs/source/configuration/segments.rst b/docs/source/configuration/segments.rst new file mode 100644 index 0000000..63b4975 --- /dev/null +++ b/docs/source/configuration/segments.rst @@ -0,0 +1,28 @@ +.. _config-segments: + +***************** +Segment reference +***************** + +Segments +======== + +Segments are written in Python, and the default segments provided with +Powerline are located in :file:`powerline/segments/{extension}.py`. +User-defined segments can be defined in any module in ``sys.path`` or +:ref:`paths common configuration option <config-common-paths>`, import is +always absolute. + +Segments are regular Python functions, and they may accept arguments. All +arguments should have a default value which will be used for themes that +don’t provide an ``args`` dict. + +More information is available in :ref:`Writing segments <dev-segments>` section. + +Available segments +================== + +.. toctree:: + :glob: + + segments/* diff --git a/docs/source/configuration/segments/common.rst b/docs/source/configuration/segments/common.rst new file mode 100644 index 0000000..5d52d69 --- /dev/null +++ b/docs/source/configuration/segments/common.rst @@ -0,0 +1,57 @@ +*************** +Common segments +*************** + +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: diff --git a/docs/source/configuration/segments/i3wm.rst b/docs/source/configuration/segments/i3wm.rst new file mode 100644 index 0000000..d103374 --- /dev/null +++ b/docs/source/configuration/segments/i3wm.rst @@ -0,0 +1,6 @@ +************* +i3wm segments +************* + +.. automodule:: powerline.segments.i3wm + :members: diff --git a/docs/source/configuration/segments/pdb.rst b/docs/source/configuration/segments/pdb.rst new file mode 100644 index 0000000..b9a104b --- /dev/null +++ b/docs/source/configuration/segments/pdb.rst @@ -0,0 +1,7 @@ +************ +PDB segments +************ + +.. automodule:: powerline.segments.pdb + :members: + diff --git a/docs/source/configuration/segments/shell.rst b/docs/source/configuration/segments/shell.rst new file mode 100644 index 0000000..fb3c804 --- /dev/null +++ b/docs/source/configuration/segments/shell.rst @@ -0,0 +1,6 @@ +************** +Shell segments +************** + +.. automodule:: powerline.segments.shell + :members: diff --git a/docs/source/configuration/segments/tmux.rst b/docs/source/configuration/segments/tmux.rst new file mode 100644 index 0000000..1a4a78f --- /dev/null +++ b/docs/source/configuration/segments/tmux.rst @@ -0,0 +1,6 @@ +************* +Tmux segments +************* + +.. automodule:: powerline.segments.tmux + :members: diff --git a/docs/source/configuration/segments/vim.rst b/docs/source/configuration/segments/vim.rst new file mode 100644 index 0000000..5df70d6 --- /dev/null +++ b/docs/source/configuration/segments/vim.rst @@ -0,0 +1,46 @@ +************ +Vim segments +************ + +.. automodule:: powerline.segments.vim + :members: + + +Plugin-specific segments +======================== + +Asynchronous Linter Engine (ALE) segments +----------------------------------------- + +.. automodule:: powerline.segments.vim.plugin.ale + :members: + +Syntastic segments +------------------ + +.. automodule:: powerline.segments.vim.plugin.syntastic + :members: + +Command-T segments +------------------ + +.. automodule:: powerline.segments.vim.plugin.commandt + :members: + +Tagbar segments +--------------- + +.. automodule:: powerline.segments.vim.plugin.tagbar + :members: + +NERDTree segments +----------------- + +.. automodule:: powerline.segments.vim.plugin.nerdtree + :members: + +Capslock segments +----------------- + +.. automodule:: powerline.segments.vim.plugin.capslock + :members: diff --git a/docs/source/configuration/selectors.rst b/docs/source/configuration/selectors.rst new file mode 100644 index 0000000..f9367b2 --- /dev/null +++ b/docs/source/configuration/selectors.rst @@ -0,0 +1,17 @@ +.. _config-selectors: + +****************** +Selector functions +****************** + +Selector functions are functions that return ``True`` or ``False`` depending on +application state. They are used for :ref:`exclude_function and include_function +segment options <config-themes-seg-exclude_function>`. + +Available selectors +=================== + +.. toctree:: + :glob: + + selectors/* diff --git a/docs/source/configuration/selectors/vim.rst b/docs/source/configuration/selectors/vim.rst new file mode 100644 index 0000000..5097320 --- /dev/null +++ b/docs/source/configuration/selectors/vim.rst @@ -0,0 +1,6 @@ +************* +Vim selectors +************* + +.. automodule:: powerline.selectors.vim + :members: diff --git a/docs/source/develop.rst b/docs/source/develop.rst new file mode 100644 index 0000000..bf45498 --- /dev/null +++ b/docs/source/develop.rst @@ -0,0 +1,13 @@ +*************** +Developer guide +*************** + +.. toctree:: + :maxdepth: 2 + :glob: + + develop/segments + develop/listers + develop/local-themes + develop/extensions + develop/tips-and-tricks diff --git a/docs/source/develop/extensions.rst b/docs/source/develop/extensions.rst new file mode 100644 index 0000000..2ddf223 --- /dev/null +++ b/docs/source/develop/extensions.rst @@ -0,0 +1,47 @@ +******************************** +Creating new powerline extension +******************************** + +Powerline extension is a code that tells powerline how to highlight and display +segments in some set of applications. Specifically this means + +#. Creating a :py:class:`powerline.Powerline` subclass that knows how to obtain + :ref:`local configuration overrides <local-configuration-overrides>`. It also + knows how to load local themes, but not when to apply them. + + Instance of this class is the only instance that interacts directly with + bindings code, so it has a proxy :py:meth:`powerline.Powerline.render` and + :py:meth:`powerline.Powerline.shutdown` methods and other methods which may + be useful for bindings. + + This subclass must be placed directly in :file:`powerline` directory (e.g. in + :file:`powerline/vim.py`) and named like ``VimPowerline`` (version of the + file name without directory and extension and first capital letter + + ``Powerline``). There is no technical reason for naming classes like this. +#. Creating a :py:class:`powerline.renderer.Renderer` subclass that knows how to + highlight a segment or reset highlighting to the default value (only makes + sense in prompts). It is also responsible for selecting local themes and + computing text width. + + This subclass must be placed directly in :file:`powerline/renderers` + directory (for powerline extensions developed for a set of applications use + :file:`powerline/renderers/{ext}/*.py`) and named like ``ExtRenderer`` or + ``AppPromptRenderer``. For technical reasons the class itself must be + referenced in ``renderer`` module attribute thus allowing only one renderer + per one module. +#. Creating an extension bindings. These are to be placed in + :file:`powerline/bindings/{ext}` and may contain virtually anything which may + be required for powerline to work inside given applications, assuming it does + not fit in other places. + +Powerline class +=============== + +.. autoclass:: powerline.Powerline + :members: + +Renderer class +============== + +.. autoclass:: powerline.renderer.Renderer + :members: diff --git a/docs/source/develop/listers.rst b/docs/source/develop/listers.rst new file mode 100644 index 0000000..e779704 --- /dev/null +++ b/docs/source/develop/listers.rst @@ -0,0 +1,49 @@ +.. _dev-listers: + +*************** +Writing listers +*************** + +Listers provide a way to show some segments multiple times: once per each entity +(buffer, tabpage, etc) lister knows. They are functions which receive the +following arguments: + +``pl`` + A :py:class:`powerline.PowerlineLogger` class instance. It must be used for + logging. + +``segment_info`` + Base segment info dictionary. Lister function or class must have + ``powerline_requires_segment_info`` to receive this argument. + + .. warning:: + Listers are close to useless if they do not have access to this + argument. + + Refer to :ref:`segment_info detailed description <dev-segments-info>` for + further details. + +``draw_inner_divider`` + If False (default) soft dividers between segments in the listed group will + not be drawn regardless of actual segment settings. If True they will be + drawn, again regardless of actual segment settings. Set it to ``None`` in + order to respect segment settings. + +And also any other argument(s) specified by user in :ref:`args key +<config-themes-seg-args>` (no additional arguments by default). + +Listers must return a sequence of pairs. First item in the pair must contain +a ``segment_info`` dictionary specific to one of the listed entities. + +Second item must contain another dictionary: it will be used to modify the +resulting segment. In addition to :ref:`usual keys that describe segment +<dev-segments-segment>` the following keys may be present (it is advised that +*only* the following keys will be used): + +``priority_multiplier`` + Value (usually a ``float``) used to multiply segment priority. It is useful + for finer-grained controlling which segments disappear first: e.g. when + listing tab pages make first disappear directory names of the tabpages which + are most far away from current tabpage, then (when all directory names + disappeared) buffer names. Check out existing listers implementation in + :file:`powerline/listers/vim.py`. diff --git a/docs/source/develop/local-themes.rst b/docs/source/develop/local-themes.rst new file mode 100644 index 0000000..959e1c4 --- /dev/null +++ b/docs/source/develop/local-themes.rst @@ -0,0 +1,59 @@ +************ +Local themes +************ + +From the user point of view local themes are the regular themes with a specific +scope where they are applied (i.e. specific vim window or specific kind of +prompt). Used themes are defined in :ref:`local_themes key +<config-ext-local_themes>`. + +Vim local themes +================ + +Vim is the only available extension that has a wide variaty of options for local +themes. It is the only extension where local theme key refers to a function as +described in :ref:`local_themes value documentation <config-ext-local_themes>`. + +This function always takes a single value named ``matcher_info`` which is the +same dictionary as :ref:`segment_info dictionary <dev-segment_info-vim>`. Unlike +segments it takes this single argument as a *positional* argument, not as +a keyword one. + +Matcher function should return a boolean value: ``True`` if theme applies for +the given ``matcher_info`` dictionary or ``False`` if it is not. When one of the +matcher functions returns ``True`` powerline takes the corresponding theme at +uses it for the given window. Matchers are not tested in any particular order. + +In addition to :ref:`local_themes configuration key <config-ext-local_themes>` +developer of some plugin which wishes to support powerline without including his +code in powerline tree may use +:py:meth:`powerline.vim.VimPowerline.add_local_theme` method. It accepts two +arguments: matcher name (same as in :ref:`local_themes +<config-ext-local_themes>`) and dictionary with theme. This dictionary is merged +with :ref:`top theme <config-ext-top_theme>` and +:file:`powerline/themes/vim/__main__.json`. Note that if user already specified +the matcher in his configuration file ``KeyError`` is raised. + +Other local themes +================== + +Except for Vim only IPython and shells have local themes. Unlike Vim these +themes are names with no special meaning (they do not refer to or cause loading +of any Python functions): + ++---------+------------+-------------------------------------------------------+ +|Extension|Theme name |Description | ++---------+------------+-------------------------------------------------------+ +|Shell |continuation|Shown for unfinished command (unclosed quote, | +| | |unfinished cycle). | +| +------------+-------------------------------------------------------+ +| |select |Shown for ``select`` command available in some shells. | ++---------+------------+-------------------------------------------------------+ +|IPython |in2 |Continuation prompt: shown for unfinished (multiline) | +| | |expression, unfinished class or function definition. | +| +------------+-------------------------------------------------------+ +| |out |Displayed before the result. | +| +------------+-------------------------------------------------------+ +| |rewrite |Displayed before the actually executed code when | +| | |``autorewrite`` IPython feature is enabled. | ++---------+------------+-------------------------------------------------------+ diff --git a/docs/source/develop/segments.rst b/docs/source/develop/segments.rst new file mode 100644 index 0000000..543ddd5 --- /dev/null +++ b/docs/source/develop/segments.rst @@ -0,0 +1,547 @@ +.. _dev-segments: + +**************** +Writing segments +**************** + +Each powerline segment is a callable object. It is supposed to be either +a Python function or :py:class:`powerline.segments.Segment` class. As a callable +object it should receive the following arguments: + +.. note:: All received arguments are keyword arguments. + +``pl`` + A :py:class:`powerline.PowerlineLogger` instance. It must be used every time + something needs to be logged. + +``segment_info`` + A dictionary. It is only received if callable has + ``powerline_requires_segment_info`` attribute. + + Refer to :ref:`segment_info detailed description <dev-segments-info>` for + further details. + +``create_watcher`` + Function that will create filesystem watcher once called. Which watcher will + be created exactly is controlled by :ref:`watcher configuration option + <config-common-watcher>`. + +And also any other argument(s) specified by user in :ref:`args key +<config-themes-seg-args>` (no additional arguments by default). + +.. note:: + For powerline-lint to work properly the following things may be needed: + + #. If segment is a :py:class:`powerline.segments.Segment` instance and used + arguments are scattered over multiple methods + :py:meth:`powerline.segments.Segment.argspecobjs` should be overridden in + subclass to tell powerline-lint which objects should be inspected for + arguments. + #. If segment takes some arguments that are never listed, but accessed via + ``kwargs.get()`` or previous function cannot be used for whatever reason + :py:meth:`powerline.segments.Segment.additional_args` should be + overridden in subclass. + #. If user is expected to use one :ref:`name <config-themes-seg-name>` for + multiple segments which cannot be linked to the segment function + automatically by powerline-lint (e.g. because there are no instances of + the segments in question in the default configuration) + :py:func:`powerline.lint.checks.register_common_name` function should be + used. + +Object representing segment may have the following attributes used by +powerline: + +``powerline_requires_segment_info`` + This attribute controls whether segment will receive ``segment_info`` + argument: if it is present argument will be received. + +``powerline_requires_filesystem_watcher`` + This attribute controls whether segment will receive ``create_watcher`` + argument: if it is present argument will be received. + +``powerline_segment_datas`` + This attribute must be a dictionary containing ``top_theme: segment_data`` + mapping where ``top_theme`` is any theme name (it is expected that all of + the names from :ref:`top-level themes list <config-top_themes-list>` are + present) and ``segment_data`` is a dictionary like the one that is contained + inside :ref:`segment_data dictionary in configuration + <config-themes-segment_data>`. This attribute should be used to specify + default theme-specific values for *third-party* segments: powerline + theme-specific values go directly to :ref:`top-level themes + <config-themes>`. + +.. _dev-segments-startup: + +``startup`` + This attribute must be a callable which accepts the following keyword + arguments: + + * ``pl``: :py:class:`powerline.PowerlineLogger` instance which is to be used + for logging. + * ``shutdown_event``: :py:class:`Event` object which will be set when + powerline will be shut down. + * Any arguments found in user configuration for the given segment (i.e. + :ref:`args key <config-themes-seg-args>`). + + This function is called at powerline startup when using long-running + processes (e.g. powerline in vim, in zsh with libzpython, in ipython or in + powerline daemon) and not called when ``powerline-render`` executable is + used (more specific: when :py:class:`powerline.Powerline` constructor + received true ``run_once`` argument). + +.. _dev-segments-shutdown: + +``shutdown`` + This attribute must be a callable that accepts no arguments and shuts down + threads and frees any other resources allocated in ``startup`` method of the + segment in question. + + This function is not called when ``startup`` method is not called. + +.. _dev-segments-expand: + +``expand`` + This attribute must be a callable that accepts the following keyword + arguments: + + * ``pl``: :py:class:`powerline.PowerlineLogger` instance which is to be used + for logging. + * ``amount``: integer number representing amount of display cells result + must occupy. + + .. warning:: + “Amount of display cells” is *not* number of Unicode codepoints, string + length, or byte count. It is suggested that this function should look + something like ``return (' ' * amount) + segment['contents']`` where + ``' '`` may be replaced with anything that is known to occupy exactly + one display cell. + * ``segment``: :ref:`segment dictionary <dev-segments-segment>`. + * Any arguments found in user configuration for the given segment (i.e. + :ref:`args key <config-themes-seg-args>`). + + It must return new value of :ref:`contents <dev-segments-seg-contents>` key. + +.. _dev-segments-truncate: + +``truncate`` + Like :ref:`expand function <dev-segments-expand>`, but for truncating + segments. Here ``amount`` means the number of display cells which must be + freed. + + This function is called for all segments before powerline starts purging + them to free space. + +This callable object should may return either a string (``unicode`` in Python2 +or ``str`` in Python3, *not* ``str`` in Python2 or ``bytes`` in Python3) object +or a list of dictionaries. String object is a short form of the following return +value: + +.. code-block:: python + + [{ + 'contents': original_return, + 'highlight_groups': [segment_name], + }] + +.. _dev-segments-return: + +Returned list is a list of segments treated independently, except for +:ref:`draw_inner_divider key <dev-segments-draw_inner_divider>`. + +All keys in segments returned by the function override those obtained from +:ref:`configuration <config-themes-segments>` and have the same meaning. + +Detailed description of used dictionary keys: + +.. _dev-segments-contents: + +``contents`` + Text displayed by segment. Should be a ``unicode`` (Python2) or ``str`` + (Python3) instance. + +``literal_contents`` + Text that needs to be output literally (i.e. without passing through + :py:meth:`powerline.renderer.strwidth` to determine length, through + :py:meth:`powerline.renderer.escape` to escape special characters and + through :py:meth:`powerline.renderer.hl` to highlight it). Should be a tuple + ``(contents_length, contents)`` where ``contents_length`` is an integer and + ``contents`` is a ``unicode`` (Python2) or ``str`` (Python3) instance. + + If this key is present and its second value is true then other contents keys + (:ref:`contents <dev-segments-contents>`, :ref:`after + <config-themes-seg-after>`, :ref:`before <config-themes-seg-before>`) will + be ignored. + + .. note:: + If target is inclusion of the segment in powerline upstream all segment + functions that output *only* subsegments with ``literal_contents`` key + must contain the following string in documentation:: + + No highlight groups are used (literal segment). + + String must be present on the separate line. + +.. _dev-segments-draw_inner_divider: + +``draw_hard_divider``, ``draw_soft_divider``, ``draw_inner_divider`` + Determines whether given divider should be drawn. All have the same meaning + as :ref:`the similar keys in configuration <config-themes-seg-draw_divider>` + (:ref:`draw_inner_divider <config-themes-seg-draw_inner_divider>`). + +.. _dev-segments-highlight_groups: + +``highlight_groups`` + Determines segment highlighting. Refer to :ref:`themes documentation + <config-themes-seg-highlight_groups>` for more details. + + Defaults to the name of the segment. + + .. note:: + If target is inclusion of the segment in powerline upstream all used + highlighting groups must be specified in the segment documentation in the + form:: + + Highlight groups used: ``g1``[ or ``g2``]*[, ``g3`` (gradient)[ or ``g4``]*]*. + + I.e. use:: + + Highlight groups used: ``foo_gradient`` (gradient) or ``foo``, ``bar``. + + to specify that the segment uses *either* ``foo_gradient`` group or + ``foo`` group *and* ``bar`` group meaning that ``powerline-lint`` will + check that at least one of the first two groups is defined (and if + ``foo_gradient`` is defined it must use at least one gradient color) and + third group is defined as well. + + All groups must be specified on one line. + +``divider_highlight_group`` + Determines segment divider highlight group. Only applicable for soft + dividers: colors for hard dividers are determined by colors of adjacent + segments. + + .. note:: + If target is inclusion of the segment in powerline upstream used divider + highlight group must be specified in the segment documentation in the + form:: + + Divider highlight group used: ``group``. + + This text must not wrap and all divider highlight group names are + supposed to end with ``:divider``: e.g. ``cwd:divider``. + +``gradient_level`` + First and the only key that may not be specified in user configuration. It + determines which color should be used for this segment when one of the + highlighting groups specified by :ref:`highlight_groups + <dev-segments-highlight_groups>` was defined to use the color gradient. + + This key may have any value from 0 to 100 inclusive, value is supposed to be + an ``int`` or ``float`` instance. + + No error occurs if segment has this key, but no used highlight groups use + gradient color. + +``_*`` + Keys starting with underscore are reserved for powerline and must not be + returned. + +``__*`` + Keys starting with two underscores are reserved for the segment functions, + specifically for :ref:`expand function <dev-segments-expand>`. + +.. _dev-segments-segment: + +Segment dictionary +================== + +Segment dictionary contains the following keys: + +* All keys returned by segment function (if it was used). + +* All of the following keys: + + ``name`` + Segment name: value of the :ref:`name key <config-themes-seg-name>` or + function name (last component of the :ref:`function key + <config-themes-seg-function>`). May be ``None``. + + ``type`` + :ref:`Segment type <config-themes-seg-type>`. Always represents actual type + and is never ``None``. + + ``highlight_groups``, ``divider_highlight_group`` + Used highlight groups. May be ``None``. + + ``highlight_group_prefix`` + If this key is present then given prefix will be prepended to each highlight + group (both regular and divider) used by this segment in a form + ``{prefix}:{group}`` (note the colon). This key is mostly useful for + :ref:`segment listers <dev-listers>`. + + .. _dev-segments-seg-around: + + ``before``, ``after`` + Value of :ref:`before <config-themes-seg-before>` or :ref:`after + <config-themes-seg-after>` configuration options. May be ``None`` as well as + an empty string. + + ``contents_func`` + Function used to get segment contents. May be ``None``. + + .. _dev-segments-seg-contents: + + ``contents`` + Actual segment contents, excluding dividers and :ref:`before/after + <dev-segments-seg-around>`. May be ``None``. + + ``priority`` + :ref:`Segment priority <config-themes-seg-priority>`. May be ``None`` for no + priority (such segments are always shown). + + ``draw_soft_divider``, ``draw_hard_divider``, ``draw_inner_divider`` + :ref:`Divider control flags <dev-segments-draw_inner_divider>`. + + ``side`` + Segment side: ``right`` or ``left``. + + ``display_condition`` + Contains function that takes three position parameters: + :py:class:`powerline.PowerlineLogger` instance, :ref:`segment_info + <dev-segments-info>` dictionary and current mode and returns either ``True`` + or ``False`` to indicate whether particular segment should be processed. + + This key is constructed based on :ref:`exclude_/include_modes keys + <config-themes-seg-exclude_modes>` and :ref:`exclude_/include_function keys + <config-themes-seg-exclude_function>`. + + ``width``, ``align`` + :ref:`Width and align options <config-themes-seg-align>`. May be ``None``. + + ``expand``, ``truncate`` + Partially applied :ref:`expand <dev-segments-expand>` or :ref:`truncate + <dev-segments-truncate>` function. Accepts ``pl``, ``amount`` and + ``segment`` positional parameters, keyword parameters from :ref:`args + <config-themes-seg-args>` key were applied. + + ``startup`` + Partially applied :ref:`startup function <dev-segments-startup>`. Accepts + ``pl`` and ``shutdown_event`` positional parameters, keyword parameters from + :ref:`args <config-themes-seg-args>` key were applied. + + ``shutdown`` + :ref:`Shutdown function <dev-segments-shutdown>`. Accepts no argument. + +Segments layout +=============== + +Powerline segments are all located in one of the ``powerline.segments`` +submodules. For extension-specific segments ``powerline.segments.{ext}`` module +should be used (e.g. ``powerline.segments.shell``), for extension-agnostic there +is ``powerline.segments.common``. + +Plugin-specific segments (currently only those that are specific to vim plugins) +should live in ``powerline.segments.{ext}.plugin.{plugin_name}``: e.g. +``powerline.segments.vim.plugin.gundo``. + +.. _dev-segments-info: + +Segment information used in various extensions +============================================== + +Each ``segment_info`` value should be a dictionary with at least the following +keys: + +``environ`` + Current environment, may be an alias to ``os.environ``. Is guaranteed to + have ``__getitem__`` and ``get`` methods and nothing more. + + .. warning:: + ``os.environ`` must not ever be used: + + * If segment is run in the daemon this way it will get daemon’s + environment which is not correct. + * If segment is run in Vim or in zsh with libzpython ``os.environ`` will + contain Vim or zsh environ *at the moment Python interpreter was + loaded*. + +``getcwd`` + Function that returns current working directory being called with no + arguments. ``os.getcwd`` must not be used for the same reasons the use of + ``os.environ`` is forbidden, except that current working directory is valid + in Vim and zsh (but not in daemon). + +``home`` + Current home directory. May be false. + +.. _dev-segment_info-vim: + +Vim +--- + +Vim ``segment_info`` argument is a dictionary with the following keys: + +``window`` + ``vim.Window`` object. ``vim.current.window`` or ``vim.windows[number - 1]`` + may be used to obtain such object. May be a false object, in which case any + of this object’s properties must not be used. + +``winnr`` + Window number. Same as ``segment_info['window'].number`` *assuming* Vim is + new enough for ``vim.Window`` object to have ``number`` attribute. + +``window_id`` + Internal powerline window id, unique for each newly created window. It is + safe to assume that this ID is hashable and supports equality comparison, + but no other assumptions about it should be used. Currently uses integer + numbers incremented each time window is created. + +``buffer`` + ``vim.Buffer`` object. One may be obtained using ``vim.current.buffer``, + ``segment_info['window'].buffer`` or ``vim.buffers[some_number]``. Note that + in the latter case depending on vim version ``some_number`` may be ``bufnr`` + or the internal Vim buffer index which is *not* buffer number. For this + reason to get ``vim.Buffer`` object other then stored in ``segment_info`` + dictionary iteration over ``vim.buffers`` and checking their ``number`` + attributes should be performed. + +``bufnr`` + Buffer number. + +``tabpage`` + ``vim.Tabpage`` object. One may be obtained using ``vim.current.tabpage`` or + ``vim.tabpages[number - 1]``. May be a false object, in which case no + object’s properties can be used. + +``tabnr`` + Tabpage number. + +``mode`` + Current mode. + +``encoding`` + Value of ``&encoding`` from the time when powerline was initialized. It + should be used to convert return values. + +.. note:: + Segment generally should not assume that it is run for the current window, + current buffer or current tabpage. “Current window” and “current buffer” + restrictions may be ignored if ``window_cached`` decorator is used, “current + tabpage” restriction may be safely ignored if segment is not supposed to be + used in tabline. + +.. warning:: + Powerline is being tested with vim-7.0.112 (some minor sanity check) and + latest Vim. This means that most of the functionality like + ``vim.Window.number``, ``vim.*.vars``, ``vim.*.options`` or even ``dir(vim + object)`` should be avoided in segments that want to be included in the + upstream. + +Shell +----- + +``args`` + Parsed shell arguments: a ``argparse.Namespace`` object. Check out + ``powerline-render --help`` for the list of all available arguments. + Currently it is expected to contain at least the following attributes: + + ``last_exit_code`` + Exit code returned by last shell command. Is either one integer, + ``sig{name}`` or ``sig{name}+core`` (latter two are only seen in ``rc`` + shell). + + ``last_pipe_status`` + List of exit codes returned by last programs in the pipe or some false + object. Only available in ``zsh`` and ``rc``. Is a list of either + integers, ``sig{name}`` or ``sig{name}+core`` (latter two are only seen + in ``rc`` shell). + + ``jobnum`` + Number of background jobs. + + ``renderer_arg`` + Dictionary containing some keys that are additional arguments used by + shell bindings. *This attribute must not be used directly*: all + arguments from this dictionary are merged with ``segment_info`` + dictionary. Known to have at least the following keys: + + ``client_id`` + Identifier unique to one shell instance. Is used to record instance + state by powerline daemon. In tmux this is the same as :ref:`pane_id + <dev-seginfo-shell-renarg-pane_id>`. + + It is not guaranteed that existing client ID will not be retaken + when old shell with this ID quit: usually process PID is used as + a client ID. + + It is also not guaranteed that client ID will be process PID, number + or something else at all. It is guaranteed though that client ID + will be some hashable object which supports equality comparison. + + ``local_theme`` + Local theme that will be used by shell. One should not rely on the + existence of this key. + + .. _dev-seginfo-shell-renarg-pane_id: + + ``pane_id`` + Identifier unique to each tmux pane. Is always an integer, optional. + Obtained by using ``tmux display -p '#D'``, then all leading spaces + and per cent signs are stripped and the result is converted into an + integer. + + Other keys, if any, are specific to segments. + +Ipython +------- + +``ipython`` + Some object which has ``prompt_count`` attribute. Currently it is guaranteed + to have only this attribute. + + Attribute ``prompt_count`` contains the so-called “history count” + (equivalent to ``\N`` in ``in_template``). + +Pdb +--- + +``pdb`` + Currently active :py:class:`pdb.Pdb` instance. + +``curframe`` + Frame which will be run next. Note: due to the existence of + :py:func:`powerline.listers.pdb.frame_lister` one must not use + ``segment_info['pdb'].curframe``. + +``initial_stack_length`` + Equal to the length of :py:attr:`pdb.Pdb.stack` at the first invocation of + the prompt decremented by one. + +i3wm +---- + +``mode`` + Currently active i3 mode (as a string). + +``output`` + ``xrandr`` output name currently drawing to. Currently only available + in lemonbar bindings. + +``workspace`` + dictionary containing the workspace name under the key ``"name"`` and + boolean values for the ``"visible"``, ``"urgent"`` and ``"focused"`` + keys, indicating the state of the workspace. Currently only provided by + the :py:func:`powerline.listers.i3wm.workspace_lister` lister. + +Segment class +============= + +.. autoclass:: powerline.segments.Segment + :members: + +PowerlineLogger class +===================== + +.. autoclass:: powerline.PowerlineLogger + :members: + :undoc-members: diff --git a/docs/source/develop/tips-and-tricks.rst b/docs/source/develop/tips-and-tricks.rst new file mode 100644 index 0000000..c850659 --- /dev/null +++ b/docs/source/develop/tips-and-tricks.rst @@ -0,0 +1,21 @@ +**************************************** +Tips and tricks for powerline developers +**************************************** + +Profiling powerline in Vim +========================== + +Given that current directory is the root of the powerline repository the +following command may be used: + +.. code-block:: sh + + vim --cmd 'let g:powerline_pyeval="powerline#debug#profile_pyeval"' \ + --cmd 'set rtp=powerline/bindings/vim' \ + -c 'runtime! plugin/powerline.vim' \ + {other arguments if needed} + +After some time run ``:WriteProfiling {filename}`` Vim command. Currently this +only works with recent Vim and python-2*. It should be easy to modify +:file:`powerline/bindings/vim/autoload/powerline/debug.vim` to suit other +needs. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..9a4d84b --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,28 @@ +********* +Powerline +********* + +.. toctree:: + :maxdepth: 3 + :glob: + + overview + installation + usage + configuration + develop + troubleshooting + tips-and-tricks + license-and-credits + +.. toctree:: + :maxdepth: 2 + + commands + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 0000000..eb40ed0 --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,132 @@ +************ +Installation +************ + +Generic requirements +==================== + +* Python 2.6 or later, 3.2 or later, PyPy 2.0 or later, PyPy3 2.3 or later. It + is the only non-optional requirement. + + .. warning: + It is highly advised to use UCS-4 version of Python because UCS-2 version + uses significantly slower text processing (length determination and + non-printable character replacement) functions due to the need of + supporting unicode characters above U+FFFF which are represented as + surrogate pairs. This price will be paid even if configuration has no such + characters. + +* C compiler. Required to build powerline client on linux. If it is not present + then powerline will fall back to shell script or python client. +* ``socat`` program. Required for shell variant of client which runs a bit + faster than python version of the client, but still slower than C version. +* ``psutil`` python package. Required for some segments like cpu_percent. Some + segments have linux-only fallbacks for ``psutil`` functionality. +* ``hglib`` python package *and* mercurial executable. Required to work with + mercurial repositories. +* ``pygit2`` python package or ``git`` executable. Required to work with ``git`` + repositories. +* ``bzr`` python package (note: *not* standalone executable). Required to work + with bazaar repositories. +* ``pyuv`` python package. Required for :ref:`libuv-based watcher + <config-common-watcher>` to work. +* ``i3ipc`` python package. Required for i3wm bindings and segments. +* ``xrandr`` program. Required for the multi-monitor lemonbar binding and the + :py:func:`powerline.listers.i3wm.output_lister`. + +.. note:: + Until bazaar supports Python-3 or PyPy powerline will not support + repository information when running in these interpreters. + +.. _repository-root: + +.. note:: + When using ``pip``, the ``{repository_root}`` directory referenced in + documentation may be found using ``pip show powerline-status``. In the output + of ``pip show`` there is a line like ``Location: {path}``, that ``{path}`` is + ``{repository_root}``. Unless it is ``--editable`` installation this is only + applicable for ``{repository_root}/powerline/…`` paths: something like + ``{repository_root}/scripts/powerline-render`` is not present. + + When using other packages referenced paths may not exist, in this case refer + to package documentation. + +Pip installation +================ + +Due to a naming conflict with an unrelated project powerline is available on +PyPI under the ``powerline-status`` name: + +.. code-block:: sh + + pip install powerline-status + +is the preferred method because this will get the latest release. To get current +development version + +.. code-block:: sh + + pip install --user git+git://github.com/powerline/powerline + +may be used. If powerline was already checked out into some directory + +.. code-block:: sh + + pip install --user --editable={path_to_powerline} + +is useful, but note that in this case ``pip`` will not install ``powerline`` +executable and something like + +.. code-block:: sh + + ln -s {path_to_powerline}/scripts/powerline ~/.local/bin + +will have to be done (:file:`~/.local/bin` should be replaced with some path +present in ``$PATH``). + +.. note:: + If ISP blocks git protocol for some reason github also provides ``ssh`` + (``git+ssh://git@github.com/powerline/powerline``) and ``https`` + (``git+https://github.com/powerline/powerline``) protocols. ``git`` protocol + should be the fastest, but least secure one though. + +Fonts installation +================== + +Powerline uses several special glyphs to get the arrow effect and some custom +symbols for developers. This requires having either a symbol font or a patched +font installed in the system. The used application (e.g. terminal emulator) must +also either be configured to use patched fonts (in some cases even support it +because custom glyphs live in private use area which some applications reserve +for themselves) or support fontconfig for powerline to work properly with +powerline-specific glyphs. + +:ref:`24-bit color support <config-common-term_truecolor>` may be enabled if +used terminal emulator supports it (see :ref:`the terminal emulator support +matrix <usage-terminal-emulators>`). + +There are basically two ways to get powerline glyphs displayed: use +:file:`PowerlineSymbols.otf` font as a fallback for one of the existing fonts or +install a patched font. + +.. _installation-patched-fonts: + +Patched fonts +------------- + +This method is the fallback method and works for every terminal. + +Download the font from `powerline-fonts`_. If preferred font can’t be found in +the `powerline-fonts`_ repo, then patching the preferred font is needed instead. + +.. _powerline-fonts: https://github.com/powerline/fonts + +After downloading this font refer to platform-specific instructions. + +Installation on various platforms +================================= + +.. toctree:: + + Linux <installation/linux> + OS X <installation/osx> diff --git a/docs/source/installation/linux.rst b/docs/source/installation/linux.rst new file mode 100644 index 0000000..15ec606 --- /dev/null +++ b/docs/source/installation/linux.rst @@ -0,0 +1,110 @@ +********************* +Installation on Linux +********************* + +The following distribution-specific packages are officially supported, and they +provide an easy way of installing and upgrading Powerline. The packages will +automatically do most of the configuration. + +* `Arch Linux (AUR), Python 2 version <https://aur.archlinux.org/packages/python2-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 +* Powerline package is available for Debian starting from Wheezy (via `backports + <https://packages.debian.org/wheezy-backports/powerline>`_). Use `search + <https://packages.debian.org/search?keywords=powerline&searchon=names&suite=all§ion=all>`_ + to get more information. + +If used distribution does not have an official package installation guide below +should be followed: + +1. Install Python 3.2+, Python 2.6+ or PyPy and ``pip`` with ``setuptools``. + This step is distribution-specific, so no commands provided. +2. Install Powerline using one of the following commands: + + .. code-block:: sh + + pip install --user powerline-status + + will get the latest release version and + + .. code-block:: sh + + pip install --user git+git://github.com/powerline/powerline + + will get the latest development version. + + .. note:: Due to the naming conflict with an unrelated project powerline is + named ``powerline-status`` in PyPI. + + .. note:: + Powerline developers should be aware that``pip install --editable`` does + not currently fully work. Installation performed this way are missing + ``powerline`` executable that needs to be symlinked. It will be located in + ``scripts/powerline``. + +Fonts installation +================== + +Fontconfig +---------- + +This method only works on Linux. It’s the second recommended method if terminal +emulator supports it as patching fonts is not needed, and it generally works +with any coding font. + +#. Download the latest version of the symbol font and fontconfig file:: + + wget https://github.com/powerline/powerline/raw/develop/font/PowerlineSymbols.otf + wget https://github.com/powerline/powerline/raw/develop/font/10-powerline-symbols.conf + +#. Move the symbol font to a valid X font path. Valid font paths can be + listed with ``xset q``:: + + mv PowerlineSymbols.otf ~/.local/share/fonts/ + +#. Update font cache for the path the font was moved to (root priveleges may be + needed to update cache for the system-wide paths):: + + fc-cache -vf ~/.local/share/fonts/ + +#. Install the fontconfig file. For newer versions of fontconfig the config + path is ``~/.config/fontconfig/conf.d/``, for older versions it’s + ``~/.fonts.conf.d/``:: + + mv 10-powerline-symbols.conf ~/.config/fontconfig/conf.d/ + +If custom symbols still cannot be seen then try closing all instances of the +terminal emulator. Restarting X may be needed for the changes to take effect. + +If custom symbols *still* can’t be seen, double-check that the font have been +installed to a valid X font path, and that the fontconfig file was installed to +a valid fontconfig path. Alternatively try to install a :ref:`patched font +<installation-patched-fonts>`. + +Patched font installation +------------------------- + +This is the preferred method, but it is not always available because not all +fonts were patched and not all fonts *can* be patched due to licensing issues. + +After downloading font the following should be done: + +#. Move the patched font to a valid X font path. Valid font paths can be + listed with ``xset q``:: + + mv 'SomeFont for Powerline.otf' ~/.local/share/fonts/ + +#. Update font cache for the path the font was moved to (root privileges may be + needed for updating font cache for some paths):: + + fc-cache -vf ~/.local/share/fonts/ + +After installing patched font terminal emulator, GVim or whatever application +powerline should work with must be configured to use the patched font. The +correct font usually ends with *for Powerline*. + +If custom symbols cannot be seen then try closing all instances of the terminal +emulator. X server may need to be restarted for the changes to take effect. + +If custom symbols *still* can’t be seen then double-check that the font have +been installed to a valid X font path. diff --git a/docs/source/installation/osx.rst b/docs/source/installation/osx.rst new file mode 100644 index 0000000..e520348 --- /dev/null +++ b/docs/source/installation/osx.rst @@ -0,0 +1,67 @@ +******************** +Installation on OS X +******************** + +Python package +============== + +1. Install a proper Python version (see `issue #39 + <https://github.com/powerline/powerline/issues/39>`_ for a discussion + regarding the required Python version on OS X):: + + sudo port select python python27-apple + + Homebrew may be used here:: + + brew install python + + .. note:: + In case :file:`powerline.sh` as a client ``socat`` and ``coreutils`` need + to be installed. ``coreutils`` may be installed using ``brew install + coreutils``. + +2. Install Powerline using one of the following commands: + + .. code-block:: sh + + pip install --user powerline-status + + will get current release version and + + .. code-block:: sh + + pip install --user git+git://github.com/powerline/powerline + + will get latest development version. + + .. warning:: + When using ``brew install`` to install Python one must not supply + ``--user`` flag to ``pip``. + + .. note:: + Due to the naming conflict with an unrelated project powerline is named + ``powerline-status`` in PyPI. + + .. note:: + Powerline developers should be aware that ``pip install --editable`` does + not currently fully work. Installation performed this way are missing + ``powerline`` executable that needs to be symlinked. It will be located in + ``scripts/powerline``. + +Vim installation +================ + +Any terminal vim version with Python 3.2+ or Python 2.6+ support should work, +but MacVim users need to install it using the following command:: + + brew install macvim --env-std --with-override-system-vim + +Fonts installation +================== + +To install patched font double-click the font file in Finder, then click +:guilabel:`Install this font` in the preview window. + +After installing the patched font MacVim or terminal emulator (whatever +application powerline should work with) need to be configured to use the patched +font. The correct font usually ends with *for Powerline*. diff --git a/docs/source/license-and-credits.rst b/docs/source/license-and-credits.rst new file mode 100644 index 0000000..5226669 --- /dev/null +++ b/docs/source/license-and-credits.rst @@ -0,0 +1,31 @@ +******************* +License and credits +******************* + +Powerline is licensed under the `MIT license +<https://raw.github.com/powerline/powerline/develop/LICENSE>`_. + +.. + This document is parsed by powerline_automan.py module. Do not forget to + check that file before altering this one. Specifically it expects + ``Authors`` and ``Contributors`` sections underlined by ``---``, a list of + authors in format ``* `{name} <`` in the “Authors” section and fonts + contributor name in format ``The glyphs in the font patcher are created by + {name},`` in the “Contributors” section. + +Authors +------- + +* `Kim Silkebækken <https://github.com/Lokaltog>`_ +* `Nikolay Pavlov <https://github.com/ZyX-I>`_ +* `Kovid Goyal <https://github.com/kovidgoyal>`_ + +Contributors +------------ + +* `List of contributors + <https://github.com/powerline/powerline/contributors>`_ +* The glyphs in the font patcher are created by Fabrizio Schiavi, creator of + the excellent coding font `Pragmata Pro`_. + +.. _`Pragmata Pro`: http://www.fsd.it/fonts/pragmatapro.htm diff --git a/docs/source/overview.rst b/docs/source/overview.rst new file mode 100644 index 0000000..e943b04 --- /dev/null +++ b/docs/source/overview.rst @@ -0,0 +1,67 @@ +******** +Overview +******** + +**Powerline is a statusline plugin for vim, and provides statuslines and +prompts for several other applications, including zsh, bash, tmux, IPython, +Awesome, i3 and Qtile.** + +Features +-------- + +* **Extensible and feature rich, written in Python.** Powerline was + completely rewritten in Python to get rid of as much vimscript as + possible. This has allowed much better extensibility, leaner and better + config files, and a structured, object-oriented codebase with no mandatory + third-party dependencies other than a Python interpreter. +* **Stable and testable code base.** Using Python has allowed unit testing + of all the project code. The code is tested to work in Python 2.6+ and + Python 3. +* **Support for prompts and statuslines in many applications.** Originally + created exclusively for vim statuslines, the project has evolved to + 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 + other applications that Powerline doesn’t yet support. +* **Configuration and colorschemes written in JSON.** JSON is + a standardized, simple and easy to use file format that allows for easy + user configuration across all of Powerline’s supported applications. +* **Fast and lightweight, with daemon support for even better performance.** + 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 + 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 + launched for prompts and statuslines, which provides excellent + performance. + +*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 / …* + +You should check out some of the Powerline derivatives. The most lightweight +and feature-rich alternative is currently Bailey Ling’s `vim-airline +<https://github.com/bling/vim-airline>`_ project. + +Screenshots +----------- + +Vim statusline +^^^^^^^^^^^^^^ + +**Mode-dependent highlighting** + +* .. image:: _static/img/pl-mode-normal.png + :alt: Normal mode +* .. image:: _static/img/pl-mode-insert.png + :alt: Insert mode +* .. image:: _static/img/pl-mode-visual.png + :alt: Visual mode +* .. image:: _static/img/pl-mode-replace.png + :alt: Replace mode + +**Automatic truncation of segments in small windows** + +* .. image:: _static/img/pl-truncate1.png + :alt: Truncation illustration +* .. image:: _static/img/pl-truncate2.png + :alt: Truncation illustration +* .. image:: _static/img/pl-truncate3.png + :alt: Truncation illustration diff --git a/docs/source/powerline_autodoc.py b/docs/source/powerline_autodoc.py new file mode 100644 index 0000000..eba42ed --- /dev/null +++ b/docs/source/powerline_autodoc.py @@ -0,0 +1,64 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from inspect import formatargspec + +from sphinx.ext import autodoc + +from powerline.lint.inspect import getconfigargspec +from powerline.segments import Segment +from powerline.lib.unicode import unicode + + +def formatvalue(val): + if type(val) is str: + return '="' + unicode(val, 'utf-8').replace('"', '\\"').replace('\\', '\\\\') + '"' + else: + return '=' + repr(val) + + +class ThreadedDocumenter(autodoc.FunctionDocumenter): + '''Specialized documenter subclass for ThreadedSegment subclasses.''' + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + return (isinstance(member, Segment) or + super(ThreadedDocumenter, cls).can_document_member(member, membername, isattr, parent)) + + def format_args(self): + argspec = getconfigargspec(self.object) + return formatargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\') + + +class Repr(object): + def __init__(self, repr_contents): + self.repr_contents = repr_contents + + def __repr__(self): + return '<{0}>'.format(self.repr_contents) + + +class EnvironDocumenter(autodoc.AttributeDocumenter): + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + if type(member) is dict and member.get('environ') is os.environ: + return True + else: + return False + + def import_object(self, *args, **kwargs): + ret = super(EnvironDocumenter, self).import_object(*args, **kwargs) + if not ret: + return ret + self.object = self.object.copy() + if 'home' in self.object: + self.object.update(home=Repr('home directory')) + self.object.update(environ=Repr('environ dictionary')) + return True + + +def setup(app): + autodoc.setup(app) + app.add_autodocumenter(ThreadedDocumenter) + app.add_autodocumenter(EnvironDocumenter) diff --git a/docs/source/powerline_automan.py b/docs/source/powerline_automan.py new file mode 100644 index 0000000..85d241c --- /dev/null +++ b/docs/source/powerline_automan.py @@ -0,0 +1,408 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re +import codecs + +from collections import namedtuple +from argparse import REMAINDER + +from functools import reduce + +from docutils.parsers.rst import Directive +from docutils.parsers.rst.directives import unchanged_required +from docutils import nodes + +from powerline.lib.unicode import u + + +AUTHOR_LINE_START = '* `' +GLYPHS_AUTHOR_LINE_START = '* The glyphs in the font patcher are created by ' + + +def get_authors(): + credits_file = os.path.join(os.path.dirname(__file__), 'license-and-credits.rst') + authors = [] + glyphs_author = None + with codecs.open(credits_file, encoding='utf-8') as CF: + section = None + prev_line = None + for line in CF: + line = line[:-1] + if line and not line.replace('-', ''): + section = prev_line + elif section == 'Authors': + if line.startswith(AUTHOR_LINE_START): + authors.append(line[len(AUTHOR_LINE_START):line.index('<')].strip()) + elif section == 'Contributors': + if line.startswith(GLYPHS_AUTHOR_LINE_START): + assert(not glyphs_author) + glyphs_author = line[len(GLYPHS_AUTHOR_LINE_START):line.index(',')].strip() + prev_line = line + return { + 'authors': ', '.join(authors), + 'glyphs_author': glyphs_author, + } + + +class AutoManSubparsers(object): + def __init__(self): + self.parsers = [] + + def add_parser(self, command, *args, **kwargs): + self.parsers.append((command, AutoManParser(*args, **kwargs))) + return self.parsers[-1][1] + + +Argument = namedtuple('Argument', ('names', 'help', 'choices', 'metavar', 'required', 'nargs', 'is_option', 'is_long_option', 'is_short_option', 'multi', 'can_be_joined')) + + +def parse_argument(*args, **kwargs): + is_option = args[0].startswith('-') + is_long_option = args[0].startswith('--') + is_short_option = is_option and not is_long_option + action = kwargs.get('action', 'store') + multi = kwargs.get('action') in ('append',) or kwargs.get('nargs') is REMAINDER + nargs = kwargs.get('nargs', (1 if action in ('append', 'store') else 0)) + return Argument( + names=args, + help=u(kwargs.get('help', '')), + choices=[str(choice) for choice in kwargs.get('choices', [])], + metavar=kwargs.get('metavar') or args[-1].lstrip('-').replace('-', '_').upper(), + required=kwargs.get('required', False) if is_option else ( + kwargs.get('nargs') not in ('?',)), + nargs=nargs, + multi=multi, + is_option=is_option, + is_long_option=is_long_option, + is_short_option=is_short_option, + can_be_joined=(is_short_option and not multi and not nargs) + ) + + +class AutoManGroup(object): + is_short_option = False + is_option = False + is_long_option = False + can_be_joined = False + + def __init__(self): + self.arguments = [] + self.required = False + + def add_argument(self, *args, **kwargs): + self.arguments.append(parse_argument(*args, **kwargs)) + + def add_argument_group(self, *args, **kwargs): + self.arguments.append(AutoManGroup()) + return self.arguments[-1] + + +class SurroundWith(): + def __init__(self, ret, condition, start='[', end=']'): + self.ret = ret + self.condition = condition + self.start = start + self.end = end + + def __enter__(self, *args): + if self.condition: + self.ret.append(nodes.Text(self.start)) + + def __exit__(self, *args): + if self.condition: + self.ret.append(nodes.Text(self.end)) + + +def insert_separators(ret, sep): + for i in range(len(ret) - 1, 0, -1): + ret.insert(i, nodes.Text(sep)) + return ret + + +def format_usage_arguments(arguments, base_length=None): + line = [] + prev_argument = None + arg_indexes = [0] + arguments = arguments[:] + while arguments: + argument = arguments.pop(0) + if isinstance(argument, nodes.Text): + line += [argument] + continue + can_join_arguments = ( + argument.is_short_option + and prev_argument + and prev_argument.can_be_joined + and prev_argument.required == argument.required + ) + if ( + prev_argument + and not prev_argument.required + and prev_argument.can_be_joined + and not can_join_arguments + ): + line.append(nodes.Text(']')) + arg_indexes.append(len(line)) + if isinstance(argument, AutoManGroup): + arguments = ( + [nodes.Text(' (')] + + insert_separators(argument.arguments[:], nodes.Text(' |')) + + [nodes.Text(' )')] + + arguments + ) + else: + if not can_join_arguments: + line.append(nodes.Text(' ')) + with SurroundWith(line, not argument.required and not argument.can_be_joined): + if argument.can_be_joined and not can_join_arguments and not argument.required: + line.append(nodes.Text('[')) + if argument.is_option: + line.append(nodes.strong()) + name = argument.names[0] + if can_join_arguments: + name = name[1:] + # `--` is automatically transformed into – (EN DASH) + # when parsing into HTML. We do not need this. + line[-1] += [nodes.Text(char) for char in name] + elif argument.nargs is REMAINDER: + line.append(nodes.Text('[')) + line.append(nodes.strong()) + line[-1] += [nodes.Text(char) for char in '--'] + line.append(nodes.Text('] ')) + if argument.nargs: + assert(argument.nargs in (1, '?', REMAINDER)) + with SurroundWith( + line, ( + True + if argument.nargs is REMAINDER + else (argument.nargs == '?' and argument.is_option) + ) + ): + if argument.is_long_option: + line.append(nodes.Text('=')) + line.append(nodes.emphasis(text=argument.metavar)) + elif not argument.is_option: + line.append(nodes.strong(text=argument.metavar)) + if argument.multi: + line.append(nodes.Text('…')) + prev_argument = argument + if ( + prev_argument + and prev_argument.can_be_joined + and not prev_argument.required + ): + line.append(nodes.Text(']')) + arg_indexes.append(len(line)) + ret = [] + if base_length is None: + ret = line + else: + length = base_length + prev_arg_idx = arg_indexes.pop(0) + while arg_indexes: + next_arg_idx = arg_indexes.pop(0) + arg_length = sum((len(element.astext()) for element in line[prev_arg_idx:next_arg_idx])) + if length + arg_length > 68: + ret.append(nodes.Text('\n' + (' ' * base_length))) + length = base_length + ret += line[prev_arg_idx:next_arg_idx] + length += arg_length + prev_arg_idx = next_arg_idx + return ret + + +LITERAL_RE = re.compile(r"`(.*?)'") + + +def parse_argparse_text(text): + rst_text = LITERAL_RE.subn(r'``\1``', text)[0] + ret = [] + for i, text in enumerate(rst_text.split('``')): + if i % 2 == 0: + ret.append(nodes.Text(text)) + else: + ret.append(nodes.literal(text=text)) + return ret + + +def flatten_groups(arguments): + for argument in arguments: + if isinstance(argument, AutoManGroup): + for group_argument in flatten_groups(argument.arguments): + yield group_argument + else: + yield argument + + +def format_arguments(arguments): + return [nodes.definition_list( + '', *[ + nodes.definition_list_item( + '', + nodes.term( + # node.Text('') is required because otherwise for some + # reason first name node is seen in HTML output as + # `<strong>abc</strong>`. + '', *([nodes.Text('')] + ( + insert_separators([ + nodes.strong('', '', *[nodes.Text(ch) for ch in name]) + for name in argument.names + ], ', ') + if argument.is_option else + # Unless node.Text('') is here metavar is written in + # bold in the man page. + [nodes.Text(''), nodes.emphasis(text=argument.metavar)] + ) + ( + [] if not argument.is_option or not argument.nargs else + [nodes.Text(' '), nodes.emphasis('', argument.metavar)] + )) + ), + nodes.definition('', nodes.paragraph('', *parse_argparse_text(argument.help or ''))), + ) + for argument in flatten_groups(arguments) + ] + [ + nodes.definition_list_item( + '', + nodes.term( + '', nodes.Text(''), + nodes.strong(text='-h'), + nodes.Text(', '), + nodes.strong('', '', nodes.Text('-'), nodes.Text('-help')), + ), + nodes.definition('', nodes.paragraph('', nodes.Text('Display help and exit.'))) + ) + ] + )] + + +def format_subcommand_usage(arguments, subcommands, progname, base_length): + return reduce((lambda a, b: a + reduce((lambda c, d: c + d), b, [])), [ + [ + [progname] + + format_usage_arguments(arguments) + + [nodes.Text(' '), nodes.strong(text=subcmd)] + + format_usage_arguments(subparser.arguments) + + [nodes.Text('\n')] + for subcmd, subparser in subparsers.parsers + ] + for subparsers in subcommands + ], []) + + +def format_subcommands(subcommands): + return reduce((lambda a, b: a + reduce((lambda c, d: c + d), b, [])), [ + [ + [ + nodes.section( + '', + nodes.title(text='Arguments specific to ' + subcmd + ' subcommand'), + *format_arguments(subparser.arguments), + ids=['subcmd-' + subcmd] + ) + ] + for subcmd, subparser in subparsers.parsers + ] + for subparsers in subcommands + ], []) + + +class AutoManParser(object): + def __init__(self, description=None, help=None): + self.description = description + self.help = help + self.arguments = [] + self.subcommands = [] + + def add_argument(self, *args, **kwargs): + self.arguments.append(parse_argument(*args, **kwargs)) + + def add_subparsers(self): + self.subcommands.append(AutoManSubparsers()) + return self.subcommands[-1] + + def add_mutually_exclusive_group(self): + self.arguments.append(AutoManGroup()) + return self.arguments[-1] + + def automan_usage(self, prog): + block = nodes.literal_block() + progname = nodes.strong() + progname += [nodes.Text(prog)] + base_length = len(prog) + if self.subcommands: + block += format_subcommand_usage(self.arguments, self.subcommands, progname, base_length) + else: + block += [progname] + block += format_usage_arguments(self.arguments, base_length) + return [block] + + def automan_description(self): + ret = [] + if self.help: + ret += parse_argparse_text(self.help) + ret += format_arguments(self.arguments) + format_subcommands(self.subcommands) + return ret + + +class AutoMan(Directive): + required_arguments = 1 + optional_arguments = 0 + option_spec = dict(prog=unchanged_required, minimal=bool) + has_content = False + + def run(self): + minimal = self.options.get('minimal') + module = self.arguments[0] + template_args = {} + template_args.update(get_authors()) + get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser + parser = get_argparser(AutoManParser) + if minimal: + container = nodes.container() + container += parser.automan_usage(self.options['prog']) + container += parser.automan_description() + return [container] + synopsis_section = nodes.section( + '', + nodes.title(text='Synopsis'), + ids=['synopsis-section'], + ) + synopsis_section += parser.automan_usage(self.options['prog']) + description_section = nodes.section( + '', nodes.title(text='Description'), + ids=['description-section'], + ) + description_section += parser.automan_description() + author_section = nodes.section( + '', nodes.title(text='Author'), + nodes.paragraph( + '', + nodes.Text('Written by {authors} and contributors. The glyphs in the font patcher are created by {glyphs_author}.'.format( + **get_authors() + )) + ), + ids=['author-section'] + ) + issues_url = 'https://github.com/powerline/powerline/issues' + reporting_bugs_section = nodes.section( + '', nodes.title(text='Reporting bugs'), + nodes.paragraph( + '', + nodes.Text('Report {prog} bugs to '.format( + prog=self.options['prog'])), + nodes.reference( + issues_url, issues_url, + refuri=issues_url, + internal=False, + ), + nodes.Text('.'), + ), + ids=['reporting-bugs-section'] + ) + return [synopsis_section, description_section, author_section, reporting_bugs_section] + + +def setup(app): + app.add_directive('automan', AutoMan) diff --git a/docs/source/tips-and-tricks.rst b/docs/source/tips-and-tricks.rst new file mode 100644 index 0000000..541b593 --- /dev/null +++ b/docs/source/tips-and-tricks.rst @@ -0,0 +1,94 @@ +*************** +Tips and tricks +*************** + +Vim +=== + +Useful settings +--------------- + +You may find the following vim settings useful when using the Powerline +statusline: + +.. code-block:: vim + + set laststatus=2 " Always display the statusline in all windows + set showtabline=2 " Always display the tabline, even if there is only one tab + set noshowmode " Hide the default mode text (e.g. -- INSERT -- below the statusline) + +.. _tips-and-tricks-urxvt: + +Rxvt-unicode +============ + +Terminus font and urxvt +----------------------- + +The Terminus fonts does not have the powerline glyphs and unless someone submits +a patch to the font author, it is unlikely to happen. However, Andre Klärner +came up with this work around: In your ``~/.Xdefault`` file add the following:: + + urxvt*font: xft:Terminus:pixelsize=12,xft:Inconsolata\ for\ Powerline:pixelsize=12 + +This will allow urxvt to fallback onto the Inconsolata fonts in case it does not +find the right glyphs within the terminus font. + +Source Code Pro font and urxvt +------------------------------ + +Much like the terminus font that was mentioned above, a similar fix can be +applied to the Source Code Pro fonts. + +In the ``~/.Xdefaults`` add the following:: + + URxvt*font: xft:Source\ Code\ Pro\ Medium:pixelsize=13:antialias=true:hinting=true,xft:Source\ Code\ Pro\ Medium:pixelsize=13:antialias=true:hinting=true + +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 +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 +problem. Font size 13 seems to work just fine. + +Reloading powerline after update +================================ + +Once you have updated powerline you generally have the following options: + +#. Restart the application you are using it in. This is the safest one. Will not + work if the application uses ``powerline-daemon``. +#. For shell and tmux bindings (except for zsh with libzpython): do not do + anything if you do not use ``powerline-daemon``, run ``powerline-daemon + --replace`` if you do. +#. Use powerline reloading feature. + + .. warning:: + This feature is an unsafe one. It is not guaranteed to work always, it may + render your Python constantly error out in place of displaying powerline + and sometimes may render your application useless, forcing you to + restart. + + *Do not report any bugs occurred when using this feature unless you know + both what caused it and how this can be fixed.* + + * When using zsh with libzpython use + + .. code-block:: bash + + powerline-reload + + .. note:: This shell function is only defined when using libzpython. + + * When using IPython use + + :: + + %powerline reload + + * When using Vim use + + .. code-block:: Vim + + py powerline.reload() + " or (depending on Python version you are using) + py3 powerline.reload() diff --git a/docs/source/troubleshooting.rst b/docs/source/troubleshooting.rst new file mode 100644 index 0000000..3599132 --- /dev/null +++ b/docs/source/troubleshooting.rst @@ -0,0 +1,329 @@ +*************** +Troubleshooting +*************** + +System-specific issues +====================== + +.. toctree:: + + Linux <troubleshooting/linux> + OS X <troubleshooting/osx> + +Common issues +============= + +After an update something stopped working +----------------------------------------- + +Assuming powerline was working before update and stopped only after there are +two possible explanations: + +* You have more then one powerline installation (e.g. ``pip`` and ``Vundle`` + installations) and you have updated only one. +* Update brought some bug to powerline. + +In the second case you, of course, should report the bug to `powerline bug +tracker <https://github.com/powerline/powerline>`_. In the first you should +make sure you either have only one powerline installation or you update all of +them simultaneously (beware that in the second case you are not supported). To +diagnose this problem you may do the following: + +#) If this problem is observed within the shell make sure that + + .. code-block:: sh + + python -c 'import powerline; print (powerline.__file__)' + + which should report something like + :file:`/usr/lib64/python2.7/site-packages/powerline/__init__.pyc` (if + powerline is installed system-wide) or + :file:`/home/USER/.../powerline/__init__.pyc` (if powerline was cloned + somewhere, e.g. in :file:`/home/USER/.vim/bundle/powerline`) reports the same + location you use to source in your shell configuration: in first case it + should be some location in :file:`/usr` (e.g. + :file:`/usr/share/zsh/site-contrib/powerline.zsh`), in the second it should + be something like + :file:`/home/USER/.../powerline/bindings/zsh/powerline.zsh`. If this is true + it may be a powerline bug, but if locations do not match you should not + report the bug until you observe it on configuration where locations do + match. +#) If this problem is observed specifically within bash make sure that you clean + ``$POWERLINE_COMMAND`` and ``$PROMPT_COMMAND`` environment variables on + startup or, at least, that it was cleaned after update. While different + ``$POWERLINE_COMMAND`` variable should not cause any troubles most of time + (and when it will cause troubles are rather trivial) spoiled + ``$PROMPT_COMMAND`` may lead to strange error messages or absense of exit + code reporting. + + These are the sources which may keep outdated environment variables: + + * Any command launched from any application inherits its environment unless + callee explicitly requests to use specific environment. So if you did + ``exec bash`` after update it is rather unlikely to fix the problem. + * More interesting: `tmux` is a client-server application, it keeps one + server instance per one user. You probably already knew that, but there is + an interesting consequence: once `tmux` server was started it inherits its + environment from the callee and keeps it *forever* (i.e. until server is + killed). This environment is then inherited by applications you start with + ``tmux new-session``. Easiest solution is to kill tmux with ``tmux + kill-server``, but you may also use ``tmux set-environment -u`` to unset + offending variables. + * Also check `When using z powerline shows wrong number of jobs`_: though + this problem should not be seen after update only, it contains another + example of ``$PROMPT_COMMAND`` spoiling results. + +#) If this problem is observed within the vim instance you should check out the + output of the following Ex mode commands + + .. code-block:: vim + + python import powerline as pl ; print (pl.__file__) + python3 import powerline as pl ; print (pl.__file__) + + One (but not both) of them will most likely error out, this is OK. The same + rules apply as in the 1), but in place of sourcing you should seek for the + place where you modify `runtimepath` vim option. If you install powerline + using `VAM <https://github.com/MarcWeber/vim-addon-manager>`_ then no + explicit modifications of runtimpath were performed in your vimrc + (runtimepath is modified by VAM in this case), but powerline will be placed + in :file:`{plugin_root_dir}/powerline` where `{plugin_root_dir}` is stored in + VAM settings dictionary: do `echo g:vim_addon_manager.plugin_root_dir`. + +There is a hint if you want to place powerline repository somewhere, but still +make powerline package importable anywhere: use + + .. code-block:: sh + + pip install --user --editable path/to/powerline + +Tmux/screen-related issues +========================== + +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 + :file:`.tmux.conf` to solve this issue:: + + set -g default-terminal "screen-256color" +* If you’re using iTerm2, make sure that you have enabled the setting + :guilabel:`Set locale variables automatically` in :menuselection:`Profiles --> + Terminal --> Environment`. +* Make sure tmux knows that terminal it is running in support 256 colors. You + may tell it tmux by using ``-2`` option when launching it. + +I’m using tmux/screen and Powerline is colorless +------------------------------------------------ + +* If the above advices do not help, then you need to disable + :ref:`term_truecolor <config-common-term_truecolor>`. +* Alternative: set :ref:`additional_escapes <config-common-additional_escapes>` + to ``"tmux"`` or ``"screen"``. Note that it is known to work perfectly in + screen, but in tmux it may produce ugly spaces. + + .. warning:: + Both tmux and screen are not resending sequences escaped in such a way. Thus + even though additional escaping will work for the last shown prompt, + highlighting will eventually go away when tmux or screen will redraw screen + for some reason. + + E.g. in screen it will go away when you used copy mode and prompt got out of + screen and in tmux it will go away immediately after you press ``<Enter>``. + +In tmux there is a green bar in place of powerline +-------------------------------------------------- + +In order for tmux bindings to work ``powerline-config`` script is required to be +present in ``$PATH``. Alternatively one may define ``$POWERLINE_CONFIG_COMMAND`` +environment variable pointing to the location of the script. *This variable must +be defined prior to launching tmux server and in the environment where server is +started from.* + +Shell issues +============ + +Pipe status segment displays only last value in bash +---------------------------------------------------- + +Make sure that powerline command that sets prompt appears the very first in +``$PROMPT_COMMAND``. To do this ``powerline.sh`` needs to be sourced the very +last, after all other users of ``$PROMPT_COMMAND``. + +Bash prompt stopped updating +---------------------------- + +Make sure that powerline commands appear in ``$PROMPT_COMMAND``: some users of +``$PROMPT_COMMAND`` have a habit of overwriting the value instead of +prepending/appending to it. All powerline commands start with ``_powerline`` or +``powerline``, e.g. ``_powerline_set_prompt``. + +Bash prompt does not show last exit code +---------------------------------------- + +There are two possibilities here: + +* You are using ``default`` theme in place of ``default_leftonly``. Unlike + ``default_leftonly`` ``default`` theme was designed for shells with right + prompt support (e.g. zsh, tcsh, fish) and status in question is supposed to be + shown on the right side which bash cannot display. + +* There is some other user of ``$PROMPT_COMMAND`` which prepended to this + variable, but did not bother keeping the exit code. For the best experience + powerline must appear first in ``$PROMPT_COMMAND`` which may be achieved by + sourcing powerline bindings the last. + + .. note:: + Resourcing bash bindings will not resolve the problem unless you clear + powerline commands from ``$PROMPT_COMMAND`` first. + +When sourcing shell bindings it complains about missing command or file +----------------------------------------------------------------------- + +If you are using ``pip`` based installation do not forget to add pip-specific +executable path to ``$PATH`` environment variable. This path usually looks +something like ``$HOME/.local/bin`` (linux) or +``$HOME/Library/Python/{python_version}/bin`` (OS X). One may check out where +``powerline-config`` script was installed by using ``pip show -f +powerline-status | grep powerline-config`` (does not always work). + +I am suffering bad lags before displaying shell prompt +------------------------------------------------------ + +To get rid of these lags there currently are two options: + +* Run ``powerline-daemon``. Powerline does not automatically start it for you. +* Compile and install ``libzpython`` module that lives in + https://bitbucket.org/ZyX_I/zpython. This variant is zsh-specific. + +Prompt is spoiled after completing files in ksh +----------------------------------------------- + +This is exactly why powerline has official mksh support, but not official ksh +support. If you know the solution feel free to share it in `powerline bug +tracker`_. + +When using z powerline shows wrong number of jobs +------------------------------------------------- + +This happens because `z <https://github.com/rupa/z>`_ is launching some jobs in +the background from ``$POWERLINE_COMMAND`` and these jobs fail to finish before +powerline prompt is run. + +Solution to this problem is simple: be sure that :file:`z.sh` is sourced +strictly after :file:`powerline/bindings/bash/powerline.sh`. This way background +jobs are spawned by `z <https://github.com/rupa/z>`_ after powerline has done +its job. + +When using shell I do not see powerline fancy characters +-------------------------------------------------------- + +If your locale encoding is not unicode (any encoding that starts with “utf” or +“ucs” will work, case is ignored) powerline falls back to ascii-only theme. You +should set up your system to use unicode locale or forget about powerline fancy +characters. + +Urxvt unicode3 and frills +------------------------- + +Make sure that, whatever urxvt package you're installing, both the `unicode3` +and `frills` features are enabled at compile time. Run +``urxvt --help 2>&1 | grep options:`` to get a list of enabled options. +This should contain at least `frills`, `unicode3` and optionally `iso14755` +if you want to input Unicode characters as well. + +Compiler flags example: + + --enable-frills \ + --enable-unicode3 + +As long as your terminal emulator is compiled without unicode rendering, +no amount of configuration will make it display unicode characters. +They're being considered 'unnecessary features', but they add negligible +overhead to the size of the installed package (~100KB). + +Vim issues +========== + +My vim statusline has strange characters like ``^B`` in it! +----------------------------------------------------------- + +* Please add ``set encoding=utf-8`` to your :file:`vimrc`. + +My vim statusline has a lot of ``^`` or underline characters in it! +------------------------------------------------------------------- + +* You need to configure the ``fillchars`` setting to disable statusline + fillchars (see ``:h 'fillchars'`` for details). Add this to your :file:`vimrc` + to solve this issue: + + .. code-block:: vim + + set fillchars+=stl:\ ,stlnc:\ + +My vim statusline is hidden/only appears in split windows! +---------------------------------------------------------- + +* Make sure that you have ``set laststatus=2`` in your :file:`vimrc`. + +My vim statusline is not displayed completely and has too much spaces +--------------------------------------------------------------------- + +* Be sure you have ``ambiwidth`` option set to ``single``. +* Alternative: set :ref:`ambiwidth <config-common-ambiwidth>` to 2, remove fancy + dividers (they suck when ``ambiwidth`` is set to double). + +Powerline loses color after editing vimrc +----------------------------------------- + +If your vimrc has something like + +.. code-block:: vim + + autocmd! BufWritePost ~/.vimrc :source ~/.vimrc + +used to automatically source vimrc after saving it then you must add ``nested`` +after pattern (``vimrc`` in this case): + +.. code-block:: vim + + autocmd! BufWritePost ~/.vimrc nested :source ~/.vimrc + +. Alternatively move ``:colorscheme`` command out of the vimrc to the file which +will not be automatically resourced. + +Observed problem is that when you use ``:colorscheme`` command existing +highlighting groups are usually cleared, including those defined by powerline. +To workaround this issue powerline hooks ``Colorscheme`` event, but when you +source vimrc with ``BufWritePost`` (or any other) event, but without ``nested`` +this event is not launched. See also `autocmd-nested +<http://vimcommunity.bitbucket.org/doc/autocmd.txt.html#autocmd-nested>`_ Vim +documentation. + +Powerline loses color after saving any file +------------------------------------------- + +It may be one of the incarnations of the above issue: specifically minibufexpl +is known to trigger it. If you are using minibufexplorer you should set + +.. code-block:: vim + + let g:miniBufExplForceSyntaxEnable = 1 + +variable so that this issue is not triggered. Complete explanation: + +#. When MBE autocommand is executed it launches ``:syntax enable`` Vim command… +#. … which makes Vim source :file:`syntax/syntax.vim` file … +#. … which in turn sources :file:`syntax/synload.vim` … +#. … which executes ``:colorscheme`` command. Normally this command triggers + ``Colorscheme`` event, but in the first point minibufexplorer did set up + autocommands that miss ``nested`` attribute meaning that no events will be + triggered when processing MBE events. + +.. note:: + This setting was introduced in version 6.3.1 of `minibufexpl + <http://www.vim.org/scripts/script.php?script_id=159>`_ and removed in + version 6.5.0 of its successor `minibufexplorer + <http://www.vim.org/scripts/script.php?script_id=3239>`_. It is highly + advised to use the latter because `minibufexpl`_ was last updated late in + 2004. diff --git a/docs/source/troubleshooting/linux.rst b/docs/source/troubleshooting/linux.rst new file mode 100644 index 0000000..e0493c6 --- /dev/null +++ b/docs/source/troubleshooting/linux.rst @@ -0,0 +1,78 @@ +************************ +Troubleshooting on Linux +************************ + +I can’t see any fancy symbols, what’s wrong? +-------------------------------------------- + +* Make sure that you’ve configured gvim or your terminal emulator to use + a patched font. +* 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 + documentation for information about setting these variables correctly. +* 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 + ``--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*…``. + + In any case the only absolute requirement is launching xterm with UTF-8 + locale. +* If you are using bitmap font make sure that + :file:`/etc/fonts/conf.d/70-no-bitmaps.conf` does not exist. If it does check + out your distribution documentation to find a proper way to remove it (so that + it won’t reappear after update). E.g. in Gentoo this is:: + + eselect fontconfig disable 70-no-bitmaps.conf + + (currently this only removes the symlink from :file:`/etc/fonts/conf.d`). Also + check out that no other fontconfig file does not have ``rejectfont`` tag that + tells fontconfig to disable bitmap fonts (they are referenced as not + scalable). + +The fancy symbols look a bit blurry or “off”! +--------------------------------------------- + +* Make sure that you have patched all variants of your font (i.e. both the + regular and the bold font files). + +I am seeing strange blocks in place of playing/paused/stopped signs +------------------------------------------------------------------- + +If you are using ``powerline_unicode7`` :ref:`top-level theme +<config-common-default_top_theme>` then symbols for player segments are taken +from U+23F4–U+23FA range which is missing from most fonts. You may fix the issue +by using `Symbola <http://users.teilar.gr/~g1951d/>`_ font (or any other font +which contains these glyphs). + +If your terminal emulator is using fontconfig library then you can create +a fontconfig configuration file with the following contents: + +.. code-block:: xml + + <?xml version="1.0"?> + <!DOCTYPE fontconfig SYSTEM "fonts.dtd"> + + <fontconfig> + <alias> + <family>Terminus</family> + <prefer><family>Symbola</family></prefer> + </alias> + </fontconfig> + +(replace ``Terminus`` with the name of the font you are using). Exact sequence +of actions you need to perform is different across distributions, most likely it +will work if you put the above xml into +:file:`/etc/fonts/conf.d/99-prefer-symbola.conf`. On Gentoo you need to put it +into :file:`/etc/fonts/conf.d/99-prefer-symbola.conf` and run:: + + eselect fontconfig enable 99-prefer-symbola + +. + +.. warning:: + This answer is only applicable if you use ``powerline_unicode7`` theme or if + you configured powerline to use the same characters yourself. diff --git a/docs/source/troubleshooting/osx.rst b/docs/source/troubleshooting/osx.rst new file mode 100644 index 0000000..b61063e --- /dev/null +++ b/docs/source/troubleshooting/osx.rst @@ -0,0 +1,71 @@ +*********************** +Troubleshooting on OS X +*********************** + +I can’t see any fancy symbols, what’s wrong? +-------------------------------------------- + +* If you’re using iTerm2, please update to `this revision + <https://github.com/gnachman/iTerm2/commit/8e3ad6dabf83c60b8cf4a3e3327c596401744af6>`_ + or newer. Also make sure that Preferences>Profiles>Text>Non-ASCII Font is the same as + your main Font. +* You need to set your ``LANG`` and ``LC_*`` environment variables to + a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distro’s + documentation for information about setting these variables correctly. + +The colors look weird in the default OS X Terminal app! +------------------------------------------------------- + +* The arrows may have the wrong colors if you have changed the “minimum + 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 + Powerline colors. Please use another terminal emulator. iTerm2 should work + fine. + +The colors look weird in iTerm2! +-------------------------------- + +* The arrows may have the wrong colors if you have changed the “minimum + contrast” slider in the color tab of your OS X settings. +* If you're using transparency, check “Keep background colors opaque”. + +Statusline is getting wrapped to the next line in iTerm2 +-------------------------------------------------------- + +* Turn off “Treat ambigious-width characters as double width” in `Preferences + --> Text`. +* Alternative: remove fancy dividers (they suck in this case), set + :ref:`ambiwidth <config-common-ambiwidth>` to 2. + +I receive a ``NameError`` when trying to use Powerline with MacVim! +------------------------------------------------------------------- + +* Please install MacVim using this command:: + + brew install macvim --env-std --override-system-vim + + Then install Powerline locally with ``pip install --user``, or by + running these commands in the ``powerline`` directory:: + + ./setup.py build + ./setup.py install --user + +I receive an ``ImportError`` when trying to use Powerline on OS X! +------------------------------------------------------------------ + +* This is caused by an invalid ``sys.path`` when using system vim and system + Python. Please try to select another Python distribution:: + + sudo port select python python27-apple + +* See `issue #39 <https://github.com/powerline/powerline/issues/39>`_ for + a discussion and other possible solutions for this issue. + +I receive “FSEventStreamStart: register_with_server: ERROR” with status_colors +------------------------------------------------------------------------------ + +This is `a known <https://github.com/joyent/node/issues/5463>`_ libuv issue that +happens if one is trying to watch too many files. It should be fixed in +libuv-0.12. Until then it is suggested to either disable ``status_colors`` (from +:py:func:`powerline.segments.common.vcs.branch`) or choose stat-based watcher +(will have effectively the same effect as disabling ``status_colors``). diff --git a/docs/source/usage.rst b/docs/source/usage.rst new file mode 100644 index 0000000..5bfd304 --- /dev/null +++ b/docs/source/usage.rst @@ -0,0 +1,88 @@ +***** +Usage +***** + +Application-specific requirements +--------------------------------- + +Vim plugin requirements +^^^^^^^^^^^^^^^^^^^^^^^ + +The vim plugin requires a vim version with Python support compiled in. Presence +of Python support in Vim can be checked by running ``vim --version | grep ++python``. + +If Python support is absent then Vim needs to be compiled with it. To do this +use ``--enable-pythoninterp`` :file:`./configure` flag (Python 3 uses +``--enable-python3interp`` flag instead). Note that this also requires the +related Python headers to be installed. Please consult distribution’s +documentation for details on how to compile and install packages. + +Vim version 7.4 or newer is recommended for performance reasons, but Powerline +supports Vim 7.0.112 and higher. + +Shell prompts requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Due to fish having incorrect code for prompt width calculations up to version +2.1 and no way to tell that certain sequence of characters has no width +(``%{…%}`` in zsh and ``\[…\]`` in bash prompts serve exactly this purpose) +users that have fish versions below 2.1 are not supported.. + + +WM widgets requirements +^^^^^^^^^^^^^^^^^^^^^^^ + +Awesome is supported starting from version 3.5.1, inclusive. QTile is supported +from version 0.6, inclusive. + +.. _usage-terminal-emulators: + +Terminal emulator requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Powerline uses several special glyphs to get the arrow effect and some custom +symbols for developers. This requires either a symbol font or a patched font +installed. Used terminal emulator must also support either patched fonts or +fontconfig for Powerline to work properly. + +:ref:`24-bit color support <config-common-term_truecolor>` can also be enabled +if terminal emulator supports it. + +.. table:: Application/terminal emulator feature support matrix + :name: term-feature-support-matrix + + ===================== ======= ===================== ===================== ===================== + Name OS Patched font support Fontconfig support 24-bit color support + ===================== ======= ===================== ===================== ===================== + Gvim Linux |i_yes| |i_no| |i_yes| + iTerm2 OS X |i_yes| |i_no| |i_no| + Konsole Linux |i_yes| |i_yes| |i_yes| + lxterminal Linux |i_yes| |i_yes| |i_no| + MacVim OS X |i_yes| |i_no| |i_yes| + rxvt-unicode Linux |i_partial| [#]_ |i_no| |i_no| + st Linux |i_yes| |i_yes| |i_yes| [#]_ + Terminal.app OS X |i_yes| |i_no| |i_no| + libvte-based [#]_ Linux |i_yes| |i_yes| |i_yes| [#]_ + xterm Linux |i_yes| |i_no| |i_partial| [#]_ + fbterm Linux |i_yes| |i_yes| |i_no| + ===================== ======= ===================== ===================== ===================== + +.. |i_yes| image:: _static/img/icons/tick.png +.. |i_no| image:: _static/img/icons/cross.png +.. |i_partial| image:: _static/img/icons/error.png + +.. [#] Must be compiled with ``--enable-unicode3`` for the patched font to work. +.. [#] Since version 0.5. +.. [#] Including XFCE terminal and GNOME terminal. +.. [#] Since version 0.36. +.. [#] Uses nearest color from 8-bit palette. + +Plugins +------- + +.. toctree:: + + usage/shell-prompts + usage/wm-widgets + usage/other diff --git a/docs/source/usage/other.rst b/docs/source/usage/other.rst new file mode 100644 index 0000000..d773484 --- /dev/null +++ b/docs/source/usage/other.rst @@ -0,0 +1,209 @@ +************* +Other plugins +************* + +.. _vim-vimrc: + +Vim statusline +============== + +If installed using pip just add + +.. code-block:: vim + + python from powerline.vim import setup as powerline_setup + python powerline_setup() + python del powerline_setup + +(replace ``python`` with ``python3`` if appropriate) to the :file:`vimrc`. + +.. note:: + Status line will not appear by default when there is only a single window + displayed. Run ``:h 'laststatus'`` in Vim for more information. + +If the repository was just cloned the following line needs to be added to the +:file:`vimrc`: + +.. code-block:: vim + + set rtp+={repository_root}/powerline/bindings/vim + +where ``{repository_root}`` is the absolute path to the Powerline installation +directory (see :ref:`repository root <repository-root>`). + +If pathogen is used and Powerline functionality is not needed outside of Vim +then it is possible to simply add Powerline as a bundle and point the path above +to the Powerline bundle directory, e.g. +:file:`~/.vim/bundle/powerline/powerline/bindings/vim`. + +Vundle and NeoBundle users may instead use + +.. code-block:: vim + + Bundle 'powerline/powerline', {'rtp': 'powerline/bindings/vim/'} + +(NeoBundle users need ``NeoBundle`` in place of ``Bundle``, otherwise setup is +the same). + +Vim-addon-manager setup is even easier because it is not needed to write this +big path or install anything by hand: ``powerline`` can be installed and +activated just like any other plugin using + +.. code-block:: vim + + call vam#ActivateAddons(['powerline']) + +.. warning:: + *Never* install powerline with pathogen/VAM/Vundle/NeoBundle *and* with pip. + If powerline functionality is needed in applications other then Vim then + system-wide installation (in case used OS distribution has powerline + package), pip-only or ``pip install --editable`` kind of installation + performed on the repository installed by Vim plugin manager should be used. + + No issues are accepted in powerline issue tracker for double pip/non-pip + installations, especially if these issues occur after update. + +.. note:: + If supplied :file:`powerline.vim` file is used to load powerline there are + additional configuration variables available: ``g:powerline_pycmd`` and + ``g:powerline_pyeval``. First sets command used to load powerline: expected + values are ``"py"`` and ``"py3"``. Second sets function used in statusline, + expected values are ``"pyeval"`` and ``"py3eval"``. + + If ``g:powerline_pycmd`` is set to the one of the expected values then + ``g:powerline_pyeval`` will be set accordingly. If it is set to some other + value then ``g:powerline_pyeval`` must also be set. Powerline will not check + that Vim is compiled with Python support if ``g:powerline_pycmd`` is set to + an unexpected value. + + These values are to be used to specify the only Python that is to be loaded + if both versions are present: Vim may disable loading one python version if + other was already loaded. They should also be used if two python versions + are able to load simultaneously, but powerline was installed only for + python-3 version. + +Tmux statusline +=============== + +Add the following lines to :file:`.tmux.conf`, where ``{repository_root}`` is +the absolute path to the Powerline installation directory (see :ref:`repository +root <repository-root>`):: + + source "{repository_root}/powerline/bindings/tmux/powerline.conf" + +.. note:: + The availability of the ``powerline-config`` command is required for + powerline support. The location of this script may be specified via + the ``$POWERLINE_CONFIG_COMMAND`` environment variable. + +.. note:: + It is advised to run ``powerline-daemon`` before adding the above line to + tmux.conf. To do so add:: + + run-shell "powerline-daemon -q" + + to :file:`.tmux.conf`. + +.. warning:: + Segments which depend on current working directory (e.g. + :py:func:`powerline.segments.common.vcs.branch`) require also setting up + :ref:`shell bindings <usage-shell>`. It is not required to use powerline + shell prompt, :ref:`components setting <config-ext-components>` allows to + set up only powerline bindings for tmux without altering your prompt. + Without setting up shell bindings powerline will use current working + directory of *tmux server* which is probably not what you need. + + Segments which depend on environment like + :py:func:`powerline.segments.common.env.virtualenv` will not work at all + (i.e. they will use environment of the tmux server), tracking environment + changes is going to slow down shell a lot. + + In any case it is suggested to avoid both kinds of segments in tmux + :ref:`themes <config-themes>` because even support for tracking current + directory is very limited: + + #. It works only in shell. Should you e.g. run Vim and run ``:cd`` there you + will get current working directory from shell. + #. It works only in local shell and requires configuring it. + #. Some shells are not supported at all. + +IPython prompt +============== + +For IPython<0.11 add the following lines to :file:`.ipython/ipy_user_conf.py`: + +.. code-block:: Python + + # top + from powerline.bindings.ipython.pre_0_11 import setup as powerline_setup + + # main() function (assuming ipython was launched without configuration to + # create skeleton ipy_user_conf.py file): + powerline_setup() + +For IPython>=0.11 add the following line to +:file:`~/.ipython/profile_default/ipython_config.py` file in the used profile: + +.. code-block:: Python + + c = get_config() + c.InteractiveShellApp.extensions = [ + 'powerline.bindings.ipython.post_0_11' + ] + +For IPython>=5.0 you may use the above set up, but it is deprecated. It is +suggested to use + +.. code-block:: Python + + from powerline.bindings.ipython.since_5 import PowerlinePrompts + c = get_config() + c.TerminalInteractiveShell.simple_prompt = False + c.TerminalInteractiveShell.prompts_class = PowerlinePrompts + +.. note:: + Setting ``simple_prompt`` to False after IPython-5.0 is required regardless + of whether you use ``c.InteractiveShellApp.extensions`` setting or + ``c.TerminalInteractiveShell.prompts_class``. But you probably already have + this line because IPython is not very useful without it. + +IPython=0.11* is not supported and does not work. IPython<0.10 was not +tested (not installable by pip). + +.. _pdb-prompt: + +PDB prompt +========== + +To use Powerline with PDB prompt you need to use custom class. Inherit your +class from :py:class:`pdb.Pdb` and decorate it with +:py:func:`powerline.bindings.pdb.use_powerline_prompt`: + +.. code-block:: Python + + import pdb + + from powerline.bindings.pdb import use_powerline_prompt + + @use_powerline_prompt + class MyPdb(pdb.Pdb): + pass + + MyPdb.run('some.code.to.debug()') + +. Alternatively you may use + +.. code-block:: bash + + python -mpowerline.bindings.pdb path/to/script.py + +just like you used ``python -m pdb``. + +.. note: + If you are using Python-2.6 you need to use ``python + -mpowerline.bindings.pdb.__main__``, not what is shown above. + +.. warning: + Using PyPy (not PyPy3) forces ASCII-only prompts. In other cases unicode + characters are allowed, even if you use `pdbpp + <https://pypi.python.org/pypi/pdbpp>`_. diff --git a/docs/source/usage/shell-prompts.rst b/docs/source/usage/shell-prompts.rst new file mode 100644 index 0000000..1ddb0a1 --- /dev/null +++ b/docs/source/usage/shell-prompts.rst @@ -0,0 +1,144 @@ +.. _usage-shell: + +************* +Shell prompts +************* + +.. note:: + Powerline daemon is not run automatically by any of my bindings. It is + advised to add + + .. code-block:: bash + + powerline-daemon -q + + before any other powerline-related code in the shell configuration file. + +Bash prompt +=========== + +Add the following line to the :file:`bashrc`, where ``{repository_root}`` is the +absolute path to the Powerline installation directory (see :ref:`repository root +<repository-root>`): + +.. code-block:: bash + + . {repository_root}/powerline/bindings/bash/powerline.sh + +.. note:: + Since without powerline daemon bash bindings are very slow PS2 + (continuation) and PS3 (select) prompts are not set up. Thus it is advised + to use + + .. code-block:: bash + + powerline-daemon -q + POWERLINE_BASH_CONTINUATION=1 + POWERLINE_BASH_SELECT=1 + . {repository_root}/powerline/bindings/bash/powerline.sh + + in the bash configuration file. Without ``POWERLINE_BASH_*`` variables PS2 + and PS3 prompts are computed exactly once at bash startup. + +.. warning:: + At maximum bash continuation PS2 and select PS3 prompts are computed each + time main PS1 prompt is computed. Thus putting e.g. current time into PS2 or + PS3 prompt will not work as expected. + + At minimum they are computed once on startup. + +Zsh prompt +========== + +Add the following line to the :file:`zshrc`, where ``{repository_root}`` is the +absolute path to the Powerline installation directory (see :ref:`repository root +<repository-root>`): + +.. code-block:: bash + + . {repository_root}/powerline/bindings/zsh/powerline.zsh + +Fish prompt +=========== + +Add the following line to :file:`config.fish`, where ``{repository_root}`` is +the absolute path to the Powerline installation directory (see :ref:`repository +root <repository-root>`): + +.. code-block:: bash + + set fish_function_path $fish_function_path "{repository_root}/powerline/bindings/fish" + powerline-setup + +.. warning:: Fish is supported only starting from version 2.1. + +Rcsh prompt +=========== + +Powerline supports Plan9 rc reimplementation *by Byron Rakitzis* packaged by +many \*nix distributions. To use it add + +.. code-block:: bash + + . {repository_root}/powerline/bindings/rc/powerline.rc + +(``{repository_root}`` is the absolute path to the Powerline installation +directory, see :ref:`repository root <repository-root>`) to :file:`rcrc` file +(usually :file:`~/.rcrc`) and make sure ``rc`` is started as a login shell (with +``-l`` argument): otherwise this configuration file is not read. + +.. warning:: + Original Plan9 shell and its \*nix port are not supported because they are + missing ``prompt`` special function (it is being called once before each + non-continuation prompt). Since powerline could not support shell without + this or equivalent feature some other not-so-critical features of that port + were used. + +Busybox (ash), mksh and dash prompt +===================================== + +After launching busybox run the following command: + +.. code-block:: bash + + . {repository_root}/powerline/bindings/shell/powerline.sh + +where ``{repository_root}`` is the absolute path to the Powerline installation +directory (see :ref:`repository root <repository-root>`). + +Mksh users may put this line into ``~/.mkshrc`` file. Dash users may use the +following in ``~/.profile``: + +.. code-block:: bash + + if test "$0" != "${0#dash}" ; then + export ENV={repository_root}/powerline/bindings/shell/powerline.sh + fi + +.. note:: + Dash users that already have ``$ENV`` defined should either put the ``. + …/shell/powerline.sh`` line in the ``$ENV`` file or create a new file which + will source (using ``.`` command) both former ``$ENV`` file and + :file:`powerline.sh` files and set ``$ENV`` to the path of this new file. + +.. warning:: + Mksh users have to set ``$POWERLINE_SHELL_CONTINUATION`` and + ``$POWERLINE_SHELL_SELECT`` to 1 to get PS2 and PS3 (continuation and + select) prompts support respectively: as command substitution is not + performed in these shells for these prompts they are updated once each time + PS1 prompt is displayed which may be slow. + + It is also known that while PS2 and PS3 update is triggered at PS1 update it + is *actually performed* only *next* time PS1 is displayed which means that + PS2 and PS3 prompts will be outdated and may be incorrect for this reason. + + Without these variables PS2 and PS3 prompts will be set once at startup. + This only touches mksh users: busybox and dash both have no such problem. + +.. warning:: + Job count is using some weird hack that uses signals and temporary files for + interprocess communication. It may be wrong sometimes. Not the case in mksh. + +.. warning:: + Busybox has two shells: ``ash`` and ``hush``. Second is known to segfault in + busybox 1.22.1 when using :file:`powerline.sh` script. diff --git a/docs/source/usage/wm-widgets.rst b/docs/source/usage/wm-widgets.rst new file mode 100644 index 0000000..204c008 --- /dev/null +++ b/docs/source/usage/wm-widgets.rst @@ -0,0 +1,102 @@ +********************** +Window manager widgets +********************** + +Awesome widget +============== + +.. note:: Powerline currently only supports awesome 3.5 and 4+. + +.. note:: The Powerline widget will spawn a shell script that runs in the + background and updates the statusline with ``awesome-client``. + +Add the following to :file:`rc.lua`, where ``{repository_root}`` is the absolute +path to Powerline installation directory (see :ref:`repository root +<repository-root>`): + +.. code-block:: lua + + package.path = package.path .. ';{repository_root}/powerline/bindings/awesome/?.lua' + require('powerline') + +Then add the ``powerline_widget`` to ``wibox``: + +.. code-block:: lua + + -- awesome3.5 + right_layout:add(powerline_widget) + + -- awesome4+ + s.mywibox:setup { + ... + { -- Right widgets + ... + powerline_widget, + }, + } + +Qtile widget +============ + +Add the following to :file:`~/.config/qtile/config.py`: + +.. code-block:: python + + from libqtile.bar import Bar + from libqtile.config import Screen + from libqtile.widget import Spacer + + from powerline.bindings.qtile.widget import PowerlineTextBox + + screens = [ + Screen( + top=Bar([ + PowerlineTextBox(update_interval=2, side='left'), + Spacer(), + PowerlineTextBox(update_interval=2, side='right'), + ], + 35 # width + ), + ), + ] + +.. _lemonbar-usage: + +lemonbar (formerly bar-aint-recursive) +====================================== + +To run the bar simply start the binding script: + + python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py + +You can specify options to be passed to ``lemonbar`` after ``--``, like so: + + python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --height 16 -- -f "Source Code Pro for Powerline-9" + +to run with i3, simply ``exec`` this in the i3 config file and set the ``--i3`` switch: + + exec python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --i3 + +Running the binding in i3-mode will require `i3ipc <https://github.com/acrisci/i3ipc-python>`_ +(or the outdated `i3-py <https://github.com/ziberna/i3-py>`_). + +See the `lemonbar documentation <https://github.com/LemonBoy/bar>`_ for more +information and options. + +All ``powerline-lemonbar.py`` arguments: + +.. automan:: powerline.commands.lemonbar + :prog: powerline-lemonbar.py + :minimal: true + +I3 bar +====== + +Add the following to :file:`~/.config/i3/config`:: + + bar { + status_command python /path/to/powerline/bindings/i3/powerline-i3.py + font pango:PowerlineFont 12 + } + +where ``PowerlineFont`` is any system font with powerline support. diff --git a/font/10-powerline-symbols.conf b/font/10-powerline-symbols.conf new file mode 100644 index 0000000..7e34a12 --- /dev/null +++ b/font/10-powerline-symbols.conf @@ -0,0 +1,105 @@ +<?xml version="1.0"?> +<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> + +<fontconfig> + <alias> + <family>monospace</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Droid Sans Mono</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Droid Sans Mono Slashed</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Droid Sans Mono Dotted</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>DejaVu Sans Mono</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>DejaVu Sans Mono</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Envy Code R</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Inconsolata</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Lucida Console</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Monaco</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Pragmata</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>PragmataPro</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Menlo</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Source Code Pro</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Consolas</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Anonymous pro</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Bitstream Vera Sans Mono</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Liberation Mono</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Ubuntu Mono</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Meslo LG L</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Meslo LG L DZ</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Meslo LG M</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Meslo LG M DZ</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Meslo LG S</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> + <alias> + <family>Meslo LG S DZ</family> + <prefer><family>PowerlineSymbols</family></prefer> + </alias> +</fontconfig> diff --git a/font/PowerlineSymbols.otf b/font/PowerlineSymbols.otf Binary files differnew file mode 100644 index 0000000..b1582af --- /dev/null +++ b/font/PowerlineSymbols.otf diff --git a/powerline/__init__.py b/powerline/__init__.py new file mode 100644 index 0000000..7c781d9 --- /dev/null +++ b/powerline/__init__.py @@ -0,0 +1,991 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys +import logging + +from threading import Lock, Event + +from powerline.colorscheme import Colorscheme +from powerline.lib.config import ConfigLoader +from powerline.lib.unicode import unicode, safe_unicode, FailedUnicode +from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR +from powerline.lib.dict import mergedicts +from powerline.lib.encoding import get_preferred_output_encoding +from powerline.lib.path import join + + +class NotInterceptedError(BaseException): + pass + + +def _config_loader_condition(path): + if path and os.path.isfile(path): + return path + return None + + +def _find_config_files(search_paths, config_file, config_loader=None, loader_callback=None): + config_file += '.json' + found = False + for path in search_paths: + config_file_path = join(path, config_file) + if os.path.isfile(config_file_path): + yield config_file_path + found = True + elif config_loader: + config_loader.register_missing(_config_loader_condition, loader_callback, config_file_path) + if not found: + raise IOError('Config file not found in search paths ({0}): {1}'.format( + ', '.join(search_paths), + config_file + )) + + +class PowerlineLogger(object): + '''Proxy class for logging.Logger instance + + It emits messages in format ``{ext}:{prefix}:{message}`` where + + ``{ext}`` + is a used powerline extension (e.g. “vim”, “shell”, “ipython”). + ``{prefix}`` + is a local prefix, usually a segment name. + ``{message}`` + is the original message passed to one of the logging methods. + + Each of the methods (``critical``, ``exception``, ``info``, ``error``, + ``warn``, ``debug``) expects to receive message in an ``str.format`` format, + not in printf-like format. + + Log is saved to the location :ref:`specified by user <config-common-log>`. + ''' + + def __init__(self, use_daemon_threads, logger, ext): + self.logger = logger + self.ext = ext + self.use_daemon_threads = use_daemon_threads + self.prefix = '' + self.last_msgs = {} + + def _log(self, attr, msg, *args, **kwargs): + prefix = kwargs.get('prefix') or self.prefix + prefix = self.ext + ((':' + prefix) if prefix else '') + msg = safe_unicode(msg) + if args or kwargs: + args = [safe_unicode(s) if isinstance(s, bytes) else s for s in args] + kwargs = dict(( + (k, safe_unicode(v) if isinstance(v, bytes) else v) + for k, v in kwargs.items() + )) + msg = msg.format(*args, **kwargs) + msg = prefix + ':' + msg + key = attr + ':' + prefix + if msg != self.last_msgs.get(key): + getattr(self.logger, attr)(msg) + self.last_msgs[key] = msg + + def critical(self, msg, *args, **kwargs): + self._log('critical', msg, *args, **kwargs) + + def exception(self, msg, *args, **kwargs): + self._log('exception', msg, *args, **kwargs) + + def info(self, msg, *args, **kwargs): + self._log('info', msg, *args, **kwargs) + + def error(self, msg, *args, **kwargs): + self._log('error', msg, *args, **kwargs) + + def warn(self, msg, *args, **kwargs): + self._log('warning', msg, *args, **kwargs) + + def debug(self, msg, *args, **kwargs): + self._log('debug', msg, *args, **kwargs) + + +_fallback_logger = None + + +def get_fallback_logger(stream=None): + global _fallback_logger + if _fallback_logger: + return _fallback_logger + + log_format = '%(asctime)s:%(levelname)s:%(message)s' + formatter = logging.Formatter(log_format) + + level = logging.WARNING + handler = logging.StreamHandler(stream) + handler.setLevel(level) + handler.setFormatter(formatter) + + logger = logging.Logger('powerline') + logger.setLevel(level) + logger.addHandler(handler) + _fallback_logger = PowerlineLogger(None, logger, '_fallback_') + return _fallback_logger + + +def _generate_change_callback(lock, key, dictionary): + def on_file_change(path): + with lock: + dictionary[key] = True + return on_file_change + + +def get_config_paths(): + '''Get configuration paths from environment variables. + + Uses $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS according to the XDG specification. + + :return: list of paths + ''' + config_home = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config')) + config_path = join(config_home, 'powerline') + config_paths = [config_path] + config_dirs = os.environ.get('XDG_CONFIG_DIRS', DEFAULT_SYSTEM_CONFIG_DIR) + if config_dirs is not None: + config_paths[:0] = reversed([join(d, 'powerline') for d in config_dirs.split(':')]) + plugin_path = join(os.path.realpath(os.path.dirname(__file__)), 'config_files') + config_paths.insert(0, plugin_path) + return config_paths + + +def generate_config_finder(get_config_paths=get_config_paths): + '''Generate find_config_files function + + This function will find .json file given its path. + + :param function get_config_paths: + Function that being called with no arguments will return a list of paths + that should be searched for configuration files. + + :return: + Function that being given configuration file name will return full path + to it or raise IOError if it failed to find the file. + ''' + config_paths = get_config_paths() + return lambda *args: _find_config_files(config_paths, *args) + + +def load_config(cfg_path, find_config_files, config_loader, loader_callback=None): + '''Load configuration file and setup watches + + Watches are only set up if loader_callback is not None. + + :param str cfg_path: + Path for configuration file that should be loaded. + :param function find_config_files: + Function that finds configuration file. Check out the description of + the return value of ``generate_config_finder`` function. + :param ConfigLoader config_loader: + Configuration file loader class instance. + :param function loader_callback: + Function that will be called by config_loader when change to + configuration file is detected. + + :return: Configuration file contents. + ''' + found_files = find_config_files(cfg_path, config_loader, loader_callback) + ret = None + for path in found_files: + if loader_callback: + config_loader.register(loader_callback, path) + if ret is None: + ret = config_loader.load(path) + else: + mergedicts(ret, config_loader.load(path)) + return ret + + +def _set_log_handlers(common_config, logger, get_module_attr, stream=None): + '''Set log handlers + + :param dict common_config: + Configuration dictionary used to create handler. + :param logging.Logger logger: + Logger to which handlers will be attached. + :param func get_module_attr: + :py:func:`gen_module_attr_getter` output. + :param file stream: + Stream to use by default for :py:class:`logging.StreamHandler` in place + of :py:attr:`sys.stderr`. May be ``None``. + ''' + log_targets = common_config['log_file'] + num_handlers = 0 + for log_target in log_targets: + if log_target is None: + log_target = ['logging.StreamHandler', []] + elif isinstance(log_target, unicode): + log_target = os.path.expanduser(log_target) + log_dir = os.path.dirname(log_target) + if log_dir and not os.path.isdir(log_dir): + os.mkdir(log_dir) + log_target = ['logging.FileHandler', [[log_target]]] + module, handler_class_name = log_target[0].rpartition('.')[::2] + module = module or 'logging.handlers' + try: + handler_class_args = log_target[1][0] + except IndexError: + if module == 'logging' and handler_class_name == 'StreamHandler': + handler_class_args = [stream] + else: + handler_class_args = () + try: + handler_class_kwargs = log_target[1][1] + except IndexError: + handler_class_kwargs = {} + module = str(module) + handler_class_name = str(handler_class_name) + handler_class = get_module_attr(module, handler_class_name) + if not handler_class: + continue + handler = handler_class(*handler_class_args, **handler_class_kwargs) + try: + handler_level_name = log_target[2] + except IndexError: + handler_level_name = common_config['log_level'] + try: + handler_format = log_target[3] + except IndexError: + handler_format = common_config['log_format'] + handler.setLevel(getattr(logging, handler_level_name)) + handler.setFormatter(logging.Formatter(handler_format)) + logger.addHandler(handler) + num_handlers += 1 + if num_handlers == 0 and log_targets: + raise ValueError('Failed to set up any handlers') + + +def create_logger(common_config, use_daemon_threads=True, ext='__unknown__', + import_paths=None, imported_modules=None, stream=None): + '''Create logger according to provided configuration + + :param dict common_config: + Common configuration, from :py:func:`finish_common_config`. + :param bool use_daemon_threads: + Whether daemon threads should be used. Argument to + :py:class:`PowerlineLogger` constructor. + :param str ext: + Used extension. Argument to :py:class:`PowerlineLogger` constructor. + :param set imported_modules: + Set where imported modules are saved. Argument to + :py:func:`gen_module_attr_getter`. May be ``None``, in this case new + empty set is used. + :param file stream: + Stream to use by default for :py:class:`logging.StreamHandler` in place + of :py:attr:`sys.stderr`. May be ``None``. + + :return: Three objects: + + #. :py:class:`logging.Logger` instance. + #. :py:class:`PowerlineLogger` instance. + #. Function, output of :py:func:`gen_module_attr_getter`. + ''' + logger = logging.Logger('powerline') + level = getattr(logging, common_config['log_level']) + logger.setLevel(level) + + pl = PowerlineLogger(use_daemon_threads, logger, ext) + get_module_attr = gen_module_attr_getter( + pl, common_config['paths'], + set() if imported_modules is None else imported_modules) + + _set_log_handlers(common_config, logger, get_module_attr, stream) + + return logger, pl, get_module_attr + + +def get_default_theme(is_unicode=True): + '''Get default theme used by powerline + + :param bool is_unicode: + If true, return theme for unicode environments, otherwise return theme + that is supposed to be ASCII-only. + + :return: theme name. + ''' + return 'powerline_terminus' if is_unicode else 'ascii' + + +def finish_common_config(encoding, common_config): + '''Add default values to common config and expand ~ in paths + + :param dict common_config: + Common configuration, as it was just loaded. + + :return: + Copy of common configuration with all configuration keys and expanded + paths. + ''' + encoding = encoding.lower() + default_top_theme = get_default_theme( + encoding.startswith('utf') or encoding.startswith('ucs')) + + common_config = common_config.copy() + common_config.setdefault('default_top_theme', default_top_theme) + common_config.setdefault('paths', []) + common_config.setdefault('watcher', 'auto') + common_config.setdefault('log_level', 'WARNING') + common_config.setdefault('log_format', '%(asctime)s:%(levelname)s:%(message)s') + common_config.setdefault('term_truecolor', False) + common_config.setdefault('term_escape_style', 'auto') + common_config.setdefault('ambiwidth', 1) + common_config.setdefault('additional_escapes', None) + common_config.setdefault('reload_config', True) + common_config.setdefault('interval', None) + common_config.setdefault('log_file', [None]) + + if not isinstance(common_config['log_file'], list): + common_config['log_file'] = [common_config['log_file']] + + common_config['paths'] = [ + os.path.expanduser(path) for path in common_config['paths'] + ] + + return common_config + + +if sys.version_info < (3,): + # `raise exception[0], None, exception[1]` is a SyntaxError in python-3* + # Not using ('''…''') because this syntax does not work in python-2.6 + exec(( + 'def reraise(exception):\n' + ' if type(exception) is tuple:\n' + ' raise exception[0], None, exception[1]\n' + ' else:\n' + ' raise exception\n' + )) +else: + def reraise(exception): + if type(exception) is tuple: + raise exception[0].with_traceback(exception[1]) + else: + raise exception + + +def gen_module_attr_getter(pl, import_paths, imported_modules): + def get_module_attr(module, attr, prefix='powerline'): + '''Import module and get its attribute. + + Replaces ``from {module} import {attr}``. + + :param str module: + Module name, will be passed as first argument to ``__import__``. + :param str attr: + Module attribute, will be passed to ``__import__`` as the only value + in ``fromlist`` tuple. + + :return: + Attribute value or ``None``. Note: there is no way to distinguish + between successfull import of attribute equal to ``None`` and + unsuccessfull import. + ''' + oldpath = sys.path + sys.path = import_paths + sys.path + module = str(module) + attr = str(attr) + try: + imported_modules.add(module) + return getattr(__import__(module, fromlist=(attr,)), attr) + except Exception as e: + pl.exception('Failed to import attr {0} from module {1}: {2}', attr, module, str(e), prefix=prefix) + return None + finally: + sys.path = oldpath + + return get_module_attr + + +LOG_KEYS = set(('log_format', 'log_level', 'log_file', 'paths')) +'''List of keys related to logging +''' + + +def _get_log_keys(common_config): + '''Return a common configuration copy with only log-related config left + + :param dict common_config: + Common configuration. + + :return: + :py:class:`dict` instance which has only keys from + :py:attr:`powerline.LOG_KEYS` left. + ''' + return dict(( + (k, v) for k, v in common_config.items() if k in LOG_KEYS + )) + + +DEFAULT_UPDATE_INTERVAL = 2 +'''Default value for :ref:`update_interval <config-ext-update_interval>` +''' + + +class Powerline(object): + '''Main powerline class, entrance point for all powerline uses. Sets + powerline up and loads the configuration. + + :param str ext: + extension used. Determines where configuration files will + searched and what renderer module will be used. Affected: used ``ext`` + dictionary from :file:`powerline/config.json`, location of themes and + colorschemes, render module (``powerline.renders.{ext}``). + :param str renderer_module: + Overrides renderer module (defaults to ``ext``). Should be the name of + the package imported like this: ``powerline.renderers.{render_module}``. + If this parameter contains a dot ``powerline.renderers.`` is not + prepended. There is also a special case for renderers defined in + toplevel modules: ``foo.`` (note: dot at the end) tries to get renderer + from module ``foo`` (because ``foo`` (without dot) tries to get renderer + from module ``powerline.renderers.foo``). When ``.foo`` (with leading + dot) variant is used ``renderer_module`` will be + ``powerline.renderers.{ext}{renderer_module}``. + :param bool run_once: + Determines whether :py:meth:`render` method will be run only once + during python session. + :param Logger logger: + If present no new logger will be created and the provided logger will be + used. + :param bool use_daemon_threads: + When creating threads make them daemon ones. + :param Event shutdown_event: + Use this Event as shutdown_event instead of creating new event. + :param ConfigLoader config_loader: + Instance of the class that manages (re)loading of the configuration. + ''' + + def __init__(self, *args, **kwargs): + self.init_args = (args, kwargs) + self.init(*args, **kwargs) + + def init(self, + ext, + renderer_module=None, + run_once=False, + logger=None, + use_daemon_threads=True, + shutdown_event=None, + config_loader=None): + '''Do actual initialization. + + __init__ function only stores the arguments and runs this function. This + function exists for powerline to be able to reload itself: it is easier + to make ``__init__`` store arguments and call overriddable ``init`` than + tell developers that each time they override Powerline.__init__ in + subclasses they must store actual arguments. + ''' + self.ext = ext + self.run_once = run_once + self.logger = logger + self.had_logger = bool(self.logger) + self.use_daemon_threads = use_daemon_threads + + if not renderer_module: + self.renderer_module = 'powerline.renderers.' + ext + elif '.' not in renderer_module: + self.renderer_module = 'powerline.renderers.' + renderer_module + elif renderer_module.startswith('.'): + self.renderer_module = 'powerline.renderers.' + ext + renderer_module + elif renderer_module.endswith('.'): + self.renderer_module = renderer_module[:-1] + else: + self.renderer_module = renderer_module + + self.find_config_files = generate_config_finder(self.get_config_paths) + + self.cr_kwargs_lock = Lock() + self.cr_kwargs = {} + self.cr_callbacks = {} + for key in ('main', 'colors', 'colorscheme', 'theme'): + self.cr_kwargs['load_' + key] = True + self.cr_callbacks[key] = _generate_change_callback( + self.cr_kwargs_lock, + 'load_' + key, + self.cr_kwargs + ) + + self.shutdown_event = shutdown_event or Event() + self.config_loader = config_loader or ConfigLoader(shutdown_event=self.shutdown_event, run_once=run_once) + self.run_loader_update = False + + self.renderer_options = {} + + self.prev_common_config = None + self.prev_ext_config = None + self.pl = None + self.setup_args = () + self.setup_kwargs = {} + self.imported_modules = set() + self.update_interval = DEFAULT_UPDATE_INTERVAL + + get_encoding = staticmethod(get_preferred_output_encoding) + '''Get encoding used by the current application + + Usually returns encoding of the current locale. + ''' + + def create_logger(self): + '''Create logger + + This function is used to create logger unless it was already specified + at initialization. + + :return: Three objects: + + #. :py:class:`logging.Logger` instance. + #. :py:class:`PowerlineLogger` instance. + #. Function, output of :py:func:`gen_module_attr_getter`. + ''' + return create_logger( + common_config=self.common_config, + use_daemon_threads=self.use_daemon_threads, + ext=self.ext, + imported_modules=self.imported_modules, + stream=self.default_log_stream, + ) + + def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False): + '''(Re)create renderer object. Can be used after Powerline object was + successfully initialized. If any of the below parameters except + ``load_main`` is True renderer object will be recreated. + + :param bool load_main: + Determines whether main configuration file (:file:`config.json`) + should be loaded. If appropriate configuration changes implies + ``load_colorscheme`` and ``load_theme`` and recreation of renderer + object. Won’t trigger recreation if only unrelated configuration + changed. + :param bool load_colors: + Determines whether colors configuration from :file:`colors.json` + should be (re)loaded. + :param bool load_colorscheme: + Determines whether colorscheme configuration should be (re)loaded. + :param bool load_theme: + Determines whether theme configuration should be reloaded. + ''' + common_config_differs = False + ext_config_differs = False + if load_main: + self._purge_configs('main') + config = self.load_main_config() + self.common_config = finish_common_config(self.get_encoding(), config['common']) + if self.common_config != self.prev_common_config: + common_config_differs = True + + load_theme = (load_theme + or not self.prev_common_config + or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme']) + + log_keys_differ = (not self.prev_common_config or ( + _get_log_keys(self.prev_common_config) != _get_log_keys(self.common_config) + )) + + self.prev_common_config = self.common_config + + if log_keys_differ: + if self.had_logger: + self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext) + self.get_module_attr = gen_module_attr_getter( + self.pl, self.common_config['paths'], self.imported_modules) + else: + self.logger, self.pl, self.get_module_attr = self.create_logger() + self.config_loader.pl = self.pl + + if not self.run_once: + self.config_loader.set_watcher(self.common_config['watcher']) + + mergedicts(self.renderer_options, dict( + pl=self.pl, + term_truecolor=self.common_config['term_truecolor'], + term_escape_style=self.common_config['term_escape_style'], + ambiwidth=self.common_config['ambiwidth'], + tmux_escape=self.common_config['additional_escapes'] == 'tmux', + screen_escape=self.common_config['additional_escapes'] == 'screen', + theme_kwargs={ + 'ext': self.ext, + 'common_config': self.common_config, + 'run_once': self.run_once, + 'shutdown_event': self.shutdown_event, + 'get_module_attr': self.get_module_attr, + }, + )) + + if not self.run_once and self.common_config['reload_config']: + interval = self.common_config['interval'] + self.config_loader.set_interval(interval) + self.run_loader_update = (interval is None) + if interval is not None and not self.config_loader.is_alive(): + self.config_loader.start() + + self.ext_config = config['ext'][self.ext] + + top_theme = ( + self.ext_config.get('top_theme') + or self.common_config['default_top_theme'] + ) + self.theme_levels = ( + os.path.join('themes', top_theme), + os.path.join('themes', self.ext, '__main__'), + ) + self.renderer_options['theme_kwargs']['top_theme'] = top_theme + + if self.ext_config != self.prev_ext_config: + ext_config_differs = True + if ( + not self.prev_ext_config + or self.ext_config.get('components') != self.prev_ext_config.get('components') + ): + self.setup_components(self.ext_config.get('components')) + if ( + not self.prev_ext_config + or self.ext_config.get('local_themes') != self.prev_ext_config.get('local_themes') + ): + self.renderer_options['local_themes'] = self.get_local_themes(self.ext_config.get('local_themes')) + self.update_interval = self.ext_config.get('update_interval', 2) + load_colorscheme = ( + load_colorscheme + or not self.prev_ext_config + or self.prev_ext_config['colorscheme'] != self.ext_config['colorscheme'] + ) + load_theme = ( + load_theme + or not self.prev_ext_config + or self.prev_ext_config['theme'] != self.ext_config['theme'] + ) + self.prev_ext_config = self.ext_config + + create_renderer = load_colors or load_colorscheme or load_theme or common_config_differs or ext_config_differs + + if load_colors: + self._purge_configs('colors') + self.colors_config = self.load_colors_config() + + if load_colorscheme or load_colors: + self._purge_configs('colorscheme') + if load_colorscheme: + self.colorscheme_config = self.load_colorscheme_config(self.ext_config['colorscheme']) + self.renderer_options['theme_kwargs']['colorscheme'] = ( + Colorscheme(self.colorscheme_config, self.colors_config)) + + if load_theme: + self._purge_configs('theme') + self.renderer_options['theme_config'] = self.load_theme_config(self.ext_config.get('theme', 'default')) + + if create_renderer: + Renderer = self.get_module_attr(self.renderer_module, 'renderer') + if not Renderer: + if hasattr(self, 'renderer'): + return + else: + raise ImportError('Failed to obtain renderer') + + # Renderer updates configuration file via segments’ .startup thus it + # should be locked to prevent state when configuration was updated, + # but .render still uses old renderer. + try: + renderer = Renderer(**self.renderer_options) + except Exception as e: + self.exception('Failed to construct renderer object: {0}', str(e)) + if not hasattr(self, 'renderer'): + raise + else: + self.renderer = renderer + + default_log_stream = sys.stdout + '''Default stream for default log handler + + Usually it is ``sys.stderr``, but there is sometimes a reason to prefer + ``sys.stdout`` or a custom file-like object. It is not supposed to be used + to write to some file. + ''' + + def setup_components(self, components): + '''Run component-specific setup + + :param set components: + Set of the enabled componets or None. + + Should be overridden by subclasses. + ''' + pass + + @staticmethod + def get_config_paths(): + '''Get configuration paths. + + Should be overridden in subclasses in order to provide a way to override + used paths. + + :return: list of paths + ''' + return get_config_paths() + + def load_config(self, cfg_path, cfg_type): + '''Load configuration and setup watches + + :param str cfg_path: + Path to the configuration file without any powerline configuration + directory or ``.json`` suffix. + :param str cfg_type: + Configuration type. May be one of ``main`` (for ``config.json`` + file), ``colors``, ``colorscheme``, ``theme``. + + :return: dictionary with loaded configuration. + ''' + return load_config( + cfg_path, + self.find_config_files, + self.config_loader, + self.cr_callbacks[cfg_type] + ) + + def _purge_configs(self, cfg_type): + function = self.cr_callbacks[cfg_type] + self.config_loader.unregister_functions(set((function,))) + self.config_loader.unregister_missing(set(((self.find_config_files, function),))) + + def load_main_config(self): + '''Get top-level configuration. + + :return: dictionary with :ref:`top-level configuration <config-main>`. + ''' + return self.load_config('config', 'main') + + def _load_hierarhical_config(self, cfg_type, levels, ignore_levels): + '''Load and merge multiple configuration files + + :param str cfg_type: + Type of the loaded configuration files (e.g. ``colorscheme``, + ``theme``). + :param list levels: + Configuration names resembling levels in hierarchy, sorted by + priority. Configuration file names with higher priority should go + last. + :param set ignore_levels: + If only files listed in this variable are present then configuration + file is considered not loaded: at least one file on the level not + listed in this variable must be present. + ''' + config = {} + loaded = 0 + exceptions = [] + for i, cfg_path in enumerate(levels): + try: + lvl_config = self.load_config(cfg_path, cfg_type) + except IOError as e: + if sys.version_info < (3,): + tb = sys.exc_info()[2] + exceptions.append((e, tb)) + else: + exceptions.append(e) + else: + if i not in ignore_levels: + loaded += 1 + mergedicts(config, lvl_config) + if not loaded: + for exception in exceptions: + if type(exception) is tuple: + e = exception[0] + else: + e = exception + self.exception('Failed to load %s: {0}' % cfg_type, e, exception=exception) + raise e + return config + + def load_colorscheme_config(self, name): + '''Get colorscheme. + + :param str name: + Name of the colorscheme to load. + + :return: dictionary with :ref:`colorscheme configuration <config-colorschemes>`. + ''' + levels = ( + os.path.join('colorschemes', name), + os.path.join('colorschemes', self.ext, '__main__'), + os.path.join('colorschemes', self.ext, name), + ) + return self._load_hierarhical_config('colorscheme', levels, (1,)) + + def load_theme_config(self, name): + '''Get theme configuration. + + :param str name: + Name of the theme to load. + + :return: dictionary with :ref:`theme configuration <config-themes>` + ''' + levels = self.theme_levels + ( + os.path.join('themes', self.ext, name), + ) + return self._load_hierarhical_config('theme', levels, (0, 1,)) + + def load_colors_config(self): + '''Get colorscheme. + + :return: dictionary with :ref:`colors configuration <config-colors>`. + ''' + return self.load_config('colors', 'colors') + + @staticmethod + def get_local_themes(local_themes): + '''Get local themes. No-op here, to be overridden in subclasses if + required. + + :param dict local_themes: + Usually accepts ``{matcher_name : theme_name}``. May also receive + None in case there is no local_themes configuration. + + :return: + anything accepted by ``self.renderer.get_theme`` and processable by + ``self.renderer.add_local_theme``. Renderer module is determined by + ``__init__`` arguments, refer to its documentation. + ''' + return None + + def update_renderer(self): + '''Updates/creates a renderer if needed.''' + if self.run_loader_update: + self.config_loader.update() + cr_kwargs = None + with self.cr_kwargs_lock: + if self.cr_kwargs: + cr_kwargs = self.cr_kwargs.copy() + if cr_kwargs: + try: + self.create_renderer(**cr_kwargs) + except Exception as e: + self.exception('Failed to create renderer: {0}', str(e)) + if hasattr(self, 'renderer'): + with self.cr_kwargs_lock: + self.cr_kwargs.clear() + else: + raise + else: + with self.cr_kwargs_lock: + self.cr_kwargs.clear() + + def render(self, *args, **kwargs): + '''Update/create renderer if needed and pass all arguments further to + ``self.renderer.render()``. + ''' + try: + self.update_renderer() + return self.renderer.render(*args, **kwargs) + except Exception as e: + exc = e + try: + self.exception('Failed to render: {0}', str(e)) + except Exception as e: + exc = e + ret = FailedUnicode(safe_unicode(exc)) + if kwargs.get('output_width', False): + ret = ret, len(ret) + return ret + + def render_above_lines(self, *args, **kwargs): + '''Like .render(), but for ``self.renderer.render_above_lines()`` + ''' + try: + self.update_renderer() + for line in self.renderer.render_above_lines(*args, **kwargs): + yield line + except Exception as e: + exc = e + try: + self.exception('Failed to render: {0}', str(e)) + except Exception as e: + exc = e + yield FailedUnicode(safe_unicode(exc)) + + def setup(self, *args, **kwargs): + '''Setup the environment to use powerline. + + Must not be overridden by subclasses. This one only saves setup + arguments for :py:meth:`reload` method and calls :py:meth:`do_setup`. + ''' + self.shutdown_event.clear() + self.setup_args = args + self.setup_kwargs.update(kwargs) + self.do_setup(*args, **kwargs) + + @staticmethod + def do_setup(): + '''Function that does initialization + + Should be overridden by subclasses. May accept any number of regular or + keyword arguments. + ''' + pass + + def reload(self): + '''Reload powerline after update. + + Should handle most (but not all) powerline updates. + + Purges out all powerline modules and modules imported by powerline for + segment and matcher functions. Requires defining ``setup`` function that + updates reference to main powerline object. + + .. warning:: + Not guaranteed to work properly, use it at your own risk. It + may break your python code. + ''' + import sys + modules = self.imported_modules | set((module for module in sys.modules if module.startswith('powerline'))) + modules_holder = [] + for module in modules: + try: + # Needs to hold module to prevent garbage collecting until they + # are all reloaded. + modules_holder.append(sys.modules.pop(module)) + except KeyError: + pass + PowerlineClass = getattr(__import__(self.__module__, fromlist=(self.__class__.__name__,)), self.__class__.__name__) + self.shutdown(set_event=True) + init_args, init_kwargs = self.init_args + powerline = PowerlineClass(*init_args, **init_kwargs) + powerline.setup(*self.setup_args, **self.setup_kwargs) + + def shutdown(self, set_event=True): + '''Shut down all background threads. + + :param bool set_event: + Set ``shutdown_event`` and call ``renderer.shutdown`` which should + shut down all threads. Set it to False unless you are exiting an + application. + + If set to False this does nothing more then resolving reference + cycle ``powerline → config_loader → bound methods → powerline`` by + unsubscribing from config_loader events. + ''' + if set_event: + self.shutdown_event.set() + try: + self.renderer.shutdown() + except AttributeError: + pass + functions = tuple(self.cr_callbacks.values()) + self.config_loader.unregister_functions(set(functions)) + self.config_loader.unregister_missing(set(((self.find_config_files, function) for function in functions))) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.shutdown() + + def exception(self, msg, *args, **kwargs): + if 'prefix' not in kwargs: + kwargs['prefix'] = 'powerline' + exception = kwargs.pop('exception', None) + pl = getattr(self, 'pl', None) or get_fallback_logger(self.default_log_stream) + if exception: + try: + reraise(exception) + except Exception: + return pl.exception(msg, *args, **kwargs) + return pl.exception(msg, *args, **kwargs) diff --git a/powerline/bindings/__init__.py b/powerline/bindings/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/bindings/__init__.py diff --git a/powerline/bindings/awesome/powerline-awesome.py b/powerline/bindings/awesome/powerline-awesome.py new file mode 100755 index 0000000..500d47d --- /dev/null +++ b/powerline/bindings/awesome/powerline-awesome.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +from powerline.bindings.wm import DEFAULT_UPDATE_INTERVAL +from powerline.bindings.wm.awesome import run + + +def main(): + try: + interval = float(sys.argv[1]) + except IndexError: + interval = DEFAULT_UPDATE_INTERVAL + run(interval=interval) + + +if __name__ == '__main__': + main() diff --git a/powerline/bindings/awesome/powerline.lua b/powerline/bindings/awesome/powerline.lua new file mode 100644 index 0000000..470901f --- /dev/null +++ b/powerline/bindings/awesome/powerline.lua @@ -0,0 +1,15 @@ +local wibox = require('wibox') +local awful = require('awful') + +powerline_widget = wibox.widget.textbox() +powerline_widget:set_align('right') + +function powerline(mode, widget) end + +if string.find(awesome.version, 'v4') then + awful.spawn.with_shell('powerline-daemon -q') + awful.spawn.with_shell('powerline wm.awesome') +else + awful.util.spawn_with_shell('powerline-daemon -q') + awful.util.spawn_with_shell('powerline wm.awesome') +end diff --git a/powerline/bindings/bar/powerline-bar.py b/powerline/bindings/bar/powerline-bar.py new file mode 100755 index 0000000..71e8ae3 --- /dev/null +++ b/powerline/bindings/bar/powerline-bar.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys +import time + +from threading import Lock, Timer +from argparse import ArgumentParser + +from powerline.lemonbar import LemonbarPowerline +from powerline.lib.encoding import get_unicode_writer +from powerline.bindings.wm import DEFAULT_UPDATE_INTERVAL + + +if __name__ == '__main__': + parser = ArgumentParser(description='Powerline lemonbar bindings.') + parser.add_argument( + '--i3', action='store_true', + help='Subscribe for i3 events.' + ) + args = parser.parse_args() + powerline = LemonbarPowerline() + powerline.update_renderer() + powerline.pl.warn("The 'bar' bindings are deprecated, please switch to 'lemonbar'") + lock = Lock() + modes = ['default'] + write = get_unicode_writer(encoding='utf-8') + + def render(reschedule=False): + if reschedule: + Timer(DEFAULT_UPDATE_INTERVAL, render, kwargs={'reschedule': True}).start() + + global lock + with lock: + write(powerline.render(mode=modes[0])) + write('\n') + sys.stdout.flush() + + def update(evt): + modes[0] = evt.change + render() + + render(reschedule=True) + + if args.i3: + try: + import i3ipc + except ImportError: + import i3 + i3.Subscription(lambda evt, data, sub: print(render()), 'workspace') + else: + conn = i3ipc.Connection() + conn.on('workspace::focus', lambda conn, evt: render()) + conn.on('mode', lambda conn, evt: update(evt)) + conn.main() + + while True: + time.sleep(1e8) diff --git a/powerline/bindings/bash/powerline.sh b/powerline/bindings/bash/powerline.sh new file mode 100644 index 0000000..f4e933e --- /dev/null +++ b/powerline/bindings/bash/powerline.sh @@ -0,0 +1,153 @@ +_powerline_columns_fallback() { + if which stty &>/dev/null ; then + local cols="$(stty size 2>/dev/null)" + if ! test -z "$cols" ; then + echo "${cols#* }" + return 0 + fi + fi + echo 0 + return 0 +} + +_powerline_tmux_pane() { + echo "${TMUX_PANE:-`TMUX="$_POWERLINE_TMUX" tmux display -p "#D"`}" | \ + tr -d ' %' +} + +_powerline_tmux_setenv() { + TMUX="$_POWERLINE_TMUX" tmux setenv -g TMUX_"$1"_`_powerline_tmux_pane` "$2" + TMUX="$_POWERLINE_TMUX" tmux refresh -S +} + +_powerline_tmux_set_pwd() { + if test "$_POWERLINE_SAVED_PWD" != "$PWD" ; then + _POWERLINE_SAVED_PWD="$PWD" + _powerline_tmux_setenv PWD "$PWD" + fi +} + +_powerline_return() { + return $1 +} + +_POWERLINE_HAS_PIPESTATUS="$( + _powerline_return 0 | _powerline_return 43 + test "${PIPESTATUS[*]}" = "0 43" + echo "$?" +)" + +_powerline_has_pipestatus() { + return $_POWERLINE_HAS_PIPESTATUS +} + +_powerline_status_wrapper() { + local last_exit_code=$? last_pipe_status=( "${PIPESTATUS[@]}" ) + + if ! _powerline_has_pipestatus \ + || test "${#last_pipe_status[@]}" -eq "0" \ + || test "$last_exit_code" != "${last_pipe_status[$(( ${#last_pipe_status[@]} - 1 ))]}" ; then + last_pipe_status=() + fi + "$@" $last_exit_code "${last_pipe_status[*]}" + return $last_exit_code +} + +_powerline_add_status_wrapped_command() { + local action="$1" ; shift + local cmd="$1" ; shift + full_cmd="_powerline_status_wrapper $cmd" + if test "$action" = "append" ; then + PROMPT_COMMAND="$PROMPT_COMMAND"$'\n'"$full_cmd" + else + PROMPT_COMMAND="$full_cmd"$'\n'"$PROMPT_COMMAND" + fi +} + +_powerline_tmux_set_columns() { + _powerline_tmux_setenv COLUMNS "${COLUMNS:-`_powerline_columns_fallback`}" +} + +_powerline_init_tmux_support() { + if test -n "$TMUX" && tmux refresh -S &>/dev/null ; then + # TMUX variable may be unset to create new tmux session inside this one + _POWERLINE_TMUX="$TMUX" + + trap '_powerline_tmux_set_columns' WINCH + _powerline_tmux_set_columns + + test "$PROMPT_COMMAND" != "${PROMPT_COMMAND/_powerline_tmux_set_pwd}" \ + || _powerline_add_status_wrapped_command append _powerline_tmux_set_pwd + fi +} + +_powerline_local_prompt() { + # Arguments: + # 1: side + # 2: renderer_module arg + # 3: last_exit_code + # 4: last_pipe_status + # 5: jobnum + # 6: local theme + "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \ + $2 \ + --last-exit-code=$3 \ + --last-pipe-status="$4" \ + --jobnum=$5 \ + --renderer-arg="client_id=$$" \ + --renderer-arg="local_theme=$6" +} + +_powerline_prompt() { + # Arguments: + # 1: side + # 2: last_exit_code + # 3: last_pipe_status + # 4: jobnum + "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \ + --width="${COLUMNS:-$(_powerline_columns_fallback)}" \ + -r.bash \ + --last-exit-code=$2 \ + --last-pipe-status="$3" \ + --jobnum=$4 \ + --renderer-arg="client_id=$$" +} + +_powerline_set_prompt() { + local last_exit_code=$1 ; shift + local last_pipe_status=$1 ; shift + local jobnum="$(jobs -p|wc -l)" + PS1="$(_powerline_prompt aboveleft $last_exit_code "$last_pipe_status" $jobnum)" + if test -n "$POWERLINE_SHELL_CONTINUATION$POWERLINE_BASH_CONTINUATION" ; then + PS2="$(_powerline_local_prompt left -r.bash $last_exit_code "$last_pipe_status" $jobnum continuation)" + fi + if test -n "$POWERLINE_SHELL_SELECT$POWERLINE_BASH_SELECT" ; then + PS3="$(_powerline_local_prompt left '' $last_exit_code "$last_pipe_status" $jobnum select)" + fi +} + +_powerline_setup_prompt() { + VIRTUAL_ENV_DISABLE_PROMPT=1 + if test -z "${POWERLINE_COMMAND}" ; then + POWERLINE_COMMAND="$("$POWERLINE_CONFIG_COMMAND" shell command)" + fi + test "$PROMPT_COMMAND" != "${PROMPT_COMMAND%_powerline_set_prompt*}" \ + || _powerline_add_status_wrapped_command prepend _powerline_set_prompt + PS2="$(_powerline_local_prompt left -r.bash 0 0 0 continuation)" + PS3="$(_powerline_local_prompt left '' 0 0 0 select)" +} + +if test -z "${POWERLINE_CONFIG_COMMAND}" ; then + if which powerline-config >/dev/null ; then + POWERLINE_CONFIG_COMMAND=powerline-config + else + POWERLINE_CONFIG_COMMAND="$(dirname "$BASH_SOURCE")/../../../scripts/powerline-config" + fi +fi + +if "${POWERLINE_CONFIG_COMMAND}" shell --shell=bash uses prompt ; then + _powerline_setup_prompt +fi +if "${POWERLINE_CONFIG_COMMAND}" shell --shell=bash uses tmux ; then + _powerline_init_tmux_support +fi diff --git a/powerline/bindings/config.py b/powerline/bindings/config.py new file mode 100644 index 0000000..3100633 --- /dev/null +++ b/powerline/bindings/config.py @@ -0,0 +1,286 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re +import sys +import subprocess +import shlex + +from powerline.config import POWERLINE_ROOT, TMUX_CONFIG_DIRECTORY +from powerline.lib.config import ConfigLoader +from powerline import generate_config_finder, load_config, create_logger, finish_common_config +from powerline.shell import ShellPowerline +from powerline.lib.shell import which +from powerline.bindings.tmux import (TmuxVersionInfo, run_tmux_command, set_tmux_environment, get_tmux_version, + source_tmux_file) +from powerline.lib.encoding import get_preferred_output_encoding +from powerline.renderers.tmux import attrs_to_tmux_attrs +from powerline.commands.main import finish_args + + +CONFIG_FILE_NAME = re.compile(r'powerline_tmux_(?P<major>\d+)\.(?P<minor>\d+)(?P<suffix>[a-z]+)?(?:_(?P<mod>plus|minus))?\.conf') +CONFIG_MATCHERS = { + None: (lambda a, b: a.major == b.major and a.minor == b.minor), + 'plus': (lambda a, b: a[:2] <= b[:2]), + 'minus': (lambda a, b: a[:2] >= b[:2]), +} +CONFIG_PRIORITY = { + None: 3, + 'plus': 2, + 'minus': 1, +} + + +def list_all_tmux_configs(): + '''List all version-specific tmux configuration files''' + for root, dirs, files in os.walk(TMUX_CONFIG_DIRECTORY): + dirs[:] = () + for fname in files: + match = CONFIG_FILE_NAME.match(fname) + if match: + assert match.group('suffix') is None + yield ( + os.path.join(root, fname), + CONFIG_MATCHERS[match.group('mod')], + CONFIG_PRIORITY[match.group('mod')], + TmuxVersionInfo( + int(match.group('major')), + int(match.group('minor')), + match.group('suffix'), + ), + ) + + +def get_tmux_configs(version): + '''Get tmux configuration suffix given parsed tmux version + + :param TmuxVersionInfo version: Parsed tmux version. + ''' + for fname, matcher, priority, file_version in list_all_tmux_configs(): + if matcher(file_version, version): + yield (fname, priority + file_version.minor * 10 + file_version.major * 10000) + + +def source_tmux_files(pl, args, tmux_version=None, source_tmux_file=source_tmux_file): + '''Source relevant version-specific tmux configuration files + + Files are sourced in the following order: + * First relevant files with older versions are sourced. + * If files for same versions are to be sourced then first _minus files are + sourced, then _plus files and then files without _minus or _plus suffixes. + ''' + tmux_version = tmux_version or get_tmux_version(pl) + source_tmux_file(os.path.join(TMUX_CONFIG_DIRECTORY, 'powerline-base.conf')) + for fname, priority in sorted(get_tmux_configs(tmux_version), key=(lambda v: v[1])): + source_tmux_file(fname) + if not os.environ.get('POWERLINE_COMMAND'): + cmd = deduce_command() + if cmd: + set_tmux_environment('POWERLINE_COMMAND', deduce_command(), remove=False) + try: + run_tmux_command('refresh-client') + except subprocess.CalledProcessError: + # On tmux-2.0 this command may fail for whatever reason. Since it is + # critical just ignore the failure. + pass + + +class EmptyArgs(object): + def __init__(self, ext, config_path): + self.ext = [ext] + self.side = 'left' + self.config_path = None + + def __getattr__(self, attr): + return None + + +def init_tmux_environment(pl, args, set_tmux_environment=set_tmux_environment): + '''Initialize tmux environment from tmux configuration + ''' + powerline = ShellPowerline(finish_args(None, os.environ, EmptyArgs('tmux', args.config_path))) + # TODO Move configuration files loading out of Powerline object and use it + # directly + powerline.update_renderer() + # FIXME Use something more stable then `theme_kwargs` + colorscheme = powerline.renderer_options['theme_kwargs']['colorscheme'] + + def get_highlighting(group): + return colorscheme.get_highlighting([group], None) + + for varname, highlight_group in ( + ('_POWERLINE_BACKGROUND_COLOR', 'background'), + ('_POWERLINE_ACTIVE_WINDOW_STATUS_COLOR', 'active_window_status'), + ('_POWERLINE_WINDOW_STATUS_COLOR', 'window_status'), + ('_POWERLINE_ACTIVITY_STATUS_COLOR', 'activity_status'), + ('_POWERLINE_BELL_STATUS_COLOR', 'bell_status'), + ('_POWERLINE_WINDOW_COLOR', 'window'), + ('_POWERLINE_WINDOW_DIVIDER_COLOR', 'window:divider'), + ('_POWERLINE_WINDOW_CURRENT_COLOR', 'window:current'), + ('_POWERLINE_WINDOW_NAME_COLOR', 'window_name'), + ('_POWERLINE_SESSION_COLOR', 'session'), + ): + highlight = get_highlighting(highlight_group) + set_tmux_environment(varname, powerline.renderer.hlstyle(**highlight)[2:-1]) + for varname, prev_group, next_group in ( + ('_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_COLOR', 'window', 'window:current'), + ('_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_NEXT_COLOR', 'window:current', 'window'), + ('_POWERLINE_SESSION_HARD_DIVIDER_NEXT_COLOR', 'session', 'background'), + ): + prev_highlight = get_highlighting(prev_group) + next_highlight = get_highlighting(next_group) + set_tmux_environment( + varname, + powerline.renderer.hlstyle( + fg=prev_highlight['bg'], + bg=next_highlight['bg'], + attrs=0, + )[2:-1] + ) + for varname, attr, group in ( + ('_POWERLINE_ACTIVE_WINDOW_FG', 'fg', 'active_window_status'), + ('_POWERLINE_WINDOW_STATUS_FG', 'fg', 'window_status'), + ('_POWERLINE_ACTIVITY_STATUS_FG', 'fg', 'activity_status'), + ('_POWERLINE_ACTIVITY_STATUS_ATTR', 'attrs', 'activity_status'), + ('_POWERLINE_BELL_STATUS_FG', 'fg', 'bell_status'), + ('_POWERLINE_BELL_STATUS_ATTR', 'attrs', 'bell_status'), + ('_POWERLINE_BACKGROUND_FG', 'fg', 'background'), + ('_POWERLINE_BACKGROUND_BG', 'bg', 'background'), + ('_POWERLINE_SESSION_FG', 'fg', 'session'), + ('_POWERLINE_SESSION_BG', 'bg', 'session'), + ('_POWERLINE_SESSION_ATTR', 'attrs', 'session'), + ('_POWERLINE_SESSION_PREFIX_FG', 'fg', 'session:prefix'), + ('_POWERLINE_SESSION_PREFIX_BG', 'bg', 'session:prefix'), + ('_POWERLINE_SESSION_PREFIX_ATTR', 'attrs', 'session:prefix'), + ): + if attr == 'attrs': + attrs = attrs_to_tmux_attrs(get_highlighting(group)[attr]) + set_tmux_environment(varname, ']#['.join(attrs)) + set_tmux_environment(varname + '_LEGACY', (','.join( + # Tmux-1.6 does not accept no… attributes in + # window-status-…-attr options. + (attr for attr in attrs if not attr.startswith('no'))) + # But it does not support empty attributes as well. + or 'none')) + else: + if powerline.common_config['term_truecolor']: + set_tmux_environment(varname, '#{0:06x}'.format(get_highlighting(group)[attr][1])) + else: + set_tmux_environment(varname, 'colour' + str(get_highlighting(group)[attr][0])) + + left_dividers = powerline.renderer.theme.dividers['left'] + set_tmux_environment('_POWERLINE_LEFT_HARD_DIVIDER', left_dividers['hard']) + set_tmux_environment('_POWERLINE_LEFT_SOFT_DIVIDER', left_dividers['soft']) + set_tmux_environment('_POWERLINE_LEFT_HARD_DIVIDER_SPACES', ( + ' ' * powerline.renderer.strwidth(left_dividers['hard']))) + + +TMUX_VAR_RE = re.compile('\$(_POWERLINE_\w+)') + + +def tmux_setup(pl, args): + tmux_environ = {} + tmux_version = get_tmux_version(pl) + + def set_tmux_environment_nosource(varname, value, remove=True): + tmux_environ[varname] = value + + def replace_cb(match): + return tmux_environ[match.group(1)] + + def replace_env(s): + return TMUX_VAR_RE.subn(replace_cb, s)[0] + + def source_tmux_file_nosource(fname): + with open(fname) as fd: + for line in fd: + if line.startswith('#') or line == '\n': + continue + args = shlex.split(line) + args = [args[0]] + [replace_env(arg) for arg in args[1:]] + run_tmux_command(*args) + + if args.source is None: + args.source = tmux_version < (1, 9) + + if args.source: + ste = set_tmux_environment + stf = source_tmux_file + else: + ste = set_tmux_environment_nosource + stf = source_tmux_file_nosource + + init_tmux_environment(pl, args, set_tmux_environment=ste) + source_tmux_files(pl, args, tmux_version=tmux_version, source_tmux_file=stf) + + +def get_main_config(args): + find_config_files = generate_config_finder() + config_loader = ConfigLoader(run_once=True) + return load_config('config', find_config_files, config_loader) + + +def create_powerline_logger(args): + config = get_main_config(args) + common_config = finish_common_config(get_preferred_output_encoding(), config['common']) + logger, pl, get_module_attr = create_logger(common_config) + return pl + + +def check_command(cmd): + if which(cmd): + return cmd + + +def deduce_command(): + '''Deduce which command to use for ``powerline`` + + Candidates: + + * ``powerline``. Present only when installed system-wide. + * ``{powerline_root}/scripts/powerline``. Present after ``pip install -e`` + was run and C client was compiled (in this case ``pip`` does not install + binary file). + * ``{powerline_root}/client/powerline.sh``. Useful when ``sh``, ``sed`` and + ``socat`` are present, but ``pip`` or ``setup.py`` was not run. + * ``{powerline_root}/client/powerline.py``. Like above, but when one of + ``sh``, ``sed`` and ``socat`` was not present. + * ``powerline-render``. Should not really ever be used. + * ``{powerline_root}/scripts/powerline-render``. Same. + ''' + return ( + None + or check_command('powerline') + or check_command(os.path.join(POWERLINE_ROOT, 'scripts', 'powerline')) + or ((which('sh') and which('sed') and which('socat')) + and check_command(os.path.join(POWERLINE_ROOT, 'client', 'powerline.sh'))) + or check_command(os.path.join(POWERLINE_ROOT, 'client', 'powerline.py')) + or check_command('powerline-render') + or check_command(os.path.join(POWERLINE_ROOT, 'scripts', 'powerline-render')) + ) + + +def shell_command(pl, args): + cmd = deduce_command() + if cmd: + print(cmd) + else: + sys.exit(1) + + +def uses(pl, args): + component = args.component + if not component: + raise ValueError('Must specify component') + shell = args.shell + template = 'POWERLINE_NO_{shell}_{component}' + for sh in (shell, 'shell') if shell else ('shell'): + varname = template.format(shell=sh.upper(), component=component.upper()) + if os.environ.get(varname): + sys.exit(1) + config = get_main_config(args) + if component in config.get('ext', {}).get('shell', {}).get('components', ('tmux', 'prompt')): + sys.exit(0) + else: + sys.exit(1) diff --git a/powerline/bindings/fish/powerline-setup.fish b/powerline/bindings/fish/powerline-setup.fish new file mode 100644 index 0000000..3887138 --- /dev/null +++ b/powerline/bindings/fish/powerline-setup.fish @@ -0,0 +1,109 @@ +function powerline-setup + function _powerline_columns_fallback + if which stty >/dev/null + if stty size >/dev/null + stty size | cut -d' ' -f2 + return 0 + end + end + echo 0 + return 0 + end + + function _powerline_columns + # Hack: `test "" -eq 0` is true, as well as `test 0 -eq 0` + # Note: at fish startup `$COLUMNS` is equal to zero, meaning that it may + # not be used. + if test "$COLUMNS" -eq 0 + _powerline_columns_fallback + else + echo "$COLUMNS" + end + end + + if test -z "$POWERLINE_CONFIG_COMMAND" + if which powerline-config >/dev/null + set -g POWERLINE_CONFIG_COMMAND powerline-config + else + set -g POWERLINE_CONFIG_COMMAND (dirname (status -f))/../../../scripts/powerline-config + end + end + + if env $POWERLINE_CONFIG_COMMAND shell --shell=fish uses prompt + if test -z "$POWERLINE_COMMAND" + set -g POWERLINE_COMMAND (env $POWERLINE_CONFIG_COMMAND shell command) + end + function _powerline_set_default_mode --on-variable fish_key_bindings + if test $fish_key_bindings != fish_vi_key_bindings + set -g _POWERLINE_DEFAULT_MODE default + else + set -g -e _POWERLINE_DEFAULT_MODE + end + end + function _powerline_update --on-variable POWERLINE_COMMAND + set -l addargs "--last-exit-code=\$status" + set -l addargs "$addargs --last-pipe-status=\$status" + set -l addargs "$addargs --jobnum=(jobs -p | wc -l)" + # One random value has an 1/32767 = 0.0031% probability of having + # the same value in two shells + set -l addargs "$addargs --renderer-arg=client_id="(random) + set -l addargs "$addargs --width=\$_POWERLINE_COLUMNS" + set -l addargs "$addargs --renderer-arg=mode=\$fish_bind_mode" + set -l addargs "$addargs --renderer-arg=default_mode=\$_POWERLINE_DEFAULT_MODE" + set -l promptside + set -l rpromptpast + set -l columnsexpr + if test -z "$POWERLINE_NO_FISH_ABOVE$POWERLINE_NO_SHELL_ABOVE" + set promptside aboveleft + set rpromptpast 'echo -n " "' + set columnsexpr '(math (_powerline_columns) - 1)' + else + set promptside left + set rpromptpast + set columnsexpr '(_powerline_columns)' + end + echo " + function fish_prompt + env \$POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS shell $promptside $addargs + end + function fish_right_prompt + env \$POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS shell right $addargs + $rpromptpast + end + function _powerline_set_columns --on-signal WINCH + set -g _POWERLINE_COLUMNS $columnsexpr + end + " | source + _powerline_set_columns + end + _powerline_set_default_mode + _powerline_update + end + if env $POWERLINE_CONFIG_COMMAND shell --shell=fish uses tmux + if test -n "$TMUX" + if tmux refresh -S ^/dev/null + set -g _POWERLINE_TMUX "$TMUX" + function _powerline_tmux_pane + if test -z "$TMUX_PANE" + env TMUX="$_POWERLINE_TMUX" tmux display -p "#D" | tr -d ' %' + else + echo "$TMUX_PANE" | tr -d ' %' + end + end + function _powerline_tmux_setenv + env TMUX="$_POWERLINE_TMUX" tmux setenv -g TMUX_$argv[1]_(_powerline_tmux_pane) "$argv[2]" + env TMUX="$_POWERLINE_TMUX" tmux refresh -S + end + function _powerline_tmux_set_pwd --on-variable PWD + _powerline_tmux_setenv PWD "$PWD" + end + function _powerline_tmux_set_columns --on-signal WINCH + _powerline_tmux_setenv COLUMNS (_powerline_columns) + end + _powerline_tmux_set_columns + _powerline_tmux_set_pwd + end + end + end +end +# vim: ft=fish diff --git a/powerline/bindings/i3/powerline-i3.py b/powerline/bindings/i3/powerline-i3.py new file mode 100755 index 0000000..f44e928 --- /dev/null +++ b/powerline/bindings/i3/powerline-i3.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import time + +from threading import Lock + +from powerline.bindings.wm import get_i3_connection, i3_subscribe + +from powerline import Powerline +from powerline.lib.monotonic import monotonic + + +class I3Powerline(Powerline): + '''Powerline child for i3bar + + Currently only changes the default log target. + ''' + default_log_stream = sys.stderr + + +if __name__ == '__main__': + name = 'wm' + if len(sys.argv) > 1: + name = sys.argv[1] + + powerline = I3Powerline(name, renderer_module='i3bar') + powerline.update_renderer() + + interval = 0.5 + + print ('{"version": 1}') + print ('[') + print ('[]') + + lock = Lock() + + def render(event=None, data=None, sub=None): + global lock + with lock: + print (',[' + powerline.render()[:-1] + ']') + sys.stdout.flush() + + i3 = get_i3_connection() + i3_subscribe(i3, 'workspace', render) + + while True: + start_time = monotonic() + render() + time.sleep(max(interval - (monotonic() - start_time), 0.1)) diff --git a/powerline/bindings/ipython/__init__.py b/powerline/bindings/ipython/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/bindings/ipython/__init__.py diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py new file mode 100644 index 0000000..6c1efb1 --- /dev/null +++ b/powerline/bindings/ipython/post_0_11.py @@ -0,0 +1,123 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from weakref import ref +from warnings import warn + +try: + from IPython.core.prompts import PromptManager + has_prompt_manager = True +except ImportError: + has_prompt_manager = False +from IPython.core.magic import Magics, magics_class, line_magic + +from powerline.ipython import IPythonPowerline, IPythonInfo + +if has_prompt_manager: + from powerline.ipython import RewriteResult + + +@magics_class +class PowerlineMagics(Magics): + def __init__(self, ip, powerline): + super(PowerlineMagics, self).__init__(ip) + self._powerline = powerline + + @line_magic + def powerline(self, line): + if line == 'reload': + self._powerline.reload() + else: + raise ValueError('Expected `reload`, but got {0}'.format(line)) + + +old_prompt_manager = None + + +class ShutdownHook(object): + def __init__(self, ip): + self.powerline = lambda: None + ip.hooks.shutdown_hook.add(self) + + def __call__(self): + from IPython.core.hooks import TryNext + powerline = self.powerline() + if powerline is not None: + powerline.shutdown() + raise TryNext() + + +if has_prompt_manager: + class PowerlinePromptManager(PromptManager): + def __init__(self, powerline, shell): + self.powerline = powerline + self.powerline_segment_info = IPythonInfo(shell) + self.shell = shell + + def render(self, name, color=True, *args, **kwargs): + res = self.powerline.render( + is_prompt=name.startswith('in'), + side='left', + output_width=True, + output_raw=not color, + matcher_info=name, + segment_info=self.powerline_segment_info, + ) + self.txtwidth = res[-1] + self.width = res[-1] + ret = res[0] if color else res[1] + if name == 'rewrite': + return RewriteResult(ret) + else: + return ret + + class ConfigurableIPythonPowerline(IPythonPowerline): + def init(self, ip): + config = ip.config.Powerline + self.config_overrides = config.get('config_overrides') + self.theme_overrides = config.get('theme_overrides', {}) + self.config_paths = config.get('config_paths') + if has_prompt_manager: + renderer_module = '.pre_5' + else: + renderer_module = '.since_5' + super(ConfigurableIPythonPowerline, self).init( + renderer_module=renderer_module) + + def do_setup(self, ip, shutdown_hook): + global old_prompt_manager + + if old_prompt_manager is None: + old_prompt_manager = ip.prompt_manager + prompt_manager = PowerlinePromptManager( + powerline=self, + shell=ip.prompt_manager.shell, + ) + ip.prompt_manager = prompt_manager + + magics = PowerlineMagics(ip, self) + shutdown_hook.powerline = ref(self) + ip.register_magics(magics) + + +def load_ipython_extension(ip): + if has_prompt_manager: + shutdown_hook = ShutdownHook(ip) + powerline = ConfigurableIPythonPowerline(ip) + powerline.setup(ip, shutdown_hook) + else: + from powerline.bindings.ipython.since_5 import PowerlinePrompts + ip.prompts_class = PowerlinePrompts + ip.prompts = PowerlinePrompts(ip) + warn(DeprecationWarning( + 'post_0_11 extension is deprecated since IPython 5, use\n' + ' from powerline.bindings.ipython.since_5 import PowerlinePrompts\n' + ' c.TerminalInteractiveShell.prompts_class = PowerlinePrompts\n' + )) + + +def unload_ipython_extension(ip): + global old_prompt_manager + if old_prompt_manager is not None: + ip.prompt_manager = old_prompt_manager + old_prompt_manager = None diff --git a/powerline/bindings/ipython/pre_0_11.py b/powerline/bindings/ipython/pre_0_11.py new file mode 100644 index 0000000..2bd8095 --- /dev/null +++ b/powerline/bindings/ipython/pre_0_11.py @@ -0,0 +1,146 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re + +from weakref import ref + +from IPython.Prompts import BasePrompt +from IPython.ipapi import get as get_ipython +from IPython.ipapi import TryNext + +from powerline.ipython import IPythonPowerline, RewriteResult +from powerline.lib.unicode import string + + +class IPythonInfo(object): + def __init__(self, cache): + self._cache = cache + + @property + def prompt_count(self): + return self._cache.prompt_count + + +class PowerlinePrompt(BasePrompt): + def __init__(self, powerline, powerline_last_in, old_prompt): + self.powerline = powerline + self.powerline_last_in = powerline_last_in + self.powerline_segment_info = IPythonInfo(old_prompt.cache) + self.cache = old_prompt.cache + if hasattr(old_prompt, 'sep'): + self.sep = old_prompt.sep + self.pad_left = False + + def __str__(self): + self.set_p_str() + return string(self.p_str) + + def set_p_str(self): + self.p_str, self.p_str_nocolor, self.powerline_prompt_width = ( + self.powerline.render( + is_prompt=self.powerline_is_prompt, + side='left', + output_raw=True, + output_width=True, + segment_info=self.powerline_segment_info, + matcher_info=self.powerline_prompt_type, + ) + ) + + @staticmethod + def set_colors(): + pass + + +class PowerlinePrompt1(PowerlinePrompt): + powerline_prompt_type = 'in' + powerline_is_prompt = True + rspace = re.compile(r'(\s*)$') + + def __str__(self): + self.cache.prompt_count += 1 + self.set_p_str() + self.cache.last_prompt = self.p_str_nocolor.split('\n')[-1] + return string(self.p_str) + + def set_p_str(self): + super(PowerlinePrompt1, self).set_p_str() + self.nrspaces = len(self.rspace.search(self.p_str_nocolor).group()) + self.powerline_last_in['nrspaces'] = self.nrspaces + + def auto_rewrite(self): + return RewriteResult(self.powerline.render( + is_prompt=False, + side='left', + matcher_info='rewrite', + segment_info=self.powerline_segment_info) + (' ' * self.nrspaces) + ) + + +class PowerlinePromptOut(PowerlinePrompt): + powerline_prompt_type = 'out' + powerline_is_prompt = False + + def set_p_str(self): + super(PowerlinePromptOut, self).set_p_str() + spaces = ' ' * self.powerline_last_in['nrspaces'] + self.p_str += spaces + self.p_str_nocolor += spaces + + +class PowerlinePrompt2(PowerlinePromptOut): + powerline_prompt_type = 'in2' + powerline_is_prompt = True + + +class ConfigurableIPythonPowerline(IPythonPowerline): + def init(self, config_overrides=None, theme_overrides={}, config_paths=None): + self.config_overrides = config_overrides + self.theme_overrides = theme_overrides + self.config_paths = config_paths + super(ConfigurableIPythonPowerline, self).init(renderer_module='.pre_5') + + def ipython_magic(self, ip, parameter_s=''): + if parameter_s == 'reload': + self.reload() + else: + raise ValueError('Expected `reload`, but got {0}'.format(parameter_s)) + + def do_setup(self, ip, shutdown_hook): + last_in = {'nrspaces': 0} + for attr, prompt_class in ( + ('prompt1', PowerlinePrompt1), + ('prompt2', PowerlinePrompt2), + ('prompt_out', PowerlinePromptOut) + ): + old_prompt = getattr(ip.IP.outputcache, attr) + prompt = prompt_class(self, last_in, old_prompt) + setattr(ip.IP.outputcache, attr, prompt) + ip.expose_magic('powerline', self.ipython_magic) + shutdown_hook.powerline = ref(self) + + +class ShutdownHook(object): + powerline = lambda: None + + def __call__(self): + from IPython.ipapi import TryNext + powerline = self.powerline() + if powerline is not None: + powerline.shutdown() + raise TryNext() + + +def setup(**kwargs): + ip = get_ipython() + + powerline = ConfigurableIPythonPowerline(**kwargs) + shutdown_hook = ShutdownHook() + + def late_startup_hook(): + powerline.setup(ip, shutdown_hook) + raise TryNext() + + ip.IP.hooks.late_startup_hook.add(late_startup_hook) + ip.IP.hooks.shutdown_hook.add(shutdown_hook) diff --git a/powerline/bindings/ipython/since_5.py b/powerline/bindings/ipython/since_5.py new file mode 100644 index 0000000..5a899ae --- /dev/null +++ b/powerline/bindings/ipython/since_5.py @@ -0,0 +1,81 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from weakref import ref + +from IPython.terminal.prompts import Prompts +from pygments.token import Token # NOQA + +from powerline.ipython import IPythonPowerline +from powerline.renderers.ipython.since_5 import PowerlinePromptStyle +from powerline.bindings.ipython.post_0_11 import PowerlineMagics, ShutdownHook + + +class ConfigurableIPythonPowerline(IPythonPowerline): + def init(self, ip): + config = ip.config.Powerline + self.config_overrides = config.get('config_overrides') + self.theme_overrides = config.get('theme_overrides', {}) + self.config_paths = config.get('config_paths') + super(ConfigurableIPythonPowerline, self).init( + renderer_module='.since_5') + + def do_setup(self, ip, prompts, shutdown_hook): + prompts.powerline = self + + msfn_missing = () + saved_msfn = getattr(ip, '_make_style_from_name', msfn_missing) + + if hasattr(saved_msfn, 'powerline_original'): + saved_msfn = saved_msfn.powerline_original + + def _make_style_from_name(ip, name): + prev_style = saved_msfn(name) + new_style = PowerlinePromptStyle(lambda: prev_style) + return new_style + + _make_style_from_name.powerline_original = saved_msfn + + if not isinstance(ip._style, PowerlinePromptStyle): + prev_style = ip._style + ip._style = PowerlinePromptStyle(lambda: prev_style) + + if not isinstance(saved_msfn, type(self.init)): + _saved_msfn = saved_msfn + saved_msfn = lambda: _saved_msfn(ip) + + if saved_msfn is not msfn_missing: + ip._make_style_from_name = _make_style_from_name + + magics = PowerlineMagics(ip, self) + ip.register_magics(magics) + + if shutdown_hook: + shutdown_hook.powerline = ref(self) + + +class PowerlinePrompts(Prompts): + '''Class that returns powerline prompts + ''' + def __init__(self, shell): + shutdown_hook = ShutdownHook(shell) + powerline = ConfigurableIPythonPowerline(shell) + self.shell = shell + powerline.do_setup(shell, self, shutdown_hook) + self.last_output_count = None + self.last_output = {} + + for prompt in ('in', 'continuation', 'rewrite', 'out'): + exec(( + 'def {0}_prompt_tokens(self, *args, **kwargs):\n' + ' if self.last_output_count != self.shell.execution_count:\n' + ' self.last_output.clear()\n' + ' self.last_output_count = self.shell.execution_count\n' + ' if "{0}" not in self.last_output:\n' + ' self.last_output["{0}"] = self.powerline.render(' + ' side="left",' + ' matcher_info="{1}",' + ' segment_info=self.shell,' + ' ) + [(Token.Generic.Prompt, " ")]\n' + ' return self.last_output["{0}"]' + ).format(prompt, 'in2' if prompt == 'continuation' else prompt)) diff --git a/powerline/bindings/lemonbar/powerline-lemonbar.py b/powerline/bindings/lemonbar/powerline-lemonbar.py new file mode 100755 index 0000000..7637254 --- /dev/null +++ b/powerline/bindings/lemonbar/powerline-lemonbar.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import time +import re +import subprocess + +from threading import Lock, Timer + +from powerline.lemonbar import LemonbarPowerline +from powerline.commands.lemonbar import get_argparser +from powerline.bindings.wm import get_connected_xrandr_outputs + + +if __name__ == '__main__': + parser = get_argparser() + args = parser.parse_args() + + powerline = LemonbarPowerline() + powerline.update_renderer() + bars = [] + + for screen in get_connected_xrandr_outputs(powerline.pl): + command = [args.bar_command, '-g', '{0}x{1}+{2}'.format(screen['width'], args.height, screen['x'])] + args.args[1:] + process = subprocess.Popen(command, stdin=subprocess.PIPE) + bars.append((screen['name'], process, int(screen['width']) / 5)) + + lock = Lock() + modes = ['default'] + + def render(reschedule=False): + if reschedule: + Timer(args.interval, render, kwargs={'reschedule': True}).start() + + global lock + with lock: + for output, process, width in bars: + process.stdin.write(powerline.render(mode=modes[0], width=width, matcher_info=output).encode('utf-8') + b'\n') + process.stdin.flush() + + def update(evt): + modes[0] = evt.change + render() + + render(reschedule=True) + + if args.i3: + try: + import i3ipc + except ImportError: + import i3 + i3.Subscription(lambda evt, data, sub: render(), 'workspace') + else: + conn = i3ipc.Connection() + conn.on('workspace::focus', lambda conn, evt: render()) + conn.on('mode', lambda conn, evt: update(evt)) + conn.main() + + while True: + time.sleep(1e8) diff --git a/powerline/bindings/pdb/__init__.py b/powerline/bindings/pdb/__init__.py new file mode 100644 index 0000000..4033e61 --- /dev/null +++ b/powerline/bindings/pdb/__init__.py @@ -0,0 +1,183 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import pdb + +from powerline.pdb import PDBPowerline +from powerline.lib.encoding import get_preferred_output_encoding +from powerline.lib.unicode import unicode + + +if sys.version_info < (3,): + # XXX The below classes make code compatible with PDBpp which uses pyrepl + # which does not expect unicode or something above ASCII. They are + # completely not needed if pdbpp is not used, but that’s not always the + # case. + class PowerlineRenderBytesResult(bytes): + def __new__(cls, s, encoding=None): + encoding = encoding or s.encoding + if isinstance(s, PowerlineRenderResult): + return s.encode(encoding) + self = bytes.__new__(cls, s.encode(encoding) if isinstance(s, unicode) else s) + self.encoding = encoding + return self + + for meth in ( + '__contains__', + 'partition', 'rpartition', + 'split', 'rsplit', + 'count', 'join', + ): + exec(( + 'def {0}(self, *args):\n' + ' if any((isinstance(arg, unicode) for arg in args)):\n' + ' return self.__unicode__().{0}(*args)\n' + ' else:\n' + ' return bytes.{0}(self, *args)' + ).format(meth)) + + for meth in ( + 'find', 'rfind', + 'index', 'rindex', + ): + exec(( + 'def {0}(self, *args):\n' + ' if any((isinstance(arg, unicode) for arg in args)):\n' + ' args = [arg.encode(self.encoding) if isinstance(arg, unicode) else arg for arg in args]\n' + ' return bytes.{0}(self, *args)' + ).format(meth)) + + def __len__(self): + return len(self.decode(self.encoding)) + + def __getitem__(self, *args): + return PowerlineRenderBytesResult(bytes.__getitem__(self, *args), encoding=self.encoding) + + def __getslice__(self, *args): + return PowerlineRenderBytesResult(bytes.__getslice__(self, *args), encoding=self.encoding) + + @staticmethod + def add(encoding, *args): + if any((isinstance(arg, unicode) for arg in args)): + return PowerlineRenderResult(''.join(( + arg + if isinstance(arg, unicode) + else arg.decode(encoding) + for arg in args + )), encoding) + else: + return PowerlineRenderBytesResult(b''.join(args), encoding=encoding) + + def __add__(self, other): + return self.add(self.encoding, self, other) + + def __radd__(self, other): + return self.add(self.encoding, other, self) + + def __unicode__(self): + return PowerlineRenderResult(self) + + class PowerlineRenderResult(unicode): + def __new__(cls, s, encoding=None): + encoding = ( + encoding + or getattr(s, 'encoding', None) + or get_preferred_output_encoding() + ) + if isinstance(s, unicode): + self = unicode.__new__(cls, s) + else: + self = unicode.__new__(cls, s, encoding, 'replace') + self.encoding = encoding + return self + + def __str__(self): + return PowerlineRenderBytesResult(self) + + def __getitem__(self, *args): + return PowerlineRenderResult(unicode.__getitem__(self, *args)) + + def __getslice__(self, *args): + return PowerlineRenderResult(unicode.__getslice__(self, *args)) + + @staticmethod + def add(encoding, *args): + return PowerlineRenderResult(''.join(( + arg + if isinstance(arg, unicode) + else arg.decode(encoding) + for arg in args + )), encoding) + + def __add__(self, other): + return self.add(self.encoding, self, other) + + def __radd__(self, other): + return self.add(self.encoding, other, self) + + def encode(self, *args, **kwargs): + return PowerlineRenderBytesResult(unicode.encode(self, *args, **kwargs), args[0]) +else: + PowerlineRenderResult = str + + +def use_powerline_prompt(cls): + '''Decorator that installs powerline prompt to the class + + :param pdb.Pdb cls: + Class that should be decorated. + + :return: + ``cls`` argument or a class derived from it. Latter is used to turn + old-style classes into new-style classes. + ''' + @property + def prompt(self): + try: + powerline = self.powerline + except AttributeError: + powerline = PDBPowerline() + powerline.setup(self) + self.powerline = powerline + return PowerlineRenderResult(powerline.render(side='left')) + + @prompt.setter + def prompt(self, _): + pass + + @prompt.deleter + def prompt(self): + pass + + if not hasattr(cls, '__class__'): + # Old-style class: make it new-style or @property will not work. + old_cls = cls + + class cls(cls, object): + __module__ = cls.__module__ + __doc__ = cls.__doc__ + + cls.__name__ = old_cls.__name__ + + cls.prompt = prompt + + return cls + + +def main(): + '''Run module as a script + + Uses :py:func:`pdb.main` function directly, but prior to that it mocks + :py:class:`pdb.Pdb` class with powerline-specific class instance. + ''' + orig_pdb = pdb.Pdb + + @use_powerline_prompt + class Pdb(pdb.Pdb, object): + def __init__(self): + orig_pdb.__init__(self) + + pdb.Pdb = Pdb + + return pdb.main() diff --git a/powerline/bindings/pdb/__main__.py b/powerline/bindings/pdb/__main__.py new file mode 100755 index 0000000..768b2f2 --- /dev/null +++ b/powerline/bindings/pdb/__main__.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.bindings.pdb import main + + +if __name__ == '__main__': + main() diff --git a/powerline/bindings/qtile/__init__.py b/powerline/bindings/qtile/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/bindings/qtile/__init__.py diff --git a/powerline/bindings/qtile/widget.py b/powerline/bindings/qtile/widget.py new file mode 100644 index 0000000..92e3a27 --- /dev/null +++ b/powerline/bindings/qtile/widget.py @@ -0,0 +1,61 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from libqtile.bar import CALCULATED +from libqtile.widget import TextBox + +from powerline import Powerline + + +class QTilePowerline(Powerline): + def do_setup(self, obj): + obj.powerline = self + + +class PowerlineTextBox(TextBox): + # TODO Replace timeout argument with update_interval argument in next major + # release. + def __init__(self, timeout=2, text=b' ', width=CALCULATED, side='right', update_interval=None, **config): + super(PowerlineTextBox, self).__init__(text, width, **config) + self.side = side + self.update_interval = update_interval or timeout + self.did_run_timer_setup = False + powerline = QTilePowerline(ext='wm', renderer_module='pango_markup') + powerline.setup(self) + + def update(self): + if not self.configured: + return True + self.text = self.powerline.render(side=self.side).encode('utf-8') + self.bar.draw() + return True + + def cmd_update(self, text): + self.update(text) + + def cmd_get(self): + return self.text + + def timer_setup(self): + if not self.did_run_timer_setup: + self.did_run_timer_setup = True + self.timeout_add(self.update_interval, self.update) + + def _configure(self, qtile, bar): + super(PowerlineTextBox, self)._configure(qtile, bar) + if self.layout.markup: + # QTile-0.9.1: no need to recreate layout or run timer_setup + return + self.layout = self.drawer.textlayout( + self.text, + self.foreground, + self.font, + self.fontsize, + self.fontshadow, + markup=True, + ) + self.timer_setup() + + +# TODO: Remove this at next major release +Powerline = PowerlineTextBox diff --git a/powerline/bindings/rc/powerline.rc b/powerline/bindings/rc/powerline.rc new file mode 100644 index 0000000..b2d6538 --- /dev/null +++ b/powerline/bindings/rc/powerline.rc @@ -0,0 +1,92 @@ +fn _powerline_sigwinch { + _POWERLINE_COLUMNS = `{ + stty size | cut -d' ' -f2 + } + _powerline_tmux_setenv COLUMNS $_POWERLINE_COLUMNS +} +fn _powerline_update_pwd { + _POWERLINE_NEW_PWD = `{pwd} + if (test $^_POWERLINE_NEW_PWD '=' $^_POWERLINE_SAVED_PWD) { + _POWERLINE_SAVED_PWD = $_POWERLINE_NEW_PWD + _powerline_tmux_setenv PWD $_POWERLINE_SAVED_PWD + } +} +fn _powerline_continuation_prompt { + _powerline_prompt --renderer-arg 'local_theme=continuation' $* +} +fn _powerline_prompt { + $POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS shell aboveleft -r.readline --last-pipe-status $^_POWERLINE_STATUS --last-exit-code $_POWERLINE_STATUS($#_POWERLINE_STATUS) --jobnum $_POWERLINE_JOBNUM --renderer-arg 'client_id='$pid $* +} +fn _powerline_set_prompt { + _POWERLINE_STATUS = ( $status ) + _POWERLINE_JOBNUM = $#apids + prompt = (``() { + _powerline_prompt + } ``() { + _powerline_continuation_prompt + }) + _powerline_update_pwd +} + +fn _powerline_common_setup { + fn sigwinch { + _powerline_sigwinch + } + _powerline_sigwinch + _POWERLINE_SAVED_PWD = '' +} + +fn _powerline_tmux_pane { + if (test -n $TMUX_PANE) { + echo $TMUX_PANE | tr -d ' %' + } else { + TMUX=$_POWERLINE_TMUX tmux display -p '#D' | tr -d ' %' + } +} + +fn _powerline_tmux_setenv { +} + +if (test -z $POWERLINE_CONFIG_COMMAND) { + if (which powerline-config >/dev/null) { + POWERLINE_CONFIG_COMMAND = powerline-config + } else { + echo powerline-config executable not found, unable to proceed >[2=1] + } +} +if (test -n $POWERLINE_CONFIG_COMMAND) { + if ($POWERLINE_CONFIG_COMMAND shell --shell rcsh uses prompt) { + if (test -n $POWERLINE_COMMAND_ARGS) { + # Perform splitting + POWERLINE_COMMAND_ARGS=( `{echo $POWERLINE_COMMAND_ARGS} ) + } + fn prompt { + _powerline_set_prompt + } + if (test -z $POWERLINE_SHELL_CONTINUATION$POWERLINE_RCSH_CONTINUATION) { + _POWERLINE_STATUS = 0 + _POWERLINE_JOBNUM = 0 + _POWERLINE_CONTINUATION = `{ + _powerline_continuation_prompt + } + fn _powerline_continuation_prompt { + echo -n $_POWERLINE_CONTINUATION + } + } + _powerline_common_setup + } + if (test -n $TMUX) { + if ($POWERLINE_CONFIG_COMMAND shell --shell rcsh uses tmux) { + _POWERLINE_TMUX=$TMUX + fn _powerline_tmux_setenv { + if (test -n $2) { + TMUX=$_POWERLINE_TMUX tmux setenv -g TMUX_$1^_`{ + _powerline_tmux_pane + } $2 + } + } + _powerline_common_setup + } + } +} +# vim: ft=rcshell diff --git a/powerline/bindings/shell/powerline.sh b/powerline/bindings/shell/powerline.sh new file mode 100644 index 0000000..15e13f2 --- /dev/null +++ b/powerline/bindings/shell/powerline.sh @@ -0,0 +1,239 @@ +_POWERLINE_SOURCED="$_" +_powerline_columns_fallback() { + if which stty >/dev/null ; then + # Ksh does not have “local” built-in + _powerline_cols="$(stty size 2>/dev/null)" + if ! test -z "$_powerline_cols" ; then + echo "${_powerline_cols#* }" + return 0 + fi + fi + echo 0 + return 0 +} + +_powerline_has_jobs_in_subshell() { + if test -n "$_POWERLINE_HAS_JOBS_IN_SUBSHELL" ; then + return $_POWERLINE_HAS_JOBS_IN_SUBSHELL + elif test -z "$1" ; then + sleep 1 & + # Check whether shell outputs anything in a subshell when using jobs + # built-in. Shells like dash will not output anything meaning that + # I have to bother with temporary files. + test "$(jobs -p|wc -l)" -gt 0 + else + case "$1" in + dash|bb|ash) return 1 ;; + mksh|ksh|bash) return 0 ;; + *) _powerline_has_jobs_in_subshell ;; + esac + fi + _POWERLINE_HAS_JOBS_IN_SUBSHELL=$? + return $_POWERLINE_HAS_JOBS_IN_SUBSHELL +} + +_powerline_set_append_trap() { + if _powerline_has_jobs_in_subshell "$@" ; then + _powerline_append_trap() { + # Arguments: command, signal + # Ksh does not have “local” built-in + _powerline_traps="$(trap)" + if echo "$_powerline_traps" | grep -cm1 $2'$' >/dev/null ; then + _powerline_traps="$(echo "$_powerline_traps" | sed "s/ $2/'\\n$1' $2/")" + eval "$_powerline_traps" + else + trap "$1" $2 + fi + } + else + _powerline_append_trap() { + # Arguments: command, signal + _powerline_create_temp + trap > $_POWERLINE_TEMP + if grep -cm1 $2'$' $_POWERLINE_TEMP >/dev/null ; then + sed -i -e "s/ $2/'\\n$1' $2/" + . $_POWERLINE_TEMP + else + trap "$1" $2 + fi + echo -n > $_POWERLINE_TEMP + } + fi + _powerline_set_append_trap() { + return 0 + } +} + +_powerline_create_temp() { + if test -z "$_POWERLINE_TEMP" || ! test -e "$_POWERLINE_TEMP" ; then + _POWERLINE_TEMP="$(mktemp "${TMPDIR:-/tmp}/powerline.XXXXXXXX")" + _powerline_append_trap 'rm $_POWERLINE_TEMP' EXIT + fi +} + +_powerline_set_set_jobs() { + if _powerline_has_jobs_in_subshell "$@" ; then + _powerline_set_jobs() { + _POWERLINE_JOBS="$(jobs -p|wc -l|tr -d ' ')" + } + else + _powerline_set_append_trap "$@" + _POWERLINE_PID=$$ + _powerline_append_trap '_powerline_do_set_jobs' USR1 + _powerline_do_set_jobs() { + _powerline_create_temp + jobs -p > $_POWERLINE_TEMP + } + # This command will always be launched from a subshell, thus a hack is + # needed to run `jobs -p` outside of the subshell. + _powerline_set_jobs() { + kill -USR1 $_POWERLINE_PID + # Note: most likely this will read data from the previous run. Tests + # show that it is OK for some reasons. + _POWERLINE_JOBS="$(wc -l < $_POWERLINE_TEMP | tr -d ' ')" + } + fi + _powerline_set_set_jobs() { + return 0 + } +} + +_powerline_set_command() { + if test -z "${POWERLINE_COMMAND}" ; then + POWERLINE_COMMAND="$("$POWERLINE_CONFIG_COMMAND" shell command)" + fi +} + +_powerline_tmux_pane() { + echo "${TMUX_PANE:-`TMUX="$_POWERLINE_TMUX" tmux display -p "#D"`}" | \ + tr -d ' %' +} + +_powerline_tmux_setenv() { + TMUX="$_POWERLINE_TMUX" tmux setenv -g TMUX_"$1"_`_powerline_tmux_pane` "$2" + TMUX="$_POWERLINE_TMUX" tmux refresh -S +} + +_powerline_tmux_set_pwd() { + if test "$_POWERLINE_SAVED_PWD" != "$PWD" ; then + _POWERLINE_SAVED_PWD="$PWD" + _powerline_tmux_setenv PWD "$PWD" + fi +} + +_powerline_tmux_set_columns() { + _powerline_tmux_setenv COLUMNS "${COLUMNS:-$(_powerline_columns_fallback)}" +} + +_powerline_set_renderer_arg() { + case "$1" in + bb|ash) _POWERLINE_RENDERER_ARG="-r .bash" ;; + mksh|ksh) _POWERLINE_RENDERER_ARG="-r .ksh" ;; + bash|dash) _POWERLINE_RENDERER_ARG= ;; + esac +} + +_powerline_set_jobs() { + _powerline_set_set_jobs + _powerline_set_jobs +} + +_powerline_local_prompt() { + # Arguments: side, exit_code, local theme + _powerline_set_jobs + "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \ + $_POWERLINE_RENDERER_ARG \ + --renderer-arg="client_id=$$" \ + --last-exit-code=$2 \ + --jobnum=$_POWERLINE_JOBS \ + --renderer-arg="local_theme=$3" +} + +_powerline_prompt() { + # Arguments: side, exit_code + _powerline_set_jobs + "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \ + --width="${COLUMNS:-$(_powerline_columns_fallback)}" \ + $_POWERLINE_RENDERER_ARG \ + --renderer-arg="client_id=$$" \ + --last-exit-code=$2 \ + --jobnum=$_POWERLINE_JOBS + _powerline_update_psN +} + +_powerline_setup_psN() { + case "$1" in + mksh|ksh|bash) + _POWERLINE_PID=$$ + _powerline_update_psN() { + kill -USR1 $_POWERLINE_PID + } + # No command substitution in PS2 and PS3 + _powerline_set_psN() { + if test -n "$POWERLINE_SHELL_CONTINUATION" ; then + PS2="$(_powerline_local_prompt left $? continuation)" + fi + if test -n "$POWERLINE_SHELL_SELECT" ; then + PS3="$(_powerline_local_prompt left $? select)" + fi + } + _powerline_append_trap '_powerline_set_psN' USR1 + _powerline_set_psN + ;; + bb|ash|dash) + _powerline_update_psN() { + # Do nothing + return + } + PS2='$(_powerline_local_prompt left $? continuation)' + # No select support + ;; + esac +} + +_powerline_setup_prompt() { + VIRTUAL_ENV_DISABLE_PROMPT=1 + _powerline_set_append_trap "$@" + _powerline_set_set_jobs "$@" + _powerline_set_command "$@" + _powerline_set_renderer_arg "$@" + PS1='$(_powerline_prompt aboveleft $?)' + PS2="$(_powerline_local_prompt left 0 continuation)" + PS3="$(_powerline_local_prompt left 0 select)" + _powerline_setup_psN "$@" +} + +_powerline_init_tmux_support() { + # Dash does not have &>/dev/null + if test -n "$TMUX" && tmux refresh -S >/dev/null 2>/dev/null ; then + # TMUX variable may be unset to create new tmux session inside this one + _POWERLINE_TMUX="$TMUX" + + _powerline_set_append_trap "$@" + + # If _powerline_tmux_set_pwd is used before _powerline_prompt it sets $? + # to zero in ksh. + PS1="$PS1"'$(_powerline_tmux_set_pwd)' + _powerline_append_trap '_powerline_tmux_set_columns' WINCH + _powerline_tmux_set_columns + fi +} + +if test -z "${POWERLINE_CONFIG_COMMAND}" ; then + if which powerline-config >/dev/null ; then + POWERLINE_CONFIG_COMMAND=powerline-config + else + POWERLINE_CONFIG_COMMAND="$(dirname "$_POWERLINE_SOURCED")/../../../scripts/powerline-config" + fi +fi + +# Strips the leading `-`: it may be present when shell is a login shell +_POWERLINE_USED_SHELL=${0#-} +_POWERLINE_USED_SHELL=${_POWERLINE_USED_SHELL##*/} + +if "${POWERLINE_CONFIG_COMMAND}" shell uses tmux ; then + _powerline_init_tmux_support $_POWERLINE_USED_SHELL +fi +if "${POWERLINE_CONFIG_COMMAND}" shell --shell=bash uses prompt ; then + _powerline_setup_prompt $_POWERLINE_USED_SHELL +fi diff --git a/powerline/bindings/tcsh/powerline.tcsh b/powerline/bindings/tcsh/powerline.tcsh new file mode 100644 index 0000000..4897b4c --- /dev/null +++ b/powerline/bindings/tcsh/powerline.tcsh @@ -0,0 +1,60 @@ +# http://unix.stackexchange.com/questions/4650/determining-path-to-sourced-shell-script: +# > In tcsh, $_ at the beginning of the script will contain the location if the +# > file was sourced and $0 contains it if it was run. +# +# Guess this relies on `$_` being set as to last argument to previous command +# which must be `.` or `source` in this case +set POWERLINE_SOURCED=($_) +if ! $?POWERLINE_CONFIG_COMMAND then + if ( { which powerline-config > /dev/null } ) then + set POWERLINE_CONFIG_COMMAND="powerline-config" + else + set POWERLINE_CONFIG_COMMAND="$POWERLINE_SOURCED[2]:h:h:h:h/scripts/powerline-config" + endif +else + if "$POWERLINE_CONFIG_COMMAND" == "" then + if ( { which powerline-config > /dev/null } ) then + set POWERLINE_CONFIG_COMMAND="powerline-config" + else + set POWERLINE_CONFIG_COMMAND="$POWERLINE_SOURCED[2]:h:h:h:h/scripts/powerline-config" + endif + endif +endif +if ( { $POWERLINE_CONFIG_COMMAND shell --shell=tcsh uses tmux } ) then + if ( $?TMUX_PANE ) then + if ( "$TMUX_PANE" == "" ) then + set _POWERLINE_TMUX_PANE="`tmux display -p '#D'`" + else + set _POWERLINE_TMUX_PANE="$TMUX_PANE" + endif + else + set _POWERLINE_TMUX_PANE="`tmux display -p '#D'`" + endif + set _POWERLINE_TMUX_PANE="`echo $_POWERLINE_TMUX_PANE:q | tr -d '% '`" + alias _powerline_tmux_set_pwd 'if ( $?TMUX && { tmux refresh -S >&/dev/null } ) tmux setenv -g TMUX_PWD_$_POWERLINE_TMUX_PANE $PWD:q ; if ( $?TMUX ) tmux refresh -S >&/dev/null' + alias cwdcmd "`alias cwdcmd` ; _powerline_tmux_set_pwd" +endif +if ( { $POWERLINE_CONFIG_COMMAND shell --shell=tcsh uses prompt } ) then + if ! $?POWERLINE_COMMAND then + set POWERLINE_COMMAND="`$POWERLINE_CONFIG_COMMAND:q shell command`" + else + if "$POWERLINE_COMMAND" == "" then + set POWERLINE_COMMAND="`$POWERLINE_CONFIG_COMMAND:q shell command`" + endif + endif + if ! $?POWERLINE_COMMAND_ARGS then + set POWERLINE_COMMAND_ARGS="" + endif + + if ( $?POWERLINE_NO_TCSH_ABOVE || $?POWERLINE_NO_SHELL_ABOVE ) then + alias _powerline_above true + else + alias _powerline_above '$POWERLINE_COMMAND:q $POWERLINE_COMMAND_ARGS shell above --renderer-arg=client_id=$$ --last-exit-code=$POWERLINE_STATUS --width=$POWERLINE_COLUMNS' + endif + + alias _powerline_set_prompt 'set prompt="`$POWERLINE_COMMAND:q $POWERLINE_COMMAND_ARGS shell left -r .tcsh --renderer-arg=client_id=$$ --last-exit-code=$POWERLINE_STATUS --width=$POWERLINE_COLUMNS`"' + alias _powerline_set_rprompt 'set rprompt="`$POWERLINE_COMMAND:q $POWERLINE_COMMAND_ARGS shell right -r .tcsh --renderer-arg=client_id=$$ --last-exit-code=$POWERLINE_STATUS --width=$POWERLINE_COLUMNS`"' + alias _powerline_set_columns 'set POWERLINE_COLUMNS=`stty size|cut -d" " -f2` ; set POWERLINE_COLUMNS=`expr $POWERLINE_COLUMNS - 2`' + + alias precmd 'set POWERLINE_STATUS=$? ; '"`alias precmd`"' ; _powerline_set_columns ; _powerline_above ; _powerline_set_prompt ; _powerline_set_rprompt' +endif diff --git a/powerline/bindings/tmux/__init__.py b/powerline/bindings/tmux/__init__.py new file mode 100644 index 0000000..011cd68 --- /dev/null +++ b/powerline/bindings/tmux/__init__.py @@ -0,0 +1,84 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re +import os +import subprocess + +from collections import namedtuple + +from powerline.lib.shell import run_cmd + + +TmuxVersionInfo = namedtuple('TmuxVersionInfo', ('major', 'minor', 'suffix')) + + +def get_tmux_executable_name(): + '''Returns tmux executable name + + It should be defined in POWERLINE_TMUX_EXE environment variable, otherwise + it is simply “tmux”. + ''' + + return os.environ.get('POWERLINE_TMUX_EXE', 'tmux') + + +def _run_tmux(runner, args): + return runner([get_tmux_executable_name()] + list(args)) + + +def run_tmux_command(*args): + '''Run tmux command, ignoring the output''' + _run_tmux(subprocess.check_call, args) + + +def get_tmux_output(pl, *args): + '''Run tmux command and return its output''' + return _run_tmux(lambda cmd: run_cmd(pl, cmd), args) + + +def set_tmux_environment(varname, value, remove=True): + '''Set tmux global environment variable + + :param str varname: + Name of the variable to set. + :param str value: + Variable value. + :param bool remove: + True if variable should be removed from the environment prior to + attaching any client (runs ``tmux set-environment -r {varname}``). + ''' + run_tmux_command('set-environment', '-g', varname, value) + if remove: + try: + run_tmux_command('set-environment', '-r', varname) + except subprocess.CalledProcessError: + # On tmux-2.0 this command may fail for whatever reason. Since it is + # critical just ignore the failure. + pass + + +def source_tmux_file(fname): + '''Source tmux configuration file + + :param str fname: + Full path to the sourced file. + ''' + run_tmux_command('source', fname) + + +NON_DIGITS = re.compile('[^0-9]+') +DIGITS = re.compile('[0-9]+') +NON_LETTERS = re.compile('[^a-z]+') + + +def get_tmux_version(pl): + version_string = get_tmux_output(pl, '-V') + _, version_string = version_string.split(' ') + version_string = version_string.strip() + if version_string == 'master': + return TmuxVersionInfo(float('inf'), 0, version_string) + major, minor = version_string.split('.') + suffix = DIGITS.subn('', minor)[0] or None + minor = NON_DIGITS.subn('', minor)[0] + return TmuxVersionInfo(int(major), int(minor), suffix) diff --git a/powerline/bindings/tmux/powerline-base.conf b/powerline/bindings/tmux/powerline-base.conf new file mode 100644 index 0000000..ca0bbf5 --- /dev/null +++ b/powerline/bindings/tmux/powerline-base.conf @@ -0,0 +1,11 @@ +set -g status on +set -g status-interval 2 +set -g status-left-length 20 +set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id=\"`tmux display -p "#""D"`\")' +set -g status-right-length 150 +set -g window-status-format "#[$_POWERLINE_WINDOW_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER_SPACES#I#F #[$_POWERLINE_WINDOW_DIVIDER_COLOR]$_POWERLINE_LEFT_SOFT_DIVIDER#[default]#W $_POWERLINE_LEFT_HARD_DIVIDER_SPACES" +set -g window-status-current-format "#[$_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER#[$_POWERLINE_WINDOW_CURRENT_COLOR]#I#F $_POWERLINE_LEFT_SOFT_DIVIDER#[$_POWERLINE_WINDOW_NAME_COLOR]#W #[$_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_NEXT_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER" + +# Legacy status-left definition to be overwritten for tmux Versions 1.8+ +set -g status-left "#[$_POWERLINE_SESSION_COLOR] #S #[$_POWERLINE_SESSION_HARD_DIVIDER_NEXT_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER#(env \"\$POWERLINE_COMMAND\" tmux left -R pane_id=\"`tmux display -p '#''D'`\")" +# vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline.conf b/powerline/bindings/tmux/powerline.conf new file mode 100644 index 0000000..29ec6a4 --- /dev/null +++ b/powerline/bindings/tmux/powerline.conf @@ -0,0 +1,2 @@ +if-shell 'env "$POWERLINE_CONFIG_COMMAND" tmux setup' '' 'run-shell "powerline-config tmux setup"' +# vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline_tmux_1.7_plus.conf b/powerline/bindings/tmux/powerline_tmux_1.7_plus.conf new file mode 100644 index 0000000..ec225c0 --- /dev/null +++ b/powerline/bindings/tmux/powerline_tmux_1.7_plus.conf @@ -0,0 +1,3 @@ +set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id="`tmux display -p "#""D"`" --width=`tmux display -p "#""{client_width}"` -R width_adjust=`tmux show-options -g status-left-length | cut -d" " -f 2`)' +set -g status-left "#[$_POWERLINE_SESSION_COLOR] #S #[$_POWERLINE_SESSION_HARD_DIVIDER_NEXT_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER#(env \"\$POWERLINE_COMMAND\" tmux left --width=`tmux display -p '#''{client_width}'` -R width_adjust=`tmux show-options -g status-right-length | cut -d' ' -f2` -R pane_id=\"`tmux display -p '#''D'`\")" +# vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline_tmux_1.8.conf b/powerline/bindings/tmux/powerline_tmux_1.8.conf new file mode 100644 index 0000000..fbcd2a5 --- /dev/null +++ b/powerline/bindings/tmux/powerline_tmux_1.8.conf @@ -0,0 +1,5 @@ +# powerline_tmux_1.8.conf +# tmux Version 1.8 introduces window-status-last-{attr,bg,fg}, which is +# deprecated for versions 1.9+, thus only applicable to version 1.8. +set -qg window-status-last-fg "$_POWERLINE_ACTIVE_WINDOW_FG" +# vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline_tmux_1.8_minus.conf b/powerline/bindings/tmux/powerline_tmux_1.8_minus.conf new file mode 100644 index 0000000..284eee0 --- /dev/null +++ b/powerline/bindings/tmux/powerline_tmux_1.8_minus.conf @@ -0,0 +1,11 @@ +# powerline_tmux_legacy_common.conf +# tmux Version 1.8 and earlier (legacy) common options. The foo-{attr,bg,fg} +# options are deprecated starting with tmux Version 1.9. +set -g status-fg "$_POWERLINE_BACKGROUND_FG" +set -g status-bg "$_POWERLINE_BACKGROUND_BG" +set-window-option -g window-status-fg "$_POWERLINE_WINDOW_STATUS_FG" +set-window-option -g window-status-activity-attr "$_POWERLINE_ACTIVITY_STATUS_ATTR_LEGACY" +set-window-option -g window-status-bell-attr "$_POWERLINE_BELL_STATUS_ATTR_LEGACY" +set-window-option -g window-status-activity-fg "$_POWERLINE_ACTIVITY_STATUS_FG" +set-window-option -g window-status-bell-fg "$_POWERLINE_BELL_STATUS_FG" +# vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline_tmux_1.8_plus.conf b/powerline/bindings/tmux/powerline_tmux_1.8_plus.conf new file mode 100644 index 0000000..4edcf21 --- /dev/null +++ b/powerline/bindings/tmux/powerline_tmux_1.8_plus.conf @@ -0,0 +1,5 @@ +# powerline_tmux_1.8_plus.conf +# tmux Version 1.8 introduces the 'client_prefix' format variable, applicable +# for versions 1.8+ +set -qg status-left "#{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_FG]#[bg=$_POWERLINE_SESSION_PREFIX_BG]#[$_POWERLINE_SESSION_PREFIX_ATTR],#[fg=$_POWERLINE_SESSION_FG]#[bg=$_POWERLINE_SESSION_BG]#[$_POWERLINE_SESSION_ATTR]} #S #{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_BG],#[fg=$_POWERLINE_SESSION_BG]}#[bg=$_POWERLINE_BACKGROUND_BG]#[nobold]$_POWERLINE_LEFT_HARD_DIVIDER#(env \$POWERLINE_COMMAND \$POWERLINE_COMMAND_ARGS tmux left --width=`tmux display -p '#''{client_width}'` -R width_adjust=`tmux show-options -g status-right-length | cut -d' ' -f2` -R pane_id=\"`tmux display -p '#''D'`\")" +# vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf b/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf new file mode 100644 index 0000000..7ab9a8b --- /dev/null +++ b/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf @@ -0,0 +1,8 @@ +# powerline_tmux_1.9_plus.conf +# Version 1.9 introduces the foo-style options, applicable to version 1.9+ +set-option -qg status-style "$_POWERLINE_BACKGROUND_COLOR" +set-option -qg window-status-last-style "$_POWERLINE_ACTIVE_WINDOW_STATUS_COLOR" +set-window-option -qg window-status-style "$_POWERLINE_WINDOW_STATUS_COLOR" +set-window-option -qg window-status-activity-style "$_POWERLINE_ACTIVITY_STATUS_COLOR" +set-window-option -qg window-status-bell-style "$_POWERLINE_BELL_STATUS_COLOR" +# vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf new file mode 100644 index 0000000..8bf8872 --- /dev/null +++ b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf @@ -0,0 +1,3 @@ +# Starting from tmux-2.1 escaping of dollar signs inside #() is harmful +set -qg status-left "#{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_FG]#[bg=$_POWERLINE_SESSION_PREFIX_BG]#[$_POWERLINE_SESSION_PREFIX_ATTR],#[fg=$_POWERLINE_SESSION_FG]#[bg=$_POWERLINE_SESSION_BG]#[$_POWERLINE_SESSION_ATTR]} #S #{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_BG],#[fg=$_POWERLINE_SESSION_BG]}#[bg=$_POWERLINE_BACKGROUND_BG]#[nobold]$_POWERLINE_LEFT_HARD_DIVIDER#(env $POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS tmux left --width=`tmux display -p '#''{client_width}'` -R width_adjust=`tmux show-options -g status-right-length | cut -d' ' -f2` -R pane_id=\"`tmux display -p '#''D'`\")" +set -g window-status-format "#[$_POWERLINE_WINDOW_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER_SPACES#I#{?window_flags,#F, } #[$_POWERLINE_WINDOW_DIVIDER_COLOR]$_POWERLINE_LEFT_SOFT_DIVIDER#[default]#W $_POWERLINE_LEFT_HARD_DIVIDER_SPACES" diff --git a/powerline/bindings/vim/__init__.py b/powerline/bindings/vim/__init__.py new file mode 100644 index 0000000..b754c1f --- /dev/null +++ b/powerline/bindings/vim/__init__.py @@ -0,0 +1,482 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import codecs + +try: + import vim +except ImportError: + vim = object() + +from powerline.lib.unicode import unicode + + +if ( + hasattr(vim, 'options') + and hasattr(vim, 'vvars') + and vim.vvars['version'] > 703 +): + if sys.version_info < (3,): + def get_vim_encoding(): + return vim.options['encoding'] or 'ascii' + else: + def get_vim_encoding(): + return vim.options['encoding'].decode('ascii') or 'ascii' +elif hasattr(vim, 'eval'): + def get_vim_encoding(): + return vim.eval('&encoding') or 'ascii' +else: + def get_vim_encoding(): + return 'utf-8' + +get_vim_encoding.__doc__ = ( + '''Get encoding used for Vim strings + + :return: + Value of ``&encoding``. If it is empty (i.e. Vim is compiled + without +multibyte) returns ``'ascii'``. When building documentation + outputs ``'utf-8'`` unconditionally. + ''' +) + + +vim_encoding = get_vim_encoding() + + +python_to_vim_types = { + unicode: ( + lambda o: b'\'' + (o.translate({ + ord('\''): '\'\'', + }).encode(vim_encoding)) + b'\'' + ), + list: ( + lambda o: b'[' + ( + b','.join((python_to_vim(i) for i in o)) + ) + 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'): + 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): + '''Return a vim function binding.''' + try: + func = vim.bindeval('function("' + f + '")') + except vim.error: + return None + else: + return rettype_func[rettype](func) +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): + '''Evaluate a vim function using vim.eval(). + + This is a fallback class for older vim versions. + ''' + __slots__ = ('f', 'eval') + + def __init__(self, f, rettype=None): + self.f = f.encode('utf-8') + self.eval = rettype_eval[rettype] + + def __call__(self, *args): + return self.eval(self.f + b'(' + (b','.join(( + python_to_vim(o) for o in args + ))) + b')') + + vim_get_func = VimFunc + + +def vim_get_autoload_func(f, rettype=None): + func = vim_get_func(f) + if not func: + vim.command('runtime! ' + f.replace('#', '/')[:f.rindex('#')] + '.vim') + func = vim_get_func(f) + return func + + +if hasattr(vim, 'Function'): + def vim_func_exists(f): + try: + vim.Function(f) + except ValueError: + return False + else: + return True +else: + def vim_func_exists(f): + try: + return bool(int(vim.eval('exists("*{0}")'.format(f)))) + except vim.error: + return False + + +if type(vim) is object: + vim_get_func = lambda *args, **kwargs: None + + +_getbufvar = vim_get_func('getbufvar') +_vim_exists = vim_get_func('exists', rettype='int') + + +# It may crash on some old vim versions and I do not remember in which patch +# I fixed this crash. +if hasattr(vim, 'vvars') and vim.vvars[str('version')] > 703: + _vim_to_python_types = { + getattr(vim, 'Dictionary', None) or type(vim.bindeval('{}')): + 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('[]')): + lambda value: [_vim_to_python(item) for item in value], + getattr(vim, 'Function', None) or type(vim.bindeval('function("mode")')): + lambda _: None, + } + + def vim_getvar(varname): + return _vim_to_python(vim.vars[str(varname)]) + + def bufvar_exists(buffer, varname): + buffer = buffer or vim.current.buffer + return varname in buffer.vars + + def vim_getwinvar(segment_info, varname): + return _vim_to_python(segment_info['window'].vars[str(varname)]) + + def vim_global_exists(name): + try: + vim.vars[name] + except KeyError: + return False + else: + return True +else: + _vim_to_python_types = { + dict: (lambda value: dict(((k, _vim_to_python(v)) for k, v in value.items()))), + list: (lambda value: [_vim_to_python(i) for i in value]), + } + + def vim_getvar(varname): + varname = 'g:' + varname + if _vim_exists(varname): + return vim.eval(varname) + else: + raise KeyError(varname) + + def bufvar_exists(buffer, varname): + if not buffer or buffer.number == vim.current.buffer.number: + return int(vim.eval('exists("b:{0}")'.format(varname))) + else: + return int(vim.eval( + 'has_key(getbufvar({0}, ""), {1})'.format(buffer.number, varname) + )) + + def vim_getwinvar(segment_info, varname): + result = vim.eval('getwinvar({0}, "{1}")'.format(segment_info['winnr'], varname)) + if result == '': + if not int(vim.eval('has_key(getwinvar({0}, ""), "{1}")'.format(segment_info['winnr'], varname))): + raise KeyError(varname) + return result + + def vim_global_exists(name): + return int(vim.eval('exists("g:' + name + '")')) + + +def vim_command_exists(name): + return _vim_exists(':' + name) + + +if sys.version_info < (3,): + getbufvar = _getbufvar +else: + _vim_to_python_types[bytes] = lambda value: value.decode(vim_encoding) + + def getbufvar(*args): + return _vim_to_python(_getbufvar(*args)) + + +_id = lambda value: value + + +def _vim_to_python(value): + return _vim_to_python_types.get(type(value), _id)(value) + + +if hasattr(vim, 'options'): + def vim_getbufoption(info, option): + return _vim_to_python(info['buffer'].options[str(option)]) + + def vim_getoption(option): + return vim.options[str(option)] + + def vim_setoption(option, value): + vim.options[str(option)] = value +else: + def vim_getbufoption(info, option): + return getbufvar(info['bufnr'], '&' + option) + + def vim_getoption(option): + return vim.eval('&g:' + option) + + def vim_setoption(option, value): + vim.command('let &g:{option} = {value}'.format( + option=option, value=python_to_vim(value))) + + +if hasattr(vim, 'tabpages'): + current_tabpage = lambda: vim.current.tabpage + list_tabpages = lambda: vim.tabpages + + def list_tabpage_buffers_segment_info(segment_info): + return ( + {'buffer': window.buffer, 'bufnr': window.buffer.number} + for window in segment_info['tabpage'].windows + ) +else: + class FalseObject(object): + @staticmethod + def __nonzero__(): + return False + + __bool__ = __nonzero__ + + def get_buffer(number): + for buffer in vim.buffers: + if buffer.number == number: + return buffer + raise KeyError(number) + + class WindowVars(object): + __slots__ = ('tabnr', 'winnr') + + def __init__(self, window): + self.tabnr = window.tabnr + self.winnr = window.number + + def __getitem__(self, key): + has_key = vim.eval('has_key(gettabwinvar({0}, {1}, ""), "{2}")'.format(self.tabnr, self.winnr, key)) + if has_key == '0': + raise KeyError + return vim.eval('gettabwinvar({0}, {1}, "{2}")'.format(self.tabnr, self.winnr, key)) + + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + + class Window(FalseObject): + __slots__ = ('tabnr', 'number', '_vars') + + def __init__(self, tabnr, number): + self.tabnr = tabnr + self.number = number + self.vars = WindowVars(self) + + @property + def buffer(self): + return get_buffer(int(vim.eval('tabpagebuflist({0})[{1}]'.format(self.tabnr, self.number - 1)))) + + class Tabpage(FalseObject): + __slots__ = ('number',) + + def __init__(self, number): + self.number = number + + def __eq__(self, tabpage): + if not isinstance(tabpage, Tabpage): + raise NotImplementedError + return self.number == tabpage.number + + @property + def window(self): + return Window(self.number, int(vim.eval('tabpagewinnr({0})'.format(self.number)))) + + def _last_tab_nr(): + return int(vim.eval('tabpagenr("$")')) + + def current_tabpage(): + return Tabpage(int(vim.eval('tabpagenr()'))) + + def list_tabpages(): + return [Tabpage(nr) for nr in range(1, _last_tab_nr() + 1)] + + class TabBufSegmentInfo(dict): + def __getitem__(self, key): + try: + return super(TabBufSegmentInfo, self).__getitem__(key) + except KeyError: + if key != 'buffer': + raise + else: + buffer = get_buffer(super(TabBufSegmentInfo, self).__getitem__('bufnr')) + self['buffer'] = buffer + return buffer + + def list_tabpage_buffers_segment_info(segment_info): + return ( + TabBufSegmentInfo(bufnr=int(bufnrstr)) + for bufnrstr in vim.eval('tabpagebuflist({0})'.format(segment_info['tabnr'])) + ) + + +class VimEnviron(object): + @staticmethod + def __getitem__(key): + return vim.eval('$' + key) + + @staticmethod + def get(key, default=None): + return vim.eval('$' + key) or default + + @staticmethod + def __setitem__(key, value): + return vim.command( + 'let ${0}="{1}"'.format( + key, + value.replace('"', '\\"') + .replace('\\', '\\\\') + .replace('\n', '\\n') + .replace('\0', '') + ) + ) + + +if sys.version_info < (3,): + def buffer_name(segment_info): + return segment_info['buffer'].name +else: + vim_bufname = vim_get_func('bufname', rettype='bytes') + + def buffer_name(segment_info): + try: + name = segment_info['buffer'].name + except UnicodeDecodeError: + return vim_bufname(segment_info['bufnr']) + else: + return name.encode(segment_info['encoding']) if name else None + + +vim_strtrans = vim_get_func('strtrans', rettype='unicode') + + +def powerline_vim_strtrans_error(e): + if not isinstance(e, UnicodeDecodeError): + raise NotImplementedError + text = vim_strtrans(e.object[e.start:e.end]) + return (text, e.end) + + +codecs.register_error('powerline_vim_strtrans_error', powerline_vim_strtrans_error) + + +did_autocmd = False +buffer_caches = [] + + +def register_buffer_cache(cachedict): + global did_autocmd + global buffer_caches + from powerline.vim import get_default_pycmd, pycmd + if not did_autocmd: + import __main__ + __main__.powerline_on_bwipe = on_bwipe + vim.command('augroup Powerline') + vim.command(' autocmd! BufWipeout * :{pycmd} powerline_on_bwipe()'.format( + pycmd=(pycmd or get_default_pycmd()))) + vim.command('augroup END') + did_autocmd = True + buffer_caches.append(cachedict) + return cachedict + + +def on_bwipe(): + global buffer_caches + bufnr = int(vim.eval('expand("<abuf>")')) + for cachedict in buffer_caches: + cachedict.pop(bufnr, None) + + +environ = VimEnviron() + + +def create_ruby_dpowerline(): + vim.command(( + ''' + ruby + if $powerline == nil + class Powerline + end + $powerline = Powerline.new + end + ''' + )) diff --git a/powerline/bindings/vim/autoload/powerline/debug.vim b/powerline/bindings/vim/autoload/powerline/debug.vim new file mode 100644 index 0000000..244319a --- /dev/null +++ b/powerline/bindings/vim/autoload/powerline/debug.vim @@ -0,0 +1,20 @@ +python import cProfile +python powerline_pr = cProfile.Profile() + +function powerline#debug#profile_pyeval(s) + python powerline_pr.enable() + try + let ret = pyeval(a:s) + finally + python powerline_pr.disable() + endtry + return ret +endfunction + +function powerline#debug#write_profile(fname) + python import vim + python powerline_pr.dump_stats(vim.eval('a:fname')) + python powerline_pr = cProfile.Profile() +endfunction + +command -nargs=1 -complete=file WriteProfiling :call powerline#debug#write_profile(<q-args>) diff --git a/powerline/bindings/vim/plugin/powerline.vim b/powerline/bindings/vim/plugin/powerline.vim new file mode 100644 index 0000000..11ec05e --- /dev/null +++ b/powerline/bindings/vim/plugin/powerline.vim @@ -0,0 +1,169 @@ +if exists('g:powerline_loaded') + finish +endif +let g:powerline_loaded = 1 + +if exists('g:powerline_pycmd') + let s:pycmd = substitute(g:powerline_pycmd, '\v\C^(py)%[thon](3?)$', '\1\2', '') + if s:pycmd is# 'py' + let s:has_python = has('python') + let s:pyeval = get(g:, 'powerline_pyeval', 'pyeval') + elseif s:pycmd is# 'py3' + let s:has_python = has('python3') + let s:pyeval = 'py3eval' + let s:pyeval = get(g:, 'powerline_pyeval', 'py3eval') + else + if !exists('g:powerline_pyeval') + echohl ErrorMsg + echomsg 'g:powerline_pycmd was set to an unknown values, but g:powerline_pyeval' + echomsg 'was not set. You should either set g:powerline_pycmd to "py3" or "py",' + echomsg 'specify g:powerline_pyeval explicitly or unset both and let powerline' + echomsg 'figure them out.' + echohl None + unlet s:pycmd + finish + endif + let s:pyeval = g:powerline_pyeval + let s:has_python = 1 + endif +elseif has('python') + let s:has_python = 1 + let s:pycmd = 'py' + let s:pyeval = get(g:, 'powerline_pyeval', 'pyeval') +elseif has('python3') + let s:has_python = 1 + let s:pycmd = 'py3' + let s:pyeval = get(g:, 'powerline_pyeval', 'py3eval') +else + let s:has_python = 0 +endif + +if !s:has_python + if !exists('g:powerline_no_python_error') + echohl ErrorMsg + echomsg 'You need vim compiled with Python 2.6, 2.7 or 3.2 and later support' + echomsg 'for Powerline to work. Please consult the documentation for more' + echomsg 'details.' + echohl None + endif + unlet s:has_python + finish +endif +unlet s:has_python + +let s:import_cmd = 'from powerline.vim import VimPowerline' +function s:rcmd(s) + if !exists('s:pystr') + let s:pystr = a:s . "\n" + else + let s:pystr = s:pystr . a:s . "\n" + endif +endfunction +try + let s:can_replace_pyeval = !exists('g:powerline_pyeval') + call s:rcmd('try:') + call s:rcmd(' powerline_appended_path = None') + call s:rcmd(' try:') + call s:rcmd(' '.s:import_cmd.'') + call s:rcmd(' except ImportError:') + call s:rcmd(' import sys, vim') + 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(' '.s:import_cmd.'') + call s:rcmd(' import vim') + call s:rcmd(' powerline_instance = 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(' del VimPowerline') + call s:rcmd(' del powerline_instance') + call s:rcmd('except Exception:') + call s:rcmd(' import traceback, sys') + call s:rcmd(' traceback.print_exc(file=sys.stdout)') + call s:rcmd(' raise') + execute s:pycmd s:pystr + unlet s:pystr + let s:launched = 1 +finally + unlet s:can_replace_pyeval + unlet s:import_cmd + if !exists('s:launched') + unlet s:pystr + echohl ErrorMsg + echomsg 'An error occurred while importing powerline module.' + echomsg 'This could be caused by invalid sys.path setting,' + 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 'the troubleshooting section in the documentation for' + echomsg 'possible solutions.' + if s:pycmd is# 'py' && has('python3') + echomsg 'If powerline on your system is installed for python 3 only you' + echomsg 'should set g:powerline_pycmd to "py3" to make it load correctly.' + endif + echohl None + call s:rcmd('def powerline_troubleshoot():') + call s:rcmd(' import sys') + call s:rcmd(' import vim') + 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(' 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(' try:') + call s:rcmd(' import powerline') + call s:rcmd(' except ImportError:') + call s:rcmd(' print("Unable to import powerline, is it installed?")') + call s:rcmd(' else:') + call s:rcmd(' if not vim.eval(''expand("<sfile>")'').startswith("/usr/"):') + 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.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.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 + call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline + call s:rcmd(' if os.path.basename(this_dir) != "powerline":') + 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("neither it is installed system-wide")') + 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(' this_dir_par = os.path.dirname(real_this_dir)') + 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(' 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(' real_this_dir, powerline_appended_path))') + 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("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("it contains some information about the alternatives.")') + call s:rcmd(' try:') + call s:rcmd(' from powerline.lint import check') + call s:rcmd(' except ImportError:') + 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 + unlet s:pystr + unlet s:pycmd + unlet s:pyeval + delfunction s:rcmd + finish + else + unlet s:launched + endif + unlet s:pycmd + unlet s:pyeval + delfunction s:rcmd +endtry diff --git a/powerline/bindings/wm/__init__.py b/powerline/bindings/wm/__init__.py new file mode 100644 index 0000000..646b701 --- /dev/null +++ b/powerline/bindings/wm/__init__.py @@ -0,0 +1,85 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re + +from powerline.theme import requires_segment_info +from powerline.lib.shell import run_cmd +from powerline.bindings.wm.awesome import AwesomeThread + + +DEFAULT_UPDATE_INTERVAL = 0.5 + + +conn = None + + +def i3_subscribe(conn, event, callback): + '''Subscribe to i3 workspace event + + :param conn: + Connection returned by :py:func:`get_i3_connection`. + :param str event: + Event to subscribe to, e.g. ``'workspace'``. + :param func callback: + Function to run on event. + ''' + try: + import i3ipc + except ImportError: + import i3 + conn.Subscription(callback, event) + return + else: + pass + + conn.on(event, callback) + + from threading import Thread + + class I3Thread(Thread): + daemon = True + + def __init__(self, conn): + super(I3Thread, self).__init__() + self.__conn = conn + + def run(self): + self.__conn.main() + + thread = I3Thread(conn=conn) + + thread.start() + + +def get_i3_connection(): + '''Return a valid, cached i3 Connection instance + ''' + global conn + if not conn: + try: + import i3ipc + except ImportError: + import i3 as conn + else: + conn = i3ipc.Connection() + return conn + + +XRANDR_OUTPUT_RE = re.compile(r'^(?P<name>[0-9A-Za-z-]+) connected(?P<primary> primary)? (?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+)', re.MULTILINE) + + +def get_connected_xrandr_outputs(pl): + '''Iterate over xrandr outputs + + Outputs are represented by a dictionary with ``name``, ``width``, + ``height``, ``primary``, ``x`` and ``y`` keys. + ''' + return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer( + run_cmd(pl, ['xrandr', '-q']) + )) + + +wm_threads = { + 'awesome': AwesomeThread, +} diff --git a/powerline/bindings/wm/awesome.py b/powerline/bindings/wm/awesome.py new file mode 100644 index 0000000..b6e07f2 --- /dev/null +++ b/powerline/bindings/wm/awesome.py @@ -0,0 +1,59 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +from threading import Thread, Event +from time import sleep +from subprocess import Popen, PIPE + +from powerline import Powerline +from powerline.lib.monotonic import monotonic + + +def read_to_log(pl, client): + for line in client.stdout: + if line: + pl.info(line, prefix='awesome-client') + for line in client.stderr: + if line: + pl.error(line, prefix='awesome-client') + if client.wait(): + pl.error('Client exited with {0}', client.returncode, prefix='awesome') + + +def run(thread_shutdown_event=None, pl_shutdown_event=None, pl_config_loader=None, + interval=None): + powerline = Powerline( + 'wm', + renderer_module='pango_markup', + shutdown_event=pl_shutdown_event, + config_loader=pl_config_loader, + ) + powerline.update_renderer() + + if not thread_shutdown_event: + thread_shutdown_event = powerline.shutdown_event + + while not thread_shutdown_event.is_set(): + # powerline.update_interval may change over time + used_interval = interval or powerline.update_interval + start_time = monotonic() + s = powerline.render(side='right') + request = 'powerline_widget:set_markup(\'' + s.translate({'\'': '\\\'', '\\': '\\\\'}) + '\')\n' + client = Popen(['awesome-client'], shell=False, stdout=PIPE, stderr=PIPE, stdin=PIPE) + client.stdin.write(request.encode('utf-8')) + client.stdin.close() + read_to_log(powerline.pl, client) + thread_shutdown_event.wait(max(used_interval - (monotonic() - start_time), 0.1)) + + +class AwesomeThread(Thread): + __slots__ = ('powerline_shutdown_event',) + + def __init__(self, **kwargs): + super(AwesomeThread, self).__init__() + self.powerline_run_kwargs = kwargs + + def run(self): + run(**self.powerline_run_kwargs) diff --git a/powerline/bindings/zsh/__init__.py b/powerline/bindings/zsh/__init__.py new file mode 100644 index 0000000..1037bba --- /dev/null +++ b/powerline/bindings/zsh/__init__.py @@ -0,0 +1,228 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import atexit + +from weakref import WeakValueDictionary, ref + +import zsh + +from powerline.shell import ShellPowerline +from powerline.lib.overrides import parsedotval, parse_override_var +from powerline.lib.unicode import unicode, u +from powerline.lib.encoding import (get_preferred_output_encoding, + get_preferred_environment_encoding) +from powerline.lib.dict import mergeargs + + +used_powerlines = WeakValueDictionary() + + +def shutdown(): + for powerline in tuple(used_powerlines.values()): + powerline.shutdown() + + +def get_var_config(var): + try: + val = zsh.getvalue(var) + if isinstance(val, dict): + return mergeargs([parsedotval((u(k), u(v))) for k, v in val.items()]) + elif isinstance(val, (unicode, str, bytes)): + return mergeargs(parse_override_var(u(val))) + else: + return None + except: + return None + + +class Args(object): + __slots__ = ('last_pipe_status', 'last_exit_code') + ext = ['shell'] + renderer_module = '.zsh' + + @property + def config_override(self): + return get_var_config('POWERLINE_CONFIG_OVERRIDES') + + @property + def theme_override(self): + return get_var_config('POWERLINE_THEME_OVERRIDES') + + @property + def config_path(self): + try: + ret = zsh.getvalue('POWERLINE_CONFIG_PATHS') + except IndexError: + return None + else: + if isinstance(ret, (unicode, str, bytes)): + return [ + path + for path in ret.split((b':' if isinstance(ret, bytes) else ':')) + if path + ] + else: + return ret + + @property + def jobnum(self): + return zsh.getvalue('_POWERLINE_JOBNUM') + + +def string(s): + if type(s) is bytes: + return s.decode(get_preferred_environment_encoding(), 'replace') + else: + return str(s) + + +class Environment(object): + @staticmethod + def __getitem__(key): + try: + return string(zsh.getvalue(key)) + except IndexError as e: + raise KeyError(*e.args) + + @staticmethod + def get(key, default=None): + try: + return string(zsh.getvalue(key)) + except IndexError: + return default + + @staticmethod + def __contains__(key): + try: + zsh.getvalue(key) + return True + except IndexError: + return False + + +if hasattr(getattr(zsh, 'environ', None), '__contains__'): + environ = zsh.environ +else: + environ = Environment() + + +if hasattr(zsh, 'expand') and zsh.expand('${:-}') == '': + zsh_expand = zsh.expand +else: + def zsh_expand(s): + zsh.eval('local _POWERLINE_REPLY="' + s + '"') + ret = zsh.getvalue('_POWERLINE_REPLY') + zsh.setvalue('_POWERLINE_REPLY', None) + return ret + + +class ZshPowerline(ShellPowerline): + def init(self, **kwargs): + super(ZshPowerline, self).init(Args(), **kwargs) + + def precmd(self): + self.args.last_pipe_status = zsh.pipestatus() + self.args.last_exit_code = zsh.last_exit_code() + + def do_setup(self, zsh_globals): + set_prompt(self, 'PS1', 'left', None, above=True) + set_prompt(self, 'RPS1', 'right', None) + set_prompt(self, 'PS2', 'left', 'continuation') + set_prompt(self, 'RPS2', 'right', 'continuation') + set_prompt(self, 'PS3', 'left', 'select') + used_powerlines[id(self)] = self + zsh_globals['_powerline'] = self + + +class Prompt(object): + __slots__ = ('powerline', 'side', 'savedpsvar', 'savedps', 'args', 'theme', 'above', '__weakref__') + + def __init__(self, powerline, side, theme, savedpsvar=None, savedps=None, above=False): + self.powerline = powerline + self.side = side + self.above = above + self.savedpsvar = savedpsvar + self.savedps = savedps + self.args = powerline.args + self.theme = theme + + def __str__(self): + parser_state = u(zsh_expand('${(%):-%_}')) + shortened_path = u(zsh_expand('${(%):-%~}')) + try: + mode = u(zsh.getvalue('_POWERLINE_MODE')) + except IndexError: + mode = None + try: + default_mode = u(zsh.getvalue('_POWERLINE_DEFAULT_MODE')) + except IndexError: + default_mode = None + segment_info = { + 'args': self.args, + 'environ': environ, + 'client_id': 1, + 'local_theme': self.theme, + 'parser_state': parser_state, + 'shortened_path': shortened_path, + 'mode': mode, + 'default_mode': default_mode, + } + try: + zle_rprompt_indent = zsh.getvalue('ZLE_RPROMPT_INDENT') + except IndexError: + zle_rprompt_indent = 1 + r = '' + if self.above: + for line in self.powerline.render_above_lines( + width=zsh.columns() - zle_rprompt_indent, + segment_info=segment_info, + ): + if line: + r += line + '\n' + r += self.powerline.render( + width=zsh.columns(), + side=self.side, + segment_info=segment_info, + mode=mode, + ) + if type(r) is not str: + if type(r) is bytes: + return r.decode(get_preferred_output_encoding(), 'replace') + else: + return r.encode(get_preferred_output_encoding(), 'replace') + return r + + def __del__(self): + if self.savedps: + zsh.setvalue(self.savedpsvar, self.savedps) + self.powerline.shutdown() + + +def set_prompt(powerline, psvar, side, theme, above=False): + try: + savedps = zsh.getvalue(psvar) + except IndexError: + savedps = None + zpyvar = 'ZPYTHON_POWERLINE_' + psvar + prompt = Prompt(powerline, side, theme, psvar, savedps, above) + zsh.setvalue(zpyvar, None) + zsh.set_special_string(zpyvar, prompt) + zsh.setvalue(psvar, '${' + zpyvar + '}') + return ref(prompt) + + +def reload(): + for powerline in tuple(used_powerlines.values()): + powerline.reload() + + +def reload_config(): + for powerline in used_powerlines.values(): + powerline.create_renderer(load_main=True, load_colors=True, load_colorscheme=True, load_theme=True) + + +def setup(zsh_globals): + powerline = ZshPowerline() + powerline.setup(zsh_globals) + atexit.register(shutdown) diff --git a/powerline/bindings/zsh/powerline.zsh b/powerline/bindings/zsh/powerline.zsh new file mode 100644 index 0000000..5d7cb28 --- /dev/null +++ b/powerline/bindings/zsh/powerline.zsh @@ -0,0 +1,216 @@ +local _POWERLINE_SOURCED="$0:A" + +_powerline_columns_fallback() { + if which stty &>/dev/null ; then + local cols="$(stty size 2>/dev/null)" + if ! test -z "$cols" ; then + echo "${cols#* }" + return 0 + fi + fi + echo 0 + return 0 +} + +_powerline_append_precmd_function() { + if test -z "${precmd_functions[(re)$1]}" ; then + precmd_functions+=( $1 ) + fi +} + +integer -g _POWERLINE_JOBNUM=0 + +_powerline_tmux_pane() { + local -x TMUX="$_POWERLINE_TMUX" + echo "${TMUX_PANE:-`tmux display -p "#D"`}" | tr -d ' %' +} + +_powerline_tmux_pane() { + local -x TMUX="$_POWERLINE_TMUX" + echo "${TMUX_PANE:-`tmux display -p "#D"`}" | tr -d ' %' +} + +_powerline_init_tmux_support() { + emulate -L zsh + if test -n "$TMUX" && tmux refresh -S &>/dev/null ; then + # TMUX variable may be unset to create new tmux session inside this one + typeset -g _POWERLINE_TMUX="$TMUX" + + function -g _powerline_tmux_setenv() { + emulate -L zsh + local -x TMUX="$_POWERLINE_TMUX" + tmux setenv -g TMUX_"$1"_$(_powerline_tmux_pane) "$2" + tmux refresh -S + } + + function -g _powerline_tmux_set_pwd() { + _powerline_tmux_setenv PWD "$PWD" + } + + function -g _powerline_tmux_set_columns() { + _powerline_tmux_setenv COLUMNS "${COLUMNS:-$(_powerline_columns_fallback)}" + } + + chpwd_functions+=( _powerline_tmux_set_pwd ) + trap '_powerline_tmux_set_columns' SIGWINCH + _powerline_tmux_set_columns + _powerline_tmux_set_pwd + fi +} + +_powerline_init_modes_support() { + emulate -L zsh + + test -z "$ZSH_VERSION" && return 0 + + local -a vs + vs=( ${(s:.:)ZSH_VERSION} ) + + # Mode support requires >=zsh-4.3.11 + if (( vs[1] < 4 || (vs[1] == 4 && (vs[2] < 3 || (vs[2] == 3 && vs[3] < 11))) )) ; then + return 0 + fi + + function -g _powerline_get_main_keymap_name() { + REPLY="${${(Q)${${(z)${"$(bindkey -lL main)"}}[3]}}:-.safe}" + } + + function -g _powerline_set_true_keymap_name() { + typeset -g _POWERLINE_MODE="${1}" + local plm_bk="$(bindkey -lL ${_POWERLINE_MODE})" + if [[ $plm_bk = 'bindkey -A'* ]] ; then + _powerline_set_true_keymap_name ${(Q)${${(z)plm_bk}[3]}} + fi + } + + function -g _powerline_zle_keymap_select() { + _powerline_set_true_keymap_name $KEYMAP + zle reset-prompt + test -z "$_POWERLINE_SAVE_WIDGET" || zle $_POWERLINE_SAVE_WIDGET + } + + function -g _powerline_set_main_keymap_name() { + local REPLY + _powerline_get_main_keymap_name + _powerline_set_true_keymap_name "$REPLY" + } + + _powerline_add_widget zle-keymap-select _powerline_zle_keymap_select + _powerline_set_main_keymap_name + + if [[ "$_POWERLINE_MODE" != vi* ]] ; then + typeset -g _POWERLINE_DEFAULT_MODE="$_POWERLINE_MODE" + fi + + _powerline_append_precmd_function _powerline_set_main_keymap_name +} + +_powerline_set_jobnum() { + # If you are wondering why I am not using the same code as I use for bash + # ($(jobs|wc -l)): consider the following test: + # echo abc | less + # <C-z> + # . This way jobs will print + # [1] + done echo abc | + # suspended less -M + # ([ is in first column). You see: any line counting thingie will return + # wrong number of jobs. You need to filter the lines first. Or not use + # jobs built-in at all. + integer -g _POWERLINE_JOBNUM=${(%):-%j} +} + +_powerline_update_counter() { + zpython '_powerline.precmd()' +} + +_powerline_setup_prompt() { + emulate -L zsh + + _powerline_append_precmd_function _powerline_set_jobnum + + typeset -g VIRTUAL_ENV_DISABLE_PROMPT=1 + + if test -z "${POWERLINE_NO_ZSH_ZPYTHON}" && { zmodload libzpython || zmodload zsh/zpython } &>/dev/null ; then + _powerline_append_precmd_function _powerline_update_counter + zpython 'from powerline.bindings.zsh import setup as _powerline_setup' + zpython '_powerline_setup(globals())' + zpython 'del _powerline_setup' + powerline-reload() { + zpython 'from powerline.bindings.zsh import reload as _powerline_reload' + zpython '_powerline_reload()' + zpython 'del _powerline_reload' + } + powerline-reload-config() { + zpython 'from powerline.bindings.zsh import reload_config as _powerline_reload_config' + zpython '_powerline_reload_config()' + zpython 'del _powerline_reload_config' + } + else + if test -z "${POWERLINE_COMMAND}" ; then + typeset -g POWERLINE_COMMAND="$($POWERLINE_CONFIG_COMMAND shell command)" + fi + + local add_args='-r .zsh' + add_args+=' --last-exit-code=$?' + add_args+=' --last-pipe-status="$pipestatus"' + add_args+=' --renderer-arg="client_id=$$"' + add_args+=' --renderer-arg="shortened_path=${(%):-%~}"' + add_args+=' --jobnum=$_POWERLINE_JOBNUM' + add_args+=' --renderer-arg="mode=$_POWERLINE_MODE"' + add_args+=' --renderer-arg="default_mode=$_POWERLINE_DEFAULT_MODE"' + local new_args_2=' --renderer-arg="parser_state=${(%%):-%_}"' + new_args_2+=' --renderer-arg="local_theme=continuation"' + local add_args_3=$add_args' --renderer-arg="local_theme=select"' + local add_args_2=$add_args$new_args_2 + add_args+=' --width=$(( ${COLUMNS:-$(_powerline_columns_fallback)} - ${ZLE_RPROMPT_INDENT:-1} ))' + local add_args_r2=$add_args$new_args_2 + typeset -g PS1='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell aboveleft '$add_args')' + typeset -g RPS1='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell right '$add_args')' + typeset -g PS2='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell left '$add_args_2')' + typeset -g RPS2='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell right '$add_args_r2')' + typeset -g PS3='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell left '$add_args_3')' + fi +} + +_powerline_add_widget() { + local widget="$1" + local function="$2" + local old_widget_command="$(zle -l -L $widget)" + if [[ "$old_widget_command" = "zle -N $widget $function" ]] ; then + return 0 + elif [[ -z "$old_widget_command" ]] ; then + zle -N $widget $function + else + local save_widget="_powerline_save_$widget" + local -i i=0 + while ! test -z "$(zle -l -L $save_widget)" ; do + save_widget="${save_widget}_$i" + (( i++ )) + done + # If widget was defined with `zle -N widget` (without `function` + # argument) then this function will be handy. + eval "function $save_widget() { emulate -L zsh; $widget \$@ }" + eval "${old_widget_command/$widget/$save_widget}" + zle -N $widget $function + typeset -g _POWERLINE_SAVE_WIDGET="$save_widget" + fi +} + +if test -z "${POWERLINE_CONFIG_COMMAND}" ; then + if which powerline-config >/dev/null ; then + typeset -g POWERLINE_CONFIG_COMMAND=powerline-config + else + typeset -g POWERLINE_CONFIG_COMMAND="${_POWERLINE_SOURCED:h:h:h:h}/scripts/powerline-config" + fi +fi + +setopt promptpercent +setopt promptsubst + +if "${POWERLINE_CONFIG_COMMAND}" shell --shell=zsh uses prompt ; then + _powerline_setup_prompt + _powerline_init_modes_support +fi +if "${POWERLINE_CONFIG_COMMAND}" shell --shell=zsh uses tmux ; then + _powerline_init_tmux_support +fi diff --git a/powerline/colorscheme.py b/powerline/colorscheme.py new file mode 100644 index 0000000..66416b5 --- /dev/null +++ b/powerline/colorscheme.py @@ -0,0 +1,147 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from copy import copy + +from powerline.lib.unicode import unicode + + +DEFAULT_MODE_KEY = None +ATTR_BOLD = 1 +ATTR_ITALIC = 2 +ATTR_UNDERLINE = 4 + + +def get_attrs_flag(attrs): + '''Convert an attribute array to a renderer flag.''' + attrs_flag = 0 + if 'bold' in attrs: + attrs_flag |= ATTR_BOLD + if 'italic' in attrs: + attrs_flag |= ATTR_ITALIC + if 'underline' in attrs: + attrs_flag |= ATTR_UNDERLINE + return attrs_flag + + +def pick_gradient_value(grad_list, gradient_level): + '''Given a list of colors and gradient percent, return a color that should be used. + + Note: gradient level is not checked for being inside [0, 100] interval. + ''' + return grad_list[int(round(gradient_level * (len(grad_list) - 1) / 100))] + + +class Colorscheme(object): + def __init__(self, colorscheme_config, colors_config): + '''Initialize a colorscheme.''' + self.colors = {} + self.gradients = {} + + self.groups = colorscheme_config['groups'] + self.translations = colorscheme_config.get('mode_translations', {}) + + # Create a dict of color tuples with both a cterm and hex value + for color_name, color in colors_config['colors'].items(): + try: + self.colors[color_name] = (color[0], int(color[1], 16)) + except TypeError: + self.colors[color_name] = (color, cterm_to_hex[color]) + + # Create a dict of gradient names with two lists: for cterm and hex + # values. Two lists in place of one list of pairs were chosen because + # true colors allow more precise gradients. + for gradient_name, gradient in colors_config['gradients'].items(): + if len(gradient) == 2: + self.gradients[gradient_name] = ( + (gradient[0], [int(color, 16) for color in gradient[1]])) + else: + self.gradients[gradient_name] = ( + (gradient[0], [cterm_to_hex[color] for color in gradient[0]])) + + def get_gradient(self, gradient, gradient_level): + if gradient in self.gradients: + return tuple((pick_gradient_value(grad_list, gradient_level) for grad_list in self.gradients[gradient])) + else: + return self.colors[gradient] + + def get_group_props(self, mode, trans, group, translate_colors=True): + if isinstance(group, (str, unicode)): + try: + group_props = trans['groups'][group] + except KeyError: + try: + group_props = self.groups[group] + except KeyError: + return None + else: + return self.get_group_props(mode, trans, group_props, True) + else: + return self.get_group_props(mode, trans, group_props, False) + else: + if translate_colors: + group_props = copy(group) + try: + ctrans = trans['colors'] + except KeyError: + pass + else: + for key in ('fg', 'bg'): + try: + group_props[key] = ctrans[group_props[key]] + except KeyError: + pass + return group_props + else: + return group + + def get_highlighting(self, groups, mode, gradient_level=None): + trans = self.translations.get(mode, {}) + for group in groups: + group_props = self.get_group_props(mode, trans, group) + if group_props: + break + else: + raise KeyError('Highlighting groups not found in colorscheme: ' + ', '.join(groups)) + + if gradient_level is None: + pick_color = self.colors.__getitem__ + else: + pick_color = lambda gradient: self.get_gradient(gradient, gradient_level) + + return { + 'fg': pick_color(group_props['fg']), + 'bg': pick_color(group_props['bg']), + 'attrs': get_attrs_flag(group_props.get('attrs', [])), + } + + +# 0 1 2 3 4 5 6 7 8 9 +cterm_to_hex = ( + 0x000000, 0xc00000, 0x008000, 0x804000, 0x0000c0, 0xc000c0, 0x008080, 0xc0c0c0, 0x808080, 0xff6060, # 0 + 0x00ff00, 0xffff00, 0x8080ff, 0xff40ff, 0x00ffff, 0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, # 1 + 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, # 2 + 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, # 3 + 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, # 4 + 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, # 5 + 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, # 6 + 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, # 7 + 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f, # 8 + 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, # 9 + 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, # 10 + 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, # 11 + 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, # 12 + 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, # 13 + 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, # 14 + 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, # 15 + 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, # 16 + 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, # 17 + 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, # 18 + 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, # 19 + 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, # 20 + 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, # 21 + 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, # 22 + 0xffffd7, 0xffffff, 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, # 23 + 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, # 24 + 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee # 25 +) diff --git a/powerline/commands/__init__.py b/powerline/commands/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/commands/__init__.py diff --git a/powerline/commands/config.py b/powerline/commands/config.py new file mode 100644 index 0000000..06c6436 --- /dev/null +++ b/powerline/commands/config.py @@ -0,0 +1,109 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (division, absolute_import, print_function) + +import argparse + +import powerline.bindings.config as config + + +class StrFunction(object): + def __init__(self, function, name=None): + self.name = name or function.__name__ + self.function = function + + def __call__(self, *args, **kwargs): + self.function(*args, **kwargs) + + def __str__(self): + return self.name + + +TMUX_ACTIONS = { + 'source': StrFunction(config.source_tmux_files, 'source'), + 'setenv': StrFunction(config.init_tmux_environment, 'setenv'), + 'setup': StrFunction(config.tmux_setup, 'setup'), +} + + +SHELL_ACTIONS = { + 'command': StrFunction(config.shell_command, 'command'), + 'uses': StrFunction(config.uses), +} + + +class ConfigArgParser(argparse.ArgumentParser): + def parse_args(self, *args, **kwargs): + ret = super(ConfigArgParser, self).parse_args(*args, **kwargs) + if not hasattr(ret, 'function'): + # In Python-3* `powerline-config` (without arguments) raises + # AttributeError. I have not found any standard way to display same + # error message as in Python-2*. + self.error('too few arguments') + return ret + + +def get_argparser(ArgumentParser=ConfigArgParser): + parser = ArgumentParser(description='Script used to obtain powerline configuration.') + parser.add_argument( + '-p', '--config-path', action='append', metavar='PATH', + help='Path to configuration directory. If it is present ' + 'then configuration files will only be seeked in the provided path. ' + 'May be provided multiple times to search in a list of directories.' + ) + subparsers = parser.add_subparsers() + tmux_parser = subparsers.add_parser('tmux', help='Tmux-specific commands') + tmux_parser.add_argument( + 'function', + choices=tuple(TMUX_ACTIONS.values()), + metavar='ACTION', + type=(lambda v: TMUX_ACTIONS.get(v)), + help='If action is `source\' then version-specific tmux configuration ' + 'files are sourced, if it is `setenv\' then special ' + '(prefixed with `_POWERLINE\') tmux global environment variables ' + 'are filled with data from powerline configuration. ' + 'Action `setup\' is just doing `setenv\' then `source\'.' + ) + tpg = tmux_parser.add_mutually_exclusive_group() + tpg.add_argument( + '-s', '--source', action='store_true', default=None, + help='When using `setup\': always use configuration file sourcing. ' + 'By default this is determined automatically based on tmux ' + 'version: this is the default for tmux 1.8 and below.', + ) + tpg.add_argument( + '-n', '--no-source', action='store_false', dest='source', default=None, + help='When using `setup\': in place of sourcing directly execute ' + 'configuration files. That is, read each needed ' + 'powerline-specific configuration file, substitute ' + '`$_POWERLINE_…\' variables with appropriate values and run ' + '`tmux config line\'. This is the default behaviour for ' + 'tmux 1.9 and above.' + ) + + shell_parser = subparsers.add_parser('shell', help='Shell-specific commands') + shell_parser.add_argument( + 'function', + choices=tuple(SHELL_ACTIONS.values()), + type=(lambda v: SHELL_ACTIONS.get(v)), + metavar='ACTION', + help='If action is `command\' then preferred powerline command is ' + 'output, if it is `uses\' then powerline-config script will exit ' + 'with 1 if specified component is disabled and 0 otherwise.', + ) + shell_parser.add_argument( + 'component', + nargs='?', + choices=('tmux', 'prompt'), + metavar='COMPONENT', + help='Only applicable for `uses\' subcommand: makes `powerline-config\' ' + 'exit with 0 if specific component is enabled and with 1 otherwise. ' + '`tmux\' component stands for tmux bindings ' + '(e.g. those that notify tmux about current directory changes), ' + '`prompt\' component stands for shell prompt.' + ) + shell_parser.add_argument( + '-s', '--shell', + metavar='SHELL', + help='Shell for which query is run', + ) + return parser diff --git a/powerline/commands/daemon.py b/powerline/commands/daemon.py new file mode 100644 index 0000000..7e8c8ab --- /dev/null +++ b/powerline/commands/daemon.py @@ -0,0 +1,24 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (division, absolute_import, print_function) + +import argparse + + +def get_argparser(ArgumentParser=argparse.ArgumentParser): + parser = ArgumentParser(description='Daemon that improves powerline performance.') + parser.add_argument( + '--quiet', '-q', action='store_true', + help='Without other options: do not complain about already running ' + 'powerline-daemon instance. ' + 'Will still exit with 1. ' + 'With `--kill\' and `--replace\': do not show any messages. ' + 'With `--foreground\': ignored. ' + 'Does not silence exceptions in any case.' + ) + parser.add_argument('--socket', '-s', help='Specify socket which will be used for connecting to daemon.') + exclusive_group = parser.add_mutually_exclusive_group() + exclusive_group.add_argument('--kill', '-k', action='store_true', help='Kill an already running instance.') + replace_group = exclusive_group.add_argument_group() + replace_group.add_argument('--foreground', '-f', action='store_true', help='Run in the foreground (don’t daemonize).') + replace_group.add_argument('--replace', '-r', action='store_true', help='Replace an already running instance.') + return parser diff --git a/powerline/commands/lemonbar.py b/powerline/commands/lemonbar.py new file mode 100644 index 0000000..547c52c --- /dev/null +++ b/powerline/commands/lemonbar.py @@ -0,0 +1,35 @@ +# vim:fileencoding=utf-8:noet +# WARNING: using unicode_literals causes errors in argparse +from __future__ import (division, absolute_import, print_function) + +import argparse + + +def get_argparser(ArgumentParser=argparse.ArgumentParser): + parser = ArgumentParser( + description='Powerline BAR bindings.' + ) + parser.add_argument( + '--i3', action='store_true', + help='Subscribe for i3 events.' + ) + parser.add_argument( + '--height', default='', + metavar='PIXELS', help='Bar height.' + ) + parser.add_argument( + '--interval', '-i', + type=float, default=0.5, + metavar='SECONDS', help='Refresh interval.' + ) + parser.add_argument( + '--bar-command', '-C', + default='lemonbar', + metavar='CMD', help='Name of the lemonbar executable to use.' + ) + parser.add_argument( + 'args', nargs=argparse.REMAINDER, + help='Extra arguments for lemonbar. Should be preceded with ``--`` ' + 'argument in order not to be confused with script own arguments.' + ) + return parser diff --git a/powerline/commands/lint.py b/powerline/commands/lint.py new file mode 100755 index 0000000..8961a65 --- /dev/null +++ b/powerline/commands/lint.py @@ -0,0 +1,21 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (division, absolute_import, print_function) + +import argparse + + +def get_argparser(ArgumentParser=argparse.ArgumentParser): + parser = ArgumentParser(description='Powerline configuration checker.') + parser.add_argument( + '-p', '--config-path', action='append', metavar='PATH', + help='Paths where configuration should be checked, in order. You must ' + 'supply all paths necessary for powerline to work, ' + 'checking partial (e.g. only user overrides) configuration ' + 'is not supported.' + ) + parser.add_argument( + '-d', '--debug', action='store_const', const=True, + help='Display additional information. Used for debugging ' + '`powerline-lint\' itself, not for debugging configuration.' + ) + return parser diff --git a/powerline/commands/main.py b/powerline/commands/main.py new file mode 100644 index 0000000..366eba2 --- /dev/null +++ b/powerline/commands/main.py @@ -0,0 +1,190 @@ +# vim:fileencoding=utf-8:noet +# WARNING: using unicode_literals causes errors in argparse +from __future__ import (division, absolute_import, print_function) + +import argparse +import sys + +from itertools import chain + +from powerline.lib.overrides import parsedotval, parse_override_var +from powerline.lib.dict import mergeargs +from powerline.lib.encoding import get_preferred_arguments_encoding +from powerline.lib.unicode import u, unicode +from powerline.bindings.wm import wm_threads + + +if sys.version_info < (3,): + encoding = get_preferred_arguments_encoding() + + def arg_to_unicode(s): + return unicode(s, encoding, 'replace') if not isinstance(s, unicode) else s # NOQA +else: + def arg_to_unicode(s): + return s + + +def finish_args(parser, environ, args, is_daemon=False): + '''Do some final transformations + + Transforms ``*_override`` arguments into dictionaries, adding overrides from + environment variables. Transforms ``renderer_arg`` argument into dictionary + as well, but only if it is true. + + :param dict environ: + Environment from which additional overrides should be taken from. + :param args: + Arguments object returned by + :py:meth:`argparse.ArgumentParser.parse_args`. Will be modified + in-place. + + :return: Object received as second (``args``) argument. + ''' + args.config_override = mergeargs(chain( + parse_override_var(environ.get('POWERLINE_CONFIG_OVERRIDES', '')), + (parsedotval(v) for v in args.config_override or ()), + )) + args.theme_override = mergeargs(chain( + parse_override_var(environ.get('POWERLINE_THEME_OVERRIDES', '')), + (parsedotval(v) for v in args.theme_override or ()), + )) + if args.renderer_arg: + args.renderer_arg = mergeargs((parsedotval(v) for v in args.renderer_arg), remove=True) + if 'pane_id' in args.renderer_arg: + if isinstance(args.renderer_arg['pane_id'], (bytes, unicode)): + try: + args.renderer_arg['pane_id'] = int(args.renderer_arg['pane_id'].lstrip(' %')) + except ValueError: + pass + if 'client_id' not in args.renderer_arg: + args.renderer_arg['client_id'] = args.renderer_arg['pane_id'] + args.config_path = ( + [path for path in environ.get('POWERLINE_CONFIG_PATHS', '').split(':') if path] + + (args.config_path or []) + ) + if args.ext[0].startswith('wm.'): + if not is_daemon: + parser.error('WM bindings must be used with daemon only') + elif args.ext[0][3:] not in wm_threads: + parser.error('WM binding not found') + elif not args.side: + parser.error('expected one argument') + return args + + +def int_or_sig(s): + if s.startswith('sig'): + return u(s) + else: + return int(s) + + +def get_argparser(ArgumentParser=argparse.ArgumentParser): + parser = ArgumentParser(description='Powerline prompt and statusline script.') + parser.add_argument( + 'ext', nargs=1, + help='Extension: application for which powerline command is launched ' + '(usually `shell\' or `tmux\'). Also supports `wm.\' extensions: ' + + ', '.join(('`wm.' + key + '\'' for key in wm_threads.keys())) + '.' + ) + parser.add_argument( + 'side', nargs='?', choices=('left', 'right', 'above', 'aboveleft'), + help='Side: `left\' and `right\' represent left and right side ' + 'respectively, `above\' emits lines that are supposed to be printed ' + 'just above the prompt and `aboveleft\' is like concatenating ' + '`above\' with `left\' with the exception that only one Python ' + 'instance is used in this case. May be omitted for `wm.*\' extensions.' + ) + parser.add_argument( + '-r', '--renderer-module', metavar='MODULE', type=str, + help='Renderer module. Usually something like `.bash\' or `.zsh\' ' + '(with leading dot) which is `powerline.renderers.{ext}{MODULE}\', ' + 'may also be full module name (must contain at least one dot or ' + 'end with a dot in case it is top-level module) or ' + '`powerline.renderers\' submodule (in case there are no dots).' + ) + parser.add_argument( + '-w', '--width', type=int, + help='Maximum prompt with. Triggers truncation of some segments.' + ) + parser.add_argument( + '--last-exit-code', metavar='INT', type=int_or_sig, + help='Last exit code.' + ) + parser.add_argument( + '--last-pipe-status', metavar='LIST', default='', + type=lambda s: [int_or_sig(status) for status in s.split()], + help='Like above, but is supposed to contain space-separated array ' + 'of statuses, representing exit statuses of commands in one pipe.' + ) + parser.add_argument( + '--jobnum', metavar='INT', type=int, + help='Number of jobs.' + ) + parser.add_argument( + '-c', '--config-override', metavar='KEY.KEY=VALUE', type=arg_to_unicode, + action='append', + help='Configuration overrides for `config.json\'. Is translated to a ' + 'dictionary and merged with the dictionary obtained from actual ' + 'JSON configuration: KEY.KEY=VALUE is translated to ' + '`{"KEY": {"KEY": VALUE}}\' and then merged recursively. ' + 'VALUE may be any JSON value, values that are not ' + '`null\', `true\', `false\', start with digit, `{\', `[\' ' + 'are treated like strings. If VALUE is omitted ' + 'then corresponding key is removed.' + ) + parser.add_argument( + '-t', '--theme-override', metavar='THEME.KEY.KEY=VALUE', type=arg_to_unicode, + action='append', + help='Like above, but theme-specific. THEME should point to ' + 'an existing and used theme to have any effect, but it is fine ' + 'to use any theme here.' + ) + parser.add_argument( + '-R', '--renderer-arg', + metavar='KEY=VAL', type=arg_to_unicode, action='append', + help='Like above, but provides argument for renderer. Is supposed ' + 'to be used only by shell bindings to provide various data like ' + 'last-exit-code or last-pipe-status (they are not using ' + '`--renderer-arg\' for historical resons: `--renderer-arg\' ' + 'was added later).' + ) + parser.add_argument( + '-p', '--config-path', action='append', metavar='PATH', + help='Path to configuration directory. If it is present then ' + 'configuration files will only be seeked in the provided path. ' + 'May be provided multiple times to search in a list of directories.' + ) + parser.add_argument( + '--socket', metavar='ADDRESS', type=str, + help='Socket address to use in daemon clients. Is always UNIX domain ' + 'socket on linux and file socket on Mac OS X. Not used here, ' + 'present only for compatibility with other powerline clients. ' + 'This argument must always be the first one and be in a form ' + '`--socket ADDRESS\': no `=\' or short form allowed ' + '(in other powerline clients, not here).' + ) + return parser + + +def write_output(args, powerline, segment_info, write): + if args.renderer_arg: + segment_info.update(args.renderer_arg) + if args.side.startswith('above'): + for line in powerline.render_above_lines( + width=args.width, + segment_info=segment_info, + mode=segment_info.get('mode', None), + ): + if line: + write(line + '\n') + args.side = args.side[len('above'):] + + if args.side: + rendered = powerline.render( + width=args.width, + side=args.side, + segment_info=segment_info, + mode=segment_info.get('mode', None), + ) + write(rendered) diff --git a/powerline/config.py b/powerline/config.py new file mode 100644 index 0000000..edcf921 --- /dev/null +++ b/powerline/config.py @@ -0,0 +1,10 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + + +POWERLINE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BINDINGS_DIRECTORY = os.path.join(POWERLINE_ROOT, 'powerline', 'bindings') +TMUX_CONFIG_DIRECTORY = os.path.join(BINDINGS_DIRECTORY, 'tmux') +DEFAULT_SYSTEM_CONFIG_DIR = None diff --git a/powerline/config_files/colors.json b/powerline/config_files/colors.json new file mode 100644 index 0000000..1564f18 --- /dev/null +++ b/powerline/config_files/colors.json @@ -0,0 +1,124 @@ +{ + "colors": { + "black": 16, + "white": 231, + + "green": 2, + "darkestgreen": 22, + "darkgreen": 28, + "mediumgreen": 70, + "brightgreen": 148, + + "darkestcyan": 23, + "darkcyan": 74, + "mediumcyan": 117, + "brightcyan": 159, + + "darkestblue": 24, + "darkblue": 31, + + "red": 1, + "darkestred": 52, + "darkred": 88, + "mediumred": 124, + "brightred": 160, + "brightestred": 196, + + "darkestpurple": 55, + "mediumpurple": 98, + "brightpurple": 189, + + "darkorange": 94, + "mediumorange": 166, + "brightorange": 208, + "brightestorange": 214, + + "yellow": 11, + "brightyellow": 220, + + "gray0": 233, + "gray1": 235, + "gray2": 236, + "gray3": 239, + "gray4": 240, + "gray5": 241, + "gray6": 244, + "gray7": 245, + "gray8": 247, + "gray9": 250, + "gray10": 252, + + "gray11": 234, + "gray90": 254, + + "gray70": [249, "b3b3b3"], + + "lightyellowgreen": 106, + "gold3": 178, + "orangered": 202, + + "steelblue": 67, + "darkorange3": 166, + "skyblue1": 117, + "khaki1": 228, + + "solarized:base03": [8, "002b36"], + "solarized:base02": [0, "073642"], + "solarized:base01": [10, "586e75"], + "solarized:base00": [11, "657b83"], + "solarized:base0": [12, "839496"], + "solarized:base1": [14, "93a1a1"], + "solarized:base2": [7, "eee8d5"], + "solarized:base3": [15, "fdf6e3"], + "solarized:yellow": [3, "b58900"], + "solarized:orange": [9, "cb4b16"], + "solarized:red": [1, "dc322f"], + "solarized:magenta": [5, "d33682"], + "solarized:violet": [13, "6c71c4"], + "solarized:blue": [4, "268bd2"], + "solarized:cyan": [6, "2aa198"], + "solarized:green": [2, "859900"] + }, + "gradients": { + "dark_GREEN_Orange_red": [ + [22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 94, 94, 94, 94, 94, 94, 94, 88, 52], + ["006000", "006000", "006000", "006000", "006000", "006000", "006000", "006000", "006000", "036000", "076000", "0a6000", "0d6000", "106000", "126000", "146000", "166000", "186000", "1a6000", "1b6000", "1d6000", "1e6000", "206000", "216000", "236000", "246000", "256000", "266000", "286000", "296000", "2a6000", "2b6000", "2c6100", "2d6100", "2f6100", "306100", "316100", "326100", "336100", "346100", "356100", "366100", "376100", "386100", "386100", "396100", "3a6100", "3b6100", "3c6100", "3d6100", "3e6100", "3f6100", "406100", "406100", "416100", "426000", "436000", "446000", "456000", "456000", "466000", "476000", "486000", "496000", "496000", "4a6000", "4b6000", "4c6000", "4d6000", "4d6000", "4e6000", "4f6000", "506000", "506000", "516000", "526000", "536000", "536000", "546000", "556000", "566000", "566000", "576000", "586000", "596000", "596000", "5a6000", "5d6000", "616000", "646000", "686000", "6b6000", "6f6000", "726000", "766000", "796000", "7d6000", "806000", "7e5500", "6f3105", "5d0001"] + ], + "GREEN_Orange_red": [ + [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1], + ["005f00", "015f00", "025f00", "035f00", "045f00", "055f00", "065f00", "075f00", "085f00", "095f00", "0b5f00", "0c5f00", "0d5f00", "0e5f00", "0f5f00", "105f00", "115f00", "125f00", "135f00", "145f00", "165f00", "175f00", "185f00", "195f00", "1a5f00", "1b5f00", "1c5f00", "1d5f00", "1e5f00", "1f5f00", "215f00", "225f00", "235f00", "245f00", "255f00", "265f00", "275f00", "285f00", "295f00", "2a5f00", "2c5f00", "2d5f00", "2e5f00", "2f5f00", "305f00", "315f00", "325f00", "335f00", "345f00", "355f00", "375f00", "385f00", "395f00", "3a5f00", "3b5f00", "3c5f00", "3d5f00", "3e5f00", "3f5f00", "415f00", "425f00", "435f00", "445f00", "455f00", "465f00", "475f00", "485f00", "495f00", "4a5f00", "4c5f00", "4d5f00", "4e5f00", "4f5f00", "505f00", "515f00", "525f00", "535f00", "545f00", "555f00", "575f00", "585f00", "595f00", "5a5f00", "5b5f00", "5c5f00", "5d5f00", "5e5f00", "615f00", "655f00", "685f00", "6c5f00", "6f5f00", "735f00", "765f00", "7a5f00", "7d5f00", "815f00", "845f00", "815200", "702900"] + ], + "green_yellow_red": [ + [190, 184, 178, 172, 166, 160], + ["8ae71c", "8ce71c", "8fe71c", "92e71c", "95e71d", "98e71d", "9ae71d", "9de71d", "a0e71e", "a3e71e", "a6e71e", "a8e71e", "abe71f", "aee71f", "b1e71f", "b4e71f", "b6e720", "b9e720", "bce720", "bfe720", "c2e821", "c3e721", "c5e621", "c7e521", "c9e522", "cbe422", "cde322", "cfe222", "d1e223", "d3e123", "d5e023", "d7df23", "d9df24", "dbde24", "dddd24", "dfdc24", "e1dc25", "e3db25", "e5da25", "e7d925", "e9d926", "e9d626", "e9d426", "e9d126", "e9cf27", "e9cc27", "e9ca27", "e9c727", "e9c528", "e9c228", "e9c028", "e9bd28", "e9bb29", "e9b829", "e9b629", "e9b329", "e9b12a", "e9ae2a", "e9ac2a", "e9a92a", "eaa72b", "eaa42b", "eaa22b", "ea9f2b", "ea9d2c", "ea9b2c", "ea982c", "ea962c", "ea942d", "ea912d", "ea8f2d", "ea8d2d", "ea8a2e", "ea882e", "ea862e", "ea832e", "ea812f", "ea7f2f", "ea7c2f", "ea7a2f", "eb7830", "eb7530", "eb7330", "eb7130", "eb6f31", "eb6c31", "eb6a31", "eb6831", "eb6632", "eb6332", "eb6132", "eb5f32", "eb5d33", "eb5a33", "eb5833", "eb5633", "eb5434", "eb5134", "eb4f34", "eb4d34", "ec4b35"] + ], + "green_yellow_orange_red": [ + [2, 3, 9, 1], + ["719e07", "739d06", "759c06", "779c06", "799b06", "7b9a05", "7d9a05", "7f9905", "819805", "839805", "859704", "879704", "899604", "8b9504", "8d9504", "8f9403", "919303", "949303", "969203", "989102", "9a9102", "9c9002", "9e9002", "a08f02", "a28e01", "a48e01", "a68d01", "a88c01", "aa8c01", "ac8b00", "ae8a00", "b08a00", "b28900", "b58900", "b58700", "b68501", "b78302", "b78102", "b87f03", "b97d04", "b97b04", "ba7905", "bb7806", "bb7606", "bc7407", "bd7208", "bd7008", "be6e09", "bf6c0a", "bf6a0a", "c0690b", "c1670c", "c1650c", "c2630d", "c3610e", "c35f0e", "c45d0f", "c55b10", "c55a10", "c65811", "c75612", "c75412", "c85213", "c95014", "c94e14", "ca4c15", "cb4b16", "cb4a16", "cc4917", "cc4818", "cd4719", "cd4719", "ce461a", "ce451b", "cf441c", "cf441c", "d0431d", "d0421e", "d1411f", "d1411f", "d24020", "d23f21", "d33e22", "d33e22", "d43d23", "d43c24", "d53b25", "d53b25", "d63a26", "d63927", "d73828", "d73828", "d83729", "d8362a", "d9352b", "d9352b", "da342c", "da332d", "db322e", "dc322f"] + ], + "yellow_red": [ + [220, 178, 172, 166, 160], + ["ffd700", "fdd500", "fbd300", "fad200", "f8d000", "f7cf00", "f5cd00", "f3cb00", "f2ca00", "f0c800", "efc700", "edc500", "ebc300", "eac200", "e8c000", "e7bf00", "e5bd00", "e3bb00", "e2ba00", "e0b800", "dfb700", "ddb500", "dbb300", "dab200", "d8b000", "d7af00", "d7ad00", "d7ab00", "d7aa00", "d7a800", "d7a700", "d7a500", "d7a300", "d7a200", "d7a000", "d79f00", "d79d00", "d79b00", "d79a00", "d79800", "d79700", "d79500", "d79300", "d79200", "d79000", "d78f00", "d78d00", "d78b00", "d78a00", "d78800", "d78700", "d78500", "d78300", "d78200", "d78000", "d77f00", "d77d00", "d77b00", "d77a00", "d77800", "d77700", "d77500", "d77300", "d77200", "d77000", "d76f00", "d76d00", "d76b00", "d76a00", "d76800", "d76700", "d76500", "d76300", "d76200", "d76000", "d75f00", "d75b00", "d75700", "d75300", "d74f00", "d74c00", "d74800", "d74400", "d74000", "d73c00", "d73900", "d73500", "d73100", "d72d00", "d72900", "d72600", "d72200", "d71e00", "d71a00", "d71600", "d71300", "d70f00", "d70b00", "d70700"] + ], + "yellow_orange_red": [ + [3, 9, 1], + ["b58900", "b58700", "b58600", "b68501", "b68401", "b78202", "b78102", "b88003", "b87f03", "b87d03", "b97c04", "b97b04", "ba7a05", "ba7805", "bb7706", "bb7606", "bc7507", "bc7307", "bc7207", "bd7108", "bd7008", "be6e09", "be6d09", "bf6c0a", "bf6b0a", "c06a0b", "c0680b", "c0670b", "c1660c", "c1650c", "c2630d", "c2620d", "c3610e", "c3600e", "c35e0e", "c45d0f", "c45c0f", "c55b10", "c55910", "c65811", "c65711", "c75612", "c75412", "c75312", "c85213", "c85113", "c94f14", "c94e14", "ca4d15", "ca4c15", "cb4b16", "cb4a16", "cb4a17", "cc4917", "cc4918", "cc4818", "cd4819", "cd4719", "cd471a", "ce461a", "ce461b", "ce451b", "cf451c", "cf441c", "cf441d", "d0431d", "d0431e", "d0421e", "d1421f", "d1411f", "d14120", "d24020", "d24021", "d23f21", "d33f22", "d33e22", "d33e23", "d43d23", "d43d24", "d43c24", "d53c25", "d53b25", "d53b26", "d63a26", "d63a27", "d63927", "d73928", "d73828", "d73829", "d83729", "d8372a", "d8362a", "d9362b", "d9352b", "d9352c", "da342c", "da342d", "da332d", "db332e"] + ], + "blue_red": [ + [39, 74, 68, 67, 103, 97, 96, 132, 131, 167, 203, 197], + ["19b4fe", "1bb2fc", "1db1fa", "1faff8", "22aef6", "24adf4", "26abf2", "29aaf0", "2ba9ee", "2da7ec", "30a6ea", "32a5e8", "34a3e6", "36a2e4", "39a0e2", "3b9fe1", "3d9edf", "409cdd", "429bdb", "449ad9", "4798d7", "4997d5", "4b96d3", "4d94d1", "5093cf", "5292cd", "5490cb", "578fc9", "598dc7", "5b8cc6", "5e8bc4", "6089c2", "6288c0", "6487be", "6785bc", "6984ba", "6b83b8", "6e81b6", "7080b4", "727eb2", "757db0", "777cae", "797aac", "7b79ab", "7e78a9", "8076a7", "8275a5", "8574a3", "8772a1", "89719f", "8c709d", "8e6e9b", "906d99", "926b97", "956a95", "976993", "996791", "9c668f", "9e658e", "a0638c", "a3628a", "a56188", "a75f86", "a95e84", "ac5c82", "ae5b80", "b05a7e", "b3587c", "b5577a", "b75678", "ba5476", "bc5374", "be5273", "c05071", "c34f6f", "c54e6d", "c74c6b", "ca4b69", "cc4967", "ce4865", "d14763", "d34561", "d5445f", "d7435d", "da415b", "dc4059", "de3f58", "e13d56", "e33c54", "e53a52", "e83950", "ea384e", "ec364c", "ee354a", "f13448", "f33246", "f53144", "f83042", "fa2e40"] + ], + "white_red": [ + [231, 255, 223, 216, 209, 202, 196], + ["ffffff", "fefefe", "fdfdfd", "fdfdfd", "fcfcfc", "fbfbfb", "fafafa", "fafafa", "f9f9f9", "f8f8f8", "f7f7f7", "f7f7f7", "f6f6f6", "f5f5f5", "f4f4f4", "f4f3f4", "f3f3f3", "f2f2f2", "f1f1f1", "f0f0f0", "f0f0f0", "efefef", "eeeeee", "efecea", "f1eae4", "f2e8de", "f3e6d8", "f5e4d3", "f6e2cd", "f7e0c7", "f8dec2", "f9dcbc", "fadab6", "fad8b1", "fbd5ac", "fbd2a9", "fbcea5", "fbcaa1", "fbc79e", "fbc39a", "fbc097", "fbbc93", "fbb88f", "fbb58c", "fab188", "faad85", "faaa81", "fba67e", "fba37a", "fb9f76", "fb9c73", "fb986f", "fb946c", "fb9168", "fa8d65", "fa8961", "fa865c", "fa8256", "fb7f4f", "fb7b48", "fb7841", "fb743a", "fb7133", "fb6d2c", "fa6a23", "fa661a", "fa620e", "fa5f03", "fa5d03", "fa5b03", "fa5a03", "fa5803", "fa5703", "fa5503", "fa5303", "fa5103", "fa4f03", "fa4e03", "fa4c03", "fa4a04", "fa4804", "fa4604", "fa4404", "fa4204", "fa3f04", "fa3d04", "fa3b04", "fa3805", "fa3605", "fa3305", "fb3105", "fb2e05", "fb2a05", "fb2705", "fb2306", "fb1f06", "fb1b06", "fb1506", "fb0e06", "fa0506", "fa0007"] + ], + "dark_green_gray": [ + [70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247], + ["51b000", "52b000", "54b000", "55b002", "56b007", "57b00d", "58b011", "59af15", "5aaf18", "5caf1b", "5daf1e", "5eaf21", "5faf23", "60ae25", "61ae27", "62ae2a", "63ae2c", "64ae2e", "65ae30", "66ae31", "67ad33", "68ad35", "69ad37", "69ad38", "6aad3a", "6bad3c", "6cac3d", "6dac3f", "6eac40", "6fac42", "70ac44", "70ac45", "71ab47", "72ab48", "73ab49", "74ab4b", "75ab4c", "75ab4e", "76aa4f", "77aa51", "78aa52", "79aa53", "79aa55", "7aaa56", "7ba957", "7ca959", "7ca95a", "7da95b", "7ea95d", "7fa95e", "7fa85f", "80a861", "81a862", "81a863", "82a865", "83a766", "83a767", "84a768", "85a76a", "85a76b", "86a66c", "87a66d", "87a66f", "88a670", "89a671", "89a672", "8aa574", "8ba575", "8ba576", "8ca577", "8da579", "8da47a", "8ea47b", "8ea47c", "8fa47d", "90a47f", "90a380", "91a381", "91a382", "92a384", "93a385", "93a286", "94a287", "94a288", "95a28a", "95a18b", "96a18c", "97a18d", "97a18e", "98a190", "98a091", "99a092", "99a093", "9aa094", "9aa096", "9b9f97", "9b9f98", "9c9f99", "9c9f9a", "9d9e9c", "9d9e9d"] + ], + "light_green_gray": [ + [148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 187, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250], + ["a3d900", "a4d800", "a4d800", "a5d805", "a5d80d", "a6d714", "a6d719", "a6d71d", "a7d621", "a7d625", "a8d628", "a8d62b", "a8d52e", "a9d531", "a9d533", "aad536", "aad438", "aad43a", "abd43d", "abd33f", "abd341", "acd343", "acd345", "acd247", "add249", "add24b", "add14d", "aed14f", "aed151", "aed152", "afd054", "afd056", "afd058", "b0d059", "b0cf5b", "b0cf5d", "b1cf5e", "b1ce60", "b1ce62", "b1ce63", "b2ce65", "b2cd67", "b2cd68", "b3cd6a", "b3cc6b", "b3cc6d", "b3cc6e", "b4cc70", "b4cb71", "b4cb73", "b4cb75", "b5ca76", "b5ca78", "b5ca79", "b5ca7a", "b6c97c", "b6c97d", "b6c97f", "b6c880", "b6c882", "b7c883", "b7c885", "b7c786", "b7c788", "b7c789", "b8c68a", "b8c68c", "b8c68d", "b8c68f", "b8c590", "b9c591", "b9c593", "b9c494", "b9c496", "b9c497", "b9c498", "bac39a", "bac39b", "bac39d", "bac29e", "bac29f", "bac2a1", "bac2a2", "bac1a4", "bbc1a5", "bbc1a6", "bbc0a8", "bbc0a9", "bbc0aa", "bbc0ac", "bbbfad", "bbbfae", "bbbfb0", "bbbeb1", "bcbeb3", "bcbeb4", "bcbdb5", "bcbdb7", "bcbdb8", "bcbdb9", "bcbcbb"] + ] + } +} diff --git a/powerline/config_files/colorschemes/default.json b/powerline/config_files/colorschemes/default.json new file mode 100644 index 0000000..7e271ef --- /dev/null +++ b/powerline/config_files/colorschemes/default.json @@ -0,0 +1,56 @@ +{ + "name": "Default", + "groups": { + "information:additional": { "fg": "gray9", "bg": "gray4", "attrs": [] }, + "information:regular": { "fg": "gray10", "bg": "gray4", "attrs": ["bold"] }, + "information:highlighted": { "fg": "white", "bg": "gray4", "attrs": [] }, + "information:priority": { "fg": "brightyellow", "bg": "mediumorange", "attrs": [] }, + "warning:regular": { "fg": "white", "bg": "brightred", "attrs": ["bold"] }, + "critical:failure": { "fg": "white", "bg": "darkestred", "attrs": [] }, + "critical:success": { "fg": "white", "bg": "darkestgreen", "attrs": [] }, + "background": { "fg": "white", "bg": "gray0", "attrs": [] }, + "background:divider": { "fg": "gray5", "bg": "gray0", "attrs": [] }, + "session": { "fg": "black", "bg": "gray10", "attrs": ["bold"] }, + "date": { "fg": "gray8", "bg": "gray2", "attrs": [] }, + "time": { "fg": "gray10", "bg": "gray2", "attrs": ["bold"] }, + "time:divider": { "fg": "gray5", "bg": "gray2", "attrs": [] }, + "email_alert": "warning:regular", + "email_alert_gradient": { "fg": "white", "bg": "yellow_orange_red", "attrs": ["bold"] }, + "hostname": { "fg": "black", "bg": "gray10", "attrs": ["bold"] }, + "weather": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "weather_temp_gradient": { "fg": "blue_red", "bg": "gray0", "attrs": [] }, + "weather_condition_hot": { "fg": "khaki1", "bg": "gray0", "attrs": [] }, + "weather_condition_snowy": { "fg": "skyblue1", "bg": "gray0", "attrs": [] }, + "weather_condition_rainy": { "fg": "skyblue1", "bg": "gray0", "attrs": [] }, + "uptime": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "external_ip": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "internal_ip": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "network_load": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "network_load_gradient": { "fg": "green_yellow_orange_red", "bg": "gray0", "attrs": [] }, + "network_load_sent_gradient": "network_load_gradient", + "network_load_recv_gradient": "network_load_gradient", + "network_load:divider": "background:divider", + "system_load": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "system_load_gradient": { "fg": "green_yellow_orange_red", "bg": "gray0", "attrs": [] }, + "environment": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "cpu_load_percent": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "cpu_load_percent_gradient": { "fg": "green_yellow_orange_red", "bg": "gray0", "attrs": [] }, + "battery": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "battery_gradient": { "fg": "white_red", "bg": "gray0", "attrs": [] }, + "battery_full": { "fg": "red", "bg": "gray0", "attrs": [] }, + "battery_empty": { "fg": "white", "bg": "gray0", "attrs": [] }, + "player": { "fg": "gray10", "bg": "black", "attrs": [] }, + "user": { "fg": "white", "bg": "darkblue", "attrs": ["bold"] }, + "branch": { "fg": "gray9", "bg": "gray2", "attrs": [] }, + "branch_dirty": { "fg": "brightyellow", "bg": "gray2", "attrs": [] }, + "branch_clean": { "fg": "gray9", "bg": "gray2", "attrs": [] }, + "branch:divider": { "fg": "gray7", "bg": "gray2", "attrs": [] }, + "stash": "branch_dirty", + "stash:divider": "branch:divider", + "cwd": "information:additional", + "cwd:current_folder": "information:regular", + "cwd:divider": { "fg": "gray7", "bg": "gray4", "attrs": [] }, + "virtualenv": { "fg": "white", "bg": "darkcyan", "attrs": [] }, + "attached_clients": { "fg": "gray8", "bg": "gray0", "attrs": [] } + } +} diff --git a/powerline/config_files/colorschemes/ipython/__main__.json b/powerline/config_files/colorschemes/ipython/__main__.json new file mode 100644 index 0000000..982ea35 --- /dev/null +++ b/powerline/config_files/colorschemes/ipython/__main__.json @@ -0,0 +1,6 @@ +{ + "groups": { + "prompt": "information:additional", + "prompt_count": "information:highlighted" + } +} diff --git a/powerline/config_files/colorschemes/pdb/__main__.json b/powerline/config_files/colorschemes/pdb/__main__.json new file mode 100644 index 0000000..01a51fe --- /dev/null +++ b/powerline/config_files/colorschemes/pdb/__main__.json @@ -0,0 +1,8 @@ +{ + "groups": { + "current_code_name": "information:additional", + "current_context": "current_code_name", + "current_line": "information:regular", + "current_file": "information:regular" + } +} diff --git a/powerline/config_files/colorschemes/pdb/default.json b/powerline/config_files/colorschemes/pdb/default.json new file mode 100644 index 0000000..b97acf7 --- /dev/null +++ b/powerline/config_files/colorschemes/pdb/default.json @@ -0,0 +1,5 @@ +{ + "groups": { + "stack_depth": { "fg": "gray1", "bg": "gray10", "attrs": ["bold"] } + } +} diff --git a/powerline/config_files/colorschemes/pdb/solarized.json b/powerline/config_files/colorschemes/pdb/solarized.json new file mode 100644 index 0000000..2e1c787 --- /dev/null +++ b/powerline/config_files/colorschemes/pdb/solarized.json @@ -0,0 +1,5 @@ +{ + "groups": { + "stack_depth": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] } + } +} diff --git a/powerline/config_files/colorschemes/shell/__main__.json b/powerline/config_files/colorschemes/shell/__main__.json new file mode 100644 index 0000000..6e3856f --- /dev/null +++ b/powerline/config_files/colorschemes/shell/__main__.json @@ -0,0 +1,10 @@ +{ + "groups": { + "continuation": "cwd", + "continuation:current": "cwd:current_folder", + "exit_fail": "critical:failure", + "exit_success": "critical:success", + "jobnum": "information:priority", + "superuser": "warning:regular" + } +} diff --git a/powerline/config_files/colorschemes/shell/default.json b/powerline/config_files/colorschemes/shell/default.json new file mode 100644 index 0000000..1126feb --- /dev/null +++ b/powerline/config_files/colorschemes/shell/default.json @@ -0,0 +1,16 @@ +{ + "name": "Default color scheme for shell prompts", + "groups": { + "hostname": { "fg": "brightyellow", "bg": "mediumorange", "attrs": [] }, + "environment": { "fg": "white", "bg": "darkestgreen", "attrs": [] }, + "mode": { "fg": "darkestgreen", "bg": "brightgreen", "attrs": ["bold"] }, + "attached_clients": { "fg": "white", "bg": "darkestgreen", "attrs": [] } + }, + "mode_translations": { + "vicmd": { + "groups": { + "mode": {"fg": "darkestcyan", "bg": "white", "attrs": ["bold"]} + } + } + } +} diff --git a/powerline/config_files/colorschemes/shell/solarized.json b/powerline/config_files/colorschemes/shell/solarized.json new file mode 100644 index 0000000..69dcab1 --- /dev/null +++ b/powerline/config_files/colorschemes/shell/solarized.json @@ -0,0 +1,13 @@ +{ + "name": "Solarized dark for shell", + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": ["bold"] } + }, + "mode_translations": { + "vicmd": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] } + } + } + } +} diff --git a/powerline/config_files/colorschemes/solarized.json b/powerline/config_files/colorschemes/solarized.json new file mode 100644 index 0000000..c0eba5a --- /dev/null +++ b/powerline/config_files/colorschemes/solarized.json @@ -0,0 +1,40 @@ +{ + "name": "Solarized dark", + "groups": { + "information:additional": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] }, + "information:regular": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] }, + "information:highlighted": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"]}, + "information:priority": { "fg": "solarized:base3", "bg": "solarized:yellow", "attrs": [] }, + "warning:regular": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": [] }, + "critical:failure": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": [] }, + "critical:success": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] }, + "background": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": [] }, + "background:divider": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "user": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }, + "virtualenv": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] }, + "branch": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": [] }, + "branch_clean": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "stash": "branch_dirty", + "email_alert_gradient": { "fg": "solarized:base3", "bg": "yellow_orange_red", "attrs": [] }, + "email_alert": "warning:regular", + "cwd": "information:additional", + "cwd:current_folder": "information:regular", + "cwd:divider": { "fg": "solarized:base1", "bg": "solarized:base01", "attrs": [] }, + "network_load": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] }, + "network_load:divider": "network_load", + "network_load_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base03", "attrs": [] }, + "network_load_sent_gradient": "network_load_gradient", + "network_load_recv_gradient": "network_load_gradient", + "hostname": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "environment": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] }, + "attached_clients": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] }, + "date": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "time": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": ["bold"] }, + "time:divider": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "system_load": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] }, + "weather_temp_gradient": { "fg": "blue_red", "bg": "solarized:base03", "attrs": [] }, + "weather": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] }, + "uptime": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] } + } +} diff --git a/powerline/config_files/colorschemes/tmux/default.json b/powerline/config_files/colorschemes/tmux/default.json new file mode 100644 index 0000000..8525fb0 --- /dev/null +++ b/powerline/config_files/colorschemes/tmux/default.json @@ -0,0 +1,14 @@ +{ + "groups": { + "active_window_status": {"fg": "darkblue", "bg": "gray0", "attrs": []}, + "window_status": {"fg": "gray70", "bg": "gray0", "attrs": []}, + "activity_status": {"fg": "yellow", "bg": "gray0", "attrs": []}, + "bell_status": {"fg": "red", "bg": "gray0", "attrs": []}, + "window": {"fg": "gray6", "bg": "gray0", "attrs": []}, + "window:divider": {"fg": "gray4", "bg": "gray0", "attrs": []}, + "window:current": {"fg": "mediumcyan", "bg": "darkblue", "attrs": []}, + "window_name": {"fg": "white", "bg": "darkblue", "attrs": ["bold"]}, + "session": {"fg": "black", "bg": "gray90", "attrs": ["bold"]}, + "session:prefix": {"fg": "gray90", "bg": "darkblue", "attrs": ["bold"]} + } +} diff --git a/powerline/config_files/colorschemes/tmux/solarized.json b/powerline/config_files/colorschemes/tmux/solarized.json new file mode 100644 index 0000000..20c42d3 --- /dev/null +++ b/powerline/config_files/colorschemes/tmux/solarized.json @@ -0,0 +1,14 @@ +{ + "groups": { + "active_window_status": { "fg": "solarized:blue", "bg": "solarized:base02", "attrs": [] }, + "window_status": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "activity_status": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": [] }, + "bell_status": { "fg": "solarized:red", "bg": "solarized:base02", "attrs": [] }, + "window": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "window:divider": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": [] }, + "window:current": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "window_name": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] }, + "session": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "session:prefix": { "fg": "solarized:base01", "bg": "solarized:base3", "attrs": [] } + } +} diff --git a/powerline/config_files/colorschemes/vim/__main__.json b/powerline/config_files/colorschemes/vim/__main__.json new file mode 100644 index 0000000..1ce2e7b --- /dev/null +++ b/powerline/config_files/colorschemes/vim/__main__.json @@ -0,0 +1,50 @@ +{ + "groups": { + "branch_clean": "branch", + "environment": "information:unimportant", + "file_size": "information:unimportant", + "file_format": "information:unimportant", + "file_encoding": "file_format", + "file_type": "file_format", + "branch": "information:additional", + "file_scheme": "file_name", + "file_directory": "information:additional", + "file_name_empty": "file_directory", + "line_percent": "information:additional", + "line_count": "line_current", + "position": "information:additional", + "single_tab": "line_current", + "many_tabs": "line_current", + "bufnr": "file_directory", + "winnr": "information:unimportant", + "tabnr": "file_directory", + "capslock_indicator": "paste_indicator", + + "csv:column_number": "line_current", + "csv:column_name": "line_current_symbol", + + "tab:background": "background", + "tab:divider": "background:divider", + + "tab_nc:modified_indicator": "modified_indicator", + "tab_nc:file_directory": "information:unimportant", + "tab_nc:file_name": "tab_nc:file_directory", + "tab_nc:tabnr": "tab_nc:file_directory", + + "buf_nc:file_directory": "tab_nc:file_directory", + "buf_nc:file_name": "buf_nc:file_directory", + "buf_nc:bufnr": "buf_nc:file_directory", + "buf_nc:modified_indicator": "tab_nc:modified_indicator", + + "buf_nc_mod:file_directory": "tab_nc:file_directory", + "buf_nc_mod:file_name": "buf_nc_mod:file_directory", + "buf_nc_mod:bufnr": "buf_nc_mod:file_directory", + "buf_nc_mod:modified_indicator": "tab_nc:modified_indicator", + + + "commandt:label": "file_name", + "commandt:background": "background", + "commandt:finder": "file_name", + "commandt:path": "file_directory" + } +} diff --git a/powerline/config_files/colorschemes/vim/default.json b/powerline/config_files/colorschemes/vim/default.json new file mode 100644 index 0000000..e02a160 --- /dev/null +++ b/powerline/config_files/colorschemes/vim/default.json @@ -0,0 +1,154 @@ +{ + "name": "Default color scheme", + "groups": { + "information:unimportant": { "fg": "gray8", "bg": "gray2", "attrs": [] }, + "information:additional": { "fg": "gray9", "bg": "gray4", "attrs": [] }, + "background": { "fg": "white", "bg": "gray2", "attrs": [] }, + "background:divider": { "fg": "gray6", "bg": "gray2", "attrs": [] }, + "mode": { "fg": "darkestgreen", "bg": "brightgreen", "attrs": ["bold"] }, + "visual_range": { "fg": "brightestorange", "bg": "darkorange", "attrs": ["bold"] }, + "modified_indicator": { "fg": "brightyellow", "bg": "gray4", "attrs": ["bold"] }, + "paste_indicator": { "fg": "white", "bg": "mediumorange", "attrs": ["bold"] }, + "readonly_indicator": { "fg": "brightestred", "bg": "gray4", "attrs": [] }, + "branch_dirty": { "fg": "brightyellow", "bg": "gray4", "attrs": [] }, + "branch:divider": { "fg": "gray7", "bg": "gray4", "attrs": [] }, + "file_name": { "fg": "white", "bg": "gray4", "attrs": ["bold"] }, + "window_title": { "fg": "white", "bg": "gray4", "attrs": [] }, + "file_name_no_file": { "fg": "gray9", "bg": "gray4", "attrs": ["bold"] }, + "file_vcs_status": { "fg": "brightestred", "bg": "gray4", "attrs": [] }, + "file_vcs_status_M": { "fg": "brightyellow", "bg": "gray4", "attrs": [] }, + "file_vcs_status_A": { "fg": "brightgreen", "bg": "gray4", "attrs": [] }, + "line_percent": { "fg": "gray9", "bg": "gray4", "attrs": [] }, + "line_percent_gradient": { "fg": "dark_green_gray", "bg": "gray4", "attrs": [] }, + "position": { "fg": "gray9", "bg": "gray4", "attrs": [] }, + "position_gradient": { "fg": "green_yellow_red", "bg": "gray4", "attrs": [] }, + "line_current": { "fg": "gray1", "bg": "gray10", "attrs": ["bold"] }, + "line_current_symbol": { "fg": "gray1", "bg": "gray10", "attrs": [] }, + "virtcol_current_gradient": { "fg": "dark_GREEN_Orange_red", "bg": "gray10", "attrs": [] }, + "col_current": { "fg": "gray6", "bg": "gray10", "attrs": [] }, + "modified_buffers": { "fg": "brightyellow", "bg": "gray2", "attrs": [] }, + "attached_clients": { "fg": "gray8", "bg": "gray2", "attrs": [] }, + "error": { "fg": "brightestred", "bg": "darkred", "attrs": ["bold"] }, + "warning": { "fg": "brightyellow", "bg": "darkorange", "attrs": ["bold"] }, + "current_tag": { "fg": "gray9", "bg": "gray2", "attrs": [] }, + + "tab_nc:modified_indicator": { "fg": "brightyellow", "bg": "gray2", "attrs": ["bold"] } + }, + "mode_translations": { + "nc": { + "colors": { + "brightyellow": "darkorange", + "brightestred": "darkred", + "gray0": "gray0", + "gray1": "gray0", + "gray2": "gray0", + "gray3": "gray1", + "gray4": "gray1", + "gray5": "gray1", + "gray6": "gray1", + "gray7": "gray4", + "gray8": "gray4", + "gray9": "gray4", + "gray10": "gray5", + "white": "gray6", + "dark_green_gray": "gray5" + } + }, + "i": { + "colors": { + "gray0": "darkestblue", + "gray1": "darkestblue", + "gray2": "darkestblue", + "gray3": "darkblue", + "gray4": "darkblue", + "gray5": "darkestcyan", + "gray6": "darkestcyan", + "gray7": "darkestcyan", + "gray8": "mediumcyan", + "gray9": "mediumcyan", + "gray10": "mediumcyan", + "green_yellow_red": "gray5", + "dark_green_gray": "light_green_gray" + }, + "groups": { + "mode": { "fg": "darkestcyan", "bg": "white", "attrs": ["bold"] }, + "background:divider": { "fg": "darkcyan", "bg": "darkestblue", "attrs": [] }, + "branch:divider": { "fg": "darkcyan", "bg": "darkblue", "attrs": [] } + } + }, + "ic": { + "colors": { + "gray0": "darkestblue", + "gray1": "darkestblue", + "gray2": "darkestblue", + "gray3": "darkblue", + "gray4": "darkblue", + "gray5": "darkestcyan", + "gray6": "darkestcyan", + "gray7": "darkestcyan", + "gray8": "mediumcyan", + "gray9": "mediumcyan", + "gray10": "mediumcyan", + "green_yellow_red": "gray5", + "dark_green_gray": "light_green_gray" + }, + "groups": { + "mode": { "fg": "darkestcyan", "bg": "white", "attrs": ["bold"] }, + "background:divider": { "fg": "darkcyan", "bg": "darkestblue", "attrs": [] }, + "branch:divider": { "fg": "darkcyan", "bg": "darkblue", "attrs": [] } + } + }, + "ix": { + "colors": { + "gray0": "darkestblue", + "gray1": "darkestblue", + "gray2": "darkestblue", + "gray3": "darkblue", + "gray4": "darkblue", + "gray5": "darkestcyan", + "gray6": "darkestcyan", + "gray7": "darkestcyan", + "gray8": "mediumcyan", + "gray9": "mediumcyan", + "gray10": "mediumcyan", + "green_yellow_red": "gray5", + "dark_green_gray": "light_green_gray" + }, + "groups": { + "mode": { "fg": "darkestcyan", "bg": "white", "attrs": ["bold"] }, + "background:divider": { "fg": "darkcyan", "bg": "darkestblue", "attrs": [] }, + "branch:divider": { "fg": "darkcyan", "bg": "darkblue", "attrs": [] } + } + }, + "v": { + "groups": { + "mode": { "fg": "darkorange", "bg": "brightestorange", "attrs": ["bold"] } + } + }, + "V": { + "groups": { + "mode": { "fg": "darkorange", "bg": "brightestorange", "attrs": ["bold"] } + } + }, + "^V": { + "groups": { + "mode": { "fg": "darkorange", "bg": "brightestorange", "attrs": ["bold"] } + } + }, + "R": { + "groups": { + "mode": { "fg": "white", "bg": "brightred", "attrs": ["bold"] } + } + }, + "Rc": { + "groups": { + "mode": { "fg": "white", "bg": "brightred", "attrs": ["bold"] } + } + }, + "Rx": { + "groups": { + "mode": { "fg": "white", "bg": "brightred", "attrs": ["bold"] } + } + } + } +} diff --git a/powerline/config_files/colorschemes/vim/solarized.json b/powerline/config_files/colorschemes/vim/solarized.json new file mode 100644 index 0000000..55cfaa7 --- /dev/null +++ b/powerline/config_files/colorschemes/vim/solarized.json @@ -0,0 +1,121 @@ +{ + "name": "Solarized dark for vim", + "groups": { + "information:additional": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] }, + "information:unimportant": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "background": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": [] }, + "background:divider": { "fg": "solarized:base00", "bg": "solarized:base02", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": ["bold"] }, + "visual_range": { "fg": "solarized:green", "bg": "solarized:base3", "attrs": ["bold"] }, + "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base01", "attrs": ["bold"] }, + "paste_indicator": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }, + "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base01", "attrs": [] }, + "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base01", "attrs": [] }, + "branch:divider": { "fg": "solarized:base1", "bg": "solarized:base01", "attrs": [] }, + "stash:divider": "branch:divider", + "file_name": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] }, + "window_title": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "file_name_no_file": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] }, + "file_format": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base01", "attrs": [] }, + "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base01", "attrs": [] }, + "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base01", "attrs": [] }, + "line_percent": { "fg": "solarized:base3", "bg": "solarized:base00", "attrs": [] }, + "line_percent_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base00", "attrs": [] }, + "position": { "fg": "solarized:base3", "bg": "solarized:base00", "attrs": [] }, + "position_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base00", "attrs": [] }, + "line_current": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] }, + "line_current_symbol": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "virtcol_current_gradient": { "fg": "GREEN_Orange_red", "bg": "solarized:base2", "attrs": [] }, + "col_current": { "fg": "solarized:base0", "bg": "solarized:base2", "attrs": [] }, + "environment": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "attached_clients": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "error": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] }, + "warning": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }, + "current_tag": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": ["bold"] } + }, + "mode_translations": { + "nc": { + "colors": { + "solarized:base01": "solarized:base02", + "solarized:base00": "solarized:base02", + "solarized:base0": "solarized:base01", + "solarized:base1": "solarized:base00", + "solarized:base2": "solarized:base0", + "solarized:base3": "solarized:base1" + } + }, + "i": { + "groups": { + "background": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "background:divider": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }, + "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": ["bold"] }, + "paste_indicator": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }, + "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] }, + "branch": { "fg": "solarized:base01", "bg": "solarized:base2", "attrs": [] }, + "branch:divider": { "fg": "solarized:base00", "bg": "solarized:base2", "attrs": [] }, + "file_directory": { "fg": "solarized:base01", "bg": "solarized:base2", "attrs": [] }, + "file_name": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": ["bold"] }, + "file_size": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] }, + "file_name_no_file": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": ["bold"] }, + "file_name_empty": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] }, + "file_format": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] }, + "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] }, + "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": [] }, + "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base2", "attrs": [] }, + "line_percent": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] }, + "line_percent_gradient": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] }, + "position": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] }, + "position_gradient": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] }, + "line_current": { "fg": "solarized:base03", "bg": "solarized:base3", "attrs": ["bold"] }, + "line_current_symbol": { "fg": "solarized:base03", "bg": "solarized:base3", "attrs": [] }, + "col_current": { "fg": "solarized:base0", "bg": "solarized:base3", "attrs": [] } + } + }, + "ic": { + "groups": { + "background": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "background:divider": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] } + } + }, + "ix": { + "groups": { + "background": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, + "background:divider": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] } + } + }, + "v": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] } + } + }, + "V": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] } + } + }, + "^V": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] } + } + }, + "R": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] } + } + }, + "Rc": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] } + } + }, + "Rx": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] } + } + } + } +} diff --git a/powerline/config_files/colorschemes/vim/solarizedlight.json b/powerline/config_files/colorschemes/vim/solarizedlight.json new file mode 100644 index 0000000..f862d39 --- /dev/null +++ b/powerline/config_files/colorschemes/vim/solarizedlight.json @@ -0,0 +1,122 @@ +{ + "name": "Solarized light for vim", + "groups": { + "information:additional": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] }, + "information:unimportant": { "fg": "solarized:base1", "bg": "solarized:base01", "attrs": [] }, + "background": { "fg": "solarized:base03", "bg": "solarized:base01", "attrs": [] }, + "background:divider": { "fg": "solarized:base0", "bg": "solarized:base01", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": ["bold"] }, + "visual_range": { "fg": "solarized:green", "bg": "solarized:base3", "attrs": ["bold"] }, + "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": ["bold"] }, + "paste_indicator": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": ["bold"] }, + "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] }, + "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": [] }, + "branch:divider": { "fg": "solarized:base1", "bg": "solarized:base2", "attrs": [] }, + "stash": "branch_dirty", + "stash:divider": "branch:divider", + "file_name": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] }, + "window_title": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "file_size": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "file_name_no_file": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] }, + "file_name_empty": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] }, + "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": [] }, + "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base2", "attrs": [] }, + "line_percent": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "line_percent_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base2", "attrs": [] }, + "position": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "position_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base2", "attrs": [] }, + "line_current": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": ["bold"] }, + "line_current_symbol": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": [] }, + "virtcol_current_gradient": { "fg": "yellow_orange_red", "bg": "solarized:base02", "attrs": [] }, + "col_current": { "fg": "solarized:base00", "bg": "solarized:base02", "attrs": [] }, + "error": { "fg": "solarized:base03", "bg": "solarized:red", "attrs": ["bold"] }, + "warning": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] }, + "current_tag": { "fg": "solarized:base03", "bg": "solarized:base01", "attrs": ["bold"] } + }, + "mode_translations": { + "nc": { + "colors": { + "solarized:base2": "solarized:base01", + "solarized:base0": "solarized:base01", + "solarized:base00": "solarized:base2", + "solarized:base1": "solarized:base0", + "solarized:base02": "solarized:base00", + "solarized:base03": "solarized:base1" + } + }, + "i": { + "groups": { + "background": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "background:divider": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }, + "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": ["bold"] }, + "paste_indicator": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }, + "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base02", "attrs": [] }, + "branch": { "fg": "solarized:base2", "bg": "solarized:base02", "attrs": [] }, + "branch:divider": { "fg": "solarized:base0", "bg": "solarized:base02", "attrs": [] }, + "file_directory": { "fg": "solarized:base2", "bg": "solarized:base02", "attrs": [] }, + "file_name": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": ["bold"] }, + "file_size": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": [] }, + "file_name_no_file": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": ["bold"] }, + "file_name_empty": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": [] }, + "file_format": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] }, + "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base02", "attrs": [] }, + "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": [] }, + "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base02", "attrs": [] }, + "line_percent": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] }, + "line_percent_gradient": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] }, + "position": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] }, + "position_gradient": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] }, + "line_current": { "fg": "solarized:base3", "bg": "solarized:base03", "attrs": ["bold"] }, + "line_current_symbol": { "fg": "solarized:base3", "bg": "solarized:base03", "attrs": [] }, + "virtcol_current_gradient": { "fg": "yellow_orange_red", "bg": "solarized:base03", "attrs": [] }, + "col_current": { "fg": "solarized:base00", "bg": "solarized:base03", "attrs": [] } + } + }, + "ic": { + "groups": { + "background": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "background:divider": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] } + } + }, + "ix": { + "groups": { + "background": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, + "background:divider": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] }, + "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] } + } + }, + "v": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] } + } + }, + "V": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] } + } + }, + "^V": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] } + } + }, + "R": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] } + } + }, + "Rc": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] } + } + }, + "Rx": { + "groups": { + "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] } + } + } + } +} diff --git a/powerline/config_files/config.json b/powerline/config_files/config.json new file mode 100644 index 0000000..4491885 --- /dev/null +++ b/powerline/config_files/config.json @@ -0,0 +1,53 @@ +{ + "common": { + "term_truecolor": false + }, + "ext": { + "ipython": { + "colorscheme": "default", + "theme": "in", + "local_themes": { + "rewrite": "rewrite", + "out": "out", + "in2": "in2" + } + }, + "pdb": { + "colorscheme": "default", + "theme": "default" + }, + "shell": { + "colorscheme": "default", + "theme": "default", + "local_themes": { + "continuation": "continuation", + "select": "select" + } + }, + "tmux": { + "colorscheme": "default", + "theme": "default" + }, + "vim": { + "colorscheme": "default", + "theme": "default", + "local_themes": { + "__tabline__": "tabline", + + "cmdwin": "cmdwin", + "help": "help", + "quickfix": "quickfix", + + "powerline.matchers.vim.plugin.nerdtree.nerdtree": "plugin_nerdtree", + "powerline.matchers.vim.plugin.commandt.commandt": "plugin_commandt", + "powerline.matchers.vim.plugin.gundo.gundo": "plugin_gundo", + "powerline.matchers.vim.plugin.gundo.gundo_preview": "plugin_gundo-preview" + } + }, + "wm": { + "colorscheme": "default", + "theme": "default", + "update_interval": 2 + } + } +} diff --git a/powerline/config_files/themes/ascii.json b/powerline/config_files/themes/ascii.json new file mode 100644 index 0000000..0ea05e7 --- /dev/null +++ b/powerline/config_files/themes/ascii.json @@ -0,0 +1,153 @@ +{ + "use_non_breaking_spaces": false, + "dividers": { + "left": { + "hard": " ", + "soft": "| " + }, + "right": { + "hard": " ", + "soft": " |" + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": "BR " + }, + "stash": { + "before": "ST " + }, + "cwd": { + "args": { + "ellipsis": "..." + } + }, + "player": { + "args": { + "state_symbols": { + "fallback": "", + "play": ">", + "pause": "~", + "stop": "X" + } + } + }, + + "line_current_symbol": { + "contents": "LN " + }, + + "time": { + "before": "" + }, + + "powerline.segments.common.net.network_load": { + "args": { + "recv_format": "DL {value:>8}", + "sent_format": "UL {value:>8}" + } + }, + "powerline.segments.common.net.hostname": { + "before": "H " + }, + "powerline.segments.common.bat.battery": { + "args": { + "full_heart": "O", + "empty_heart": "O", + "online": "C", + "offline": " " + } + }, + "powerline.segments.common.sys.uptime": { + "before": "UP " + }, + "powerline.segments.common.mail.email_imap_alert": { + "before": "MAIL " + }, + "powerline.segments.common.env.virtualenv": { + "before": "(e) " + }, + "powerline.segments.common.wthr.weather": { + "args": { + "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_format": "{temp:.0f} C" + } + }, + "powerline.segments.common.time.fuzzy_time": { + "args": { + "unicode_text": false + } + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N-OPER", + "v": "VISUAL", + "V": "V-LINE", + "^V": "V-BLCK", + "s": "SELECT", + "S": "S-LINE", + "^S": "S-BLCK", + "i": "INSERT", + "ic": "I-COMP", + "ix": "I-C_X ", + "R": "RPLACE", + "Rv": "V-RPLC", + "Rc": "R-COMP", + "Rx": "R-C_X ", + "c": "COMMND", + "cv": "VIM-EX", + "ce": "NRM-EX", + "r": "PROMPT", + "rm": "-MORE-", + "r?": "CNFIRM", + "!": "!SHELL", + "t": "TERM " + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows} x {vcols}", + "v_text_oneline": "C:{vcols}", + "v_text_multiline": "L:{rows}", + "V_text": "L:{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "RO" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "O", + "changed": "X" + } + } + } + } +} diff --git a/powerline/config_files/themes/ipython/in.json b/powerline/config_files/themes/ipython/in.json new file mode 100644 index 0000000..edd4d29 --- /dev/null +++ b/powerline/config_files/themes/ipython/in.json @@ -0,0 +1,25 @@ +{ + "segments": { + "left": [ + { + "function": "powerline.segments.common.env.virtualenv", + "priority": 10 + }, + { + "type": "string", + "contents": "In [", + "draw_soft_divider": false, + "highlight_groups": ["prompt"] + }, + { + "function": "powerline.segments.ipython.prompt_count", + "draw_soft_divider": false + }, + { + "type": "string", + "contents": "]", + "highlight_groups": ["prompt"] + } + ] + } +} diff --git a/powerline/config_files/themes/ipython/in2.json b/powerline/config_files/themes/ipython/in2.json new file mode 100644 index 0000000..422c44b --- /dev/null +++ b/powerline/config_files/themes/ipython/in2.json @@ -0,0 +1,12 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "contents": "", + "width": "auto", + "highlight_groups": ["prompt"] + } + ] + } +} diff --git a/powerline/config_files/themes/ipython/out.json b/powerline/config_files/themes/ipython/out.json new file mode 100644 index 0000000..2425d0b --- /dev/null +++ b/powerline/config_files/themes/ipython/out.json @@ -0,0 +1,24 @@ +{ + "default_module": "powerline.segments.ipython", + "segments": { + "left": [ + { + "type": "string", + "contents": "Out[", + "draw_soft_divider": false, + "width": "auto", + "align": "r", + "highlight_groups": ["prompt"] + }, + { + "function": "prompt_count", + "draw_soft_divider": false + }, + { + "type": "string", + "contents": "]", + "highlight_groups": ["prompt"] + } + ] + } +} diff --git a/powerline/config_files/themes/ipython/rewrite.json b/powerline/config_files/themes/ipython/rewrite.json new file mode 100644 index 0000000..8192fe4 --- /dev/null +++ b/powerline/config_files/themes/ipython/rewrite.json @@ -0,0 +1,23 @@ +{ + "default_module": "powerline.segments.ipython", + "segments": { + "left": [ + { + "type": "string", + "contents": "", + "draw_soft_divider": false, + "width": "auto", + "highlight_groups": ["prompt"] + }, + { + "function": "prompt_count", + "draw_soft_divider": false + }, + { + "type": "string", + "contents": ">", + "highlight_groups": ["prompt"] + } + ] + } +} diff --git a/powerline/config_files/themes/pdb/default.json b/powerline/config_files/themes/pdb/default.json new file mode 100644 index 0000000..dcae108 --- /dev/null +++ b/powerline/config_files/themes/pdb/default.json @@ -0,0 +1,27 @@ +{ + "default_module": "powerline.segments.pdb", + "segments": { + "left": [ + { + "function": "stack_depth" + }, + { + "type": "segment_list", + "function": "powerline.listers.pdb.frame_lister", + "segments": [ + { + "function": "current_file", + "after": ":" + }, + { + "function": "current_line", + "after": " " + }, + { + "function": "current_code_name" + } + ] + } + ] + } +} diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json new file mode 100644 index 0000000..366a7ea --- /dev/null +++ b/powerline/config_files/themes/powerline.json @@ -0,0 +1,151 @@ +{ + "dividers": { + "left": { + "hard": " ", + "soft": " " + }, + "right": { + "hard": " ", + "soft": " " + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": " " + }, + "stash": { + "before": "⌆ " + }, + "cwd": { + "args": { + "ellipsis": "⋯" + } + }, + + "line_current_symbol": { + "contents": " " + }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + + "time": { + "before": "⌚ " + }, + + "powerline.segments.common.net.network_load": { + "args": { + "recv_format": "⬇ {value:>8}", + "sent_format": "⬆ {value:>8}" + } + }, + "powerline.segments.common.net.hostname": { + "before": " " + }, + "powerline.segments.common.bat.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥", + "online": "⚡︎", + "offline": " " + } + }, + "powerline.segments.common.sys.uptime": { + "before": "⇑ " + }, + "powerline.segments.common.mail.email_imap_alert": { + "before": "✉ " + }, + "powerline.segments.common.env.virtualenv": { + "before": "ⓔ " + }, + "powerline.segments.common.wthr.weather": { + "args": { + "icons": { + "day": "〇", + "blustery": "⚑", + "rainy": "☔", + "cloudy": "☁", + "snowy": "❅", + "stormy": "☈", + "foggy": "≡", + "sunny": "☼", + "night": "☾", + "windy": "☴", + "not_available": "�", + "unknown": "⚠" + } + } + }, + "powerline.segments.common.time.fuzzy_time": { + "args": { + "unicode_text": true + } + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "ic": "I·COMP", + "ix": "I·C-X ", + "R": "RPLACE", + "Rv": "V·RPLC", + "Rc": "R·COMP", + "Rx": "R·C-X ", + "c": "COMMND", + "cv": "VIM·EX", + "ce": "NRM·EX", + "r": "PROMPT", + "rm": "-MORE-", + "r?": "CNFIRM", + "!": "!SHELL", + "t": "TERM " + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "↕{rows} ↔{vcols}", + "v_text_oneline": "↔{vcols}", + "v_text_multiline": "↕{rows}", + "V_text": "⇕{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } + } + } +} diff --git a/powerline/config_files/themes/powerline_terminus.json b/powerline/config_files/themes/powerline_terminus.json new file mode 100644 index 0000000..e5fb1c8 --- /dev/null +++ b/powerline/config_files/themes/powerline_terminus.json @@ -0,0 +1,151 @@ +{ + "dividers": { + "left": { + "hard": " ", + "soft": " " + }, + "right": { + "hard": " ", + "soft": " " + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": " " + }, + "stash": { + "before": "ST " + }, + "cwd": { + "args": { + "ellipsis": "…" + } + }, + + "line_current_symbol": { + "contents": " " + }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + + "time": { + "before": "" + }, + + "powerline.segments.common.net.network_load": { + "args": { + "recv_format": "⇓ {value:>8}", + "sent_format": "⇑ {value:>8}" + } + }, + "powerline.segments.common.net.hostname": { + "before": " " + }, + "powerline.segments.common.bat.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥", + "online": "⚡︎", + "offline": " " + } + }, + "powerline.segments.common.sys.uptime": { + "before": "↑ " + }, + "powerline.segments.common.mail.email_imap_alert": { + "before": "MAIL " + }, + "powerline.segments.common.env.virtualenv": { + "before": "(e) " + }, + "powerline.segments.common.wthr.weather": { + "args": { + "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" + } + } + }, + "powerline.segments.common.time.fuzzy_time": { + "args": { + "unicode_text": true + } + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "ic": "I·COMP", + "ix": "I·C-X ", + "R": "RPLACE", + "Rv": "V·RPLC", + "Rc": "R·COMP", + "Rx": "R·C-X ", + "c": "COMMND", + "cv": "VIM·EX", + "ce": "NRM·EX", + "r": "PROMPT", + "rm": "-MORE-", + "r?": "CNFIRM", + "!": "!SHELL", + "t": "TERM " + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "↕{rows} ↔{vcols}", + "v_text_oneline": "↔{vcols}", + "v_text_multiline": "↕{rows}", + "V_text": "⇕{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } + } + } +} diff --git a/powerline/config_files/themes/powerline_unicode7.json b/powerline/config_files/themes/powerline_unicode7.json new file mode 100644 index 0000000..bd62826 --- /dev/null +++ b/powerline/config_files/themes/powerline_unicode7.json @@ -0,0 +1,165 @@ +{ + "dividers": { + "left": { + "hard": " ", + "soft": " " + }, + "right": { + "hard": " ", + "soft": " " + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": "🔀 " + }, + "stash": { + "before": "📝" + }, + "cwd": { + "args": { + "ellipsis": "⋯" + } + }, + + "line_current_symbol": { + "contents": " " + }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "⏵", + "pause": "⏸", + "stop": "⏹" + } + } + }, + + "time": { + "before": "🕐 " + }, + + "powerline.segments.common.net.network_load": { + "args": { + "recv_format": "⬇ {value:>8}", + "sent_format": "⬆ {value:>8}" + } + }, + "powerline.segments.common.net.hostname": { + "before": "🏠 " + }, + "powerline.segments.common.bat.battery": { + "args": { + "full_heart": "💙", + "empty_heart": "💛", + "online": "⚡️", + "offline": " " + } + }, + "powerline.segments.common.sys.uptime": { + "before": "⇑ " + }, + "powerline.segments.common.mail.email_imap_alert": { + "before": "✉ " + }, + "powerline.segments.common.env.virtualenv": { + "before": "🐍 " + }, + "powerline.segments.common.wthr.weather": { + "args": { + "icons": { + "tornado": "🌪", + "hurricane": "🌀", + "showers": "☔", + "scattered_showers": "☔", + "thunderstorms": "🌩", + "isolated_thunderstorms": "🌩", + "scattered_thunderstorms": "🌩", + "dust": "🌫", + "fog": "🌫", + "cold": "❄", + "partly_cloudy_day": "🌤", + "mostly_cloudy_day": "🌥", + "sun": "🌣", + "hot": "♨", + "day": "☀", + "blustery": "⚑", + "rainy": "☂", + "cloudy": "☁", + "snowy": "☃", + "stormy": "☈", + "foggy": "🌁", + "sunny": "🌣", + "night": "☾", + "windy": "☴", + "not_available": "�", + "unknown": "⚠" + } + } + }, + "powerline.segments.common.time.fuzzy_time": { + "args": { + "unicode_text": true + } + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "ic": "I·COMP", + "ix": "I·C-X ", + "R": "RPLACE", + "Rv": "V·RPLC", + "Rc": "R·COMP", + "Rx": "R·C-X ", + "c": "COMMND", + "cv": "VIM·EX", + "ce": "NRM·EX", + "r": "PROMPT", + "rm": "-MORE-", + "r?": "CNFIRM", + "!": "!SHELL", + "t": "TERM " + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "↕{rows} ↔{vcols}", + "v_text_oneline": "↔{vcols}", + "v_text_multiline": "↕{rows}", + "V_text": "⇕{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "🔏" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "🖫⃥" + } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } + } + } +} diff --git a/powerline/config_files/themes/shell/__main__.json b/powerline/config_files/themes/shell/__main__.json new file mode 100644 index 0000000..13ae942 --- /dev/null +++ b/powerline/config_files/themes/shell/__main__.json @@ -0,0 +1,14 @@ +{ + "segment_data": { + "hostname": { + "args": { + "only_if_ssh": true + } + }, + "cwd": { + "args": { + "dir_limit_depth": 3 + } + } + } +} diff --git a/powerline/config_files/themes/shell/continuation.json b/powerline/config_files/themes/shell/continuation.json new file mode 100644 index 0000000..9307fc0 --- /dev/null +++ b/powerline/config_files/themes/shell/continuation.json @@ -0,0 +1,12 @@ +{ + "default_module": "powerline.segments.shell", + "segments": { + "left": [ + { + "function": "continuation" + } + ], + "right": [ + ] + } +} diff --git a/powerline/config_files/themes/shell/default.json b/powerline/config_files/themes/shell/default.json new file mode 100644 index 0000000..38039d8 --- /dev/null +++ b/powerline/config_files/themes/shell/default.json @@ -0,0 +1,43 @@ +{ + "segments": { + "left": [ + { + "function": "powerline.segments.shell.mode" + }, + { + "function": "powerline.segments.common.net.hostname", + "priority": 10 + }, + { + "function": "powerline.segments.common.env.user", + "priority": 30 + }, + { + "function": "powerline.segments.common.env.virtualenv", + "priority": 50 + }, + { + "function": "powerline.segments.shell.cwd", + "priority": 10 + }, + { + "function": "powerline.segments.shell.jobnum", + "priority": 20 + } + ], + "right": [ + { + "function": "powerline.segments.shell.last_pipe_status", + "priority": 10 + }, + { + "function": "powerline.segments.common.vcs.stash", + "priority": 50 + }, + { + "function": "powerline.segments.common.vcs.branch", + "priority": 40 + } + ] + } +} diff --git a/powerline/config_files/themes/shell/default_leftonly.json b/powerline/config_files/themes/shell/default_leftonly.json new file mode 100644 index 0000000..b576273 --- /dev/null +++ b/powerline/config_files/themes/shell/default_leftonly.json @@ -0,0 +1,34 @@ +{ + "segments": { + "left": [ + { + "function": "powerline.segments.common.net.hostname", + "priority": 10 + }, + { + "function": "powerline.segments.common.env.user", + "priority": 30 + }, + { + "function": "powerline.segments.common.env.virtualenv", + "priority": 50 + }, + { + "function": "powerline.segments.common.vcs.branch", + "priority": 40 + }, + { + "function": "powerline.segments.shell.cwd", + "priority": 10 + }, + { + "function": "powerline.segments.shell.jobnum", + "priority": 20 + }, + { + "function": "powerline.segments.shell.last_pipe_status", + "priority": 10 + } + ] + } +} diff --git a/powerline/config_files/themes/shell/select.json b/powerline/config_files/themes/shell/select.json new file mode 100644 index 0000000..3d81408 --- /dev/null +++ b/powerline/config_files/themes/shell/select.json @@ -0,0 +1,13 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "contents": "Select variant", + "width": "auto", + "align": "r", + "highlight_groups": ["continuation:current"] + } + ] + } +} diff --git a/powerline/config_files/themes/tmux/default.json b/powerline/config_files/themes/tmux/default.json new file mode 100644 index 0000000..4532ced --- /dev/null +++ b/powerline/config_files/themes/tmux/default.json @@ -0,0 +1,28 @@ +{ + "segments": { + "right": [ + { + "function": "powerline.segments.common.sys.uptime", + "priority": 50 + }, + { + "function": "powerline.segments.common.sys.system_load", + "priority": 50 + }, + { + "function": "powerline.segments.common.time.date" + }, + { + "function": "powerline.segments.common.time.date", + "name": "time", + "args": { + "format": "%H:%M", + "istime": true + } + }, + { + "function": "powerline.segments.common.net.hostname" + } + ] + } +} diff --git a/powerline/config_files/themes/unicode.json b/powerline/config_files/themes/unicode.json new file mode 100644 index 0000000..0802852 --- /dev/null +++ b/powerline/config_files/themes/unicode.json @@ -0,0 +1,151 @@ +{ + "dividers": { + "left": { + "hard": "▌ ", + "soft": "│ " + }, + "right": { + "hard": " ▐", + "soft": " │" + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": "⎇ " + }, + "stash": { + "before": "⌆" + }, + "cwd": { + "args": { + "ellipsis": "⋯" + } + }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + + "line_current_symbol": { + "contents": " " + }, + + "time": { + "before": "⌚ " + }, + + "powerline.segments.common.net.network_load": { + "args": { + "recv_format": "⬇ {value:>8}", + "sent_format": "⬆ {value:>8}" + } + }, + "powerline.segments.common.net.hostname": { + "before": "⌂ " + }, + "powerline.segments.common.bat.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥", + "online": "⚡︎", + "offline": " " + } + }, + "powerline.segments.common.sys.uptime": { + "before": "⇑ " + }, + "powerline.segments.common.mail.email_imap_alert": { + "before": "✉ " + }, + "powerline.segments.common.env.virtualenv": { + "before": "ⓔ " + }, + "powerline.segments.common.wthr.weather": { + "args": { + "icons": { + "day": "〇", + "blustery": "⚑", + "rainy": "☔", + "cloudy": "☁", + "snowy": "❅", + "stormy": "☈", + "foggy": "≡", + "sunny": "☼", + "night": "☾", + "windy": "☴", + "not_available": "�", + "unknown": "⚠" + } + } + }, + "powerline.segments.common.time.fuzzy_time": { + "args": { + "unicode_text": true + } + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "ic": "I·COMP", + "ix": "I·C-X ", + "R": "RPLACE", + "Rv": "V·RPLC", + "Rc": "R·COMP", + "Rx": "R·C-X ", + "c": "COMMND", + "cv": "VIM·EX", + "ce": "NRM·EX", + "r": "PROMPT", + "rm": "-MORE-", + "r?": "CNFIRM", + "!": "!SHELL", + "t": "TERM " + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "↕{rows} ↔{vcols}", + "v_text_oneline": "↔{vcols}", + "v_text_multiline": "↕{rows}", + "V_text": "⇕{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "⊗" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } + } + } +} diff --git a/powerline/config_files/themes/unicode_terminus.json b/powerline/config_files/themes/unicode_terminus.json new file mode 100644 index 0000000..9c76985 --- /dev/null +++ b/powerline/config_files/themes/unicode_terminus.json @@ -0,0 +1,151 @@ +{ + "dividers": { + "left": { + "hard": "▌ ", + "soft": "│ " + }, + "right": { + "hard": " ▐", + "soft": " │" + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": "BR " + }, + "stash": { + "before": "ST " + }, + "cwd": { + "args": { + "ellipsis": "…" + } + }, + + "line_current_symbol": { + "contents": " " + }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + + "time": { + "before": "" + }, + + "powerline.segments.common.net.network_load": { + "args": { + "recv_format": "⇓ {value:>8}", + "sent_format": "⇑ {value:>8}" + } + }, + "powerline.segments.common.net.hostname": { + "before": "⌂ " + }, + "powerline.segments.common.bat.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥", + "online": "⚡︎", + "offline": " " + } + }, + "powerline.segments.common.sys.uptime": { + "before": "↑ " + }, + "powerline.segments.common.mail.email_imap_alert": { + "before": "MAIL " + }, + "powerline.segments.common.env.virtualenv": { + "before": "(e) " + }, + "powerline.segments.common.wthr.weather": { + "args": { + "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" + } + } + }, + "powerline.segments.common.time.fuzzy_time": { + "args": { + "unicode_text": true + } + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "ic": "I·COMP", + "ix": "I·C-X ", + "R": "RPLACE", + "Rv": "V·RPLC", + "Rc": "R·COMP", + "Rx": "R·C-X ", + "c": "COMMND", + "cv": "VIM·EX", + "ce": "NRM·EX", + "r": "PROMPT", + "rm": "-MORE-", + "r?": "CNFIRM", + "!": "!SHELL", + "t": "TERM " + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows} × {vcols}", + "v_text_oneline": "C:{vcols}", + "v_text_multiline": "L:{rows}", + "V_text": "L:{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "RO" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } + } + } +} diff --git a/powerline/config_files/themes/unicode_terminus_condensed.json b/powerline/config_files/themes/unicode_terminus_condensed.json new file mode 100644 index 0000000..421f5c8 --- /dev/null +++ b/powerline/config_files/themes/unicode_terminus_condensed.json @@ -0,0 +1,151 @@ +{ + "dividers": { + "left": { + "hard": "▌", + "soft": "│" + }, + "right": { + "hard": "▐", + "soft": "│" + } + }, + "spaces": 0, + "segment_data": { + "branch": { + "before": "B " + }, + "stash": { + "before": "S " + }, + "cwd": { + "args": { + "use_path_separator": true, + "ellipsis": "…" + } + }, + + "line_current_symbol": { + "contents": "" + }, + "player": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + + "time": { + "before": "" + }, + + "powerline.segments.common.net.network_load": { + "args": { + "recv_format": "⇓{value:>8}", + "sent_format": "⇑{value:>8}" + } + }, + "powerline.segments.common.net.hostname": { + "before": "⌂" + }, + "powerline.segments.common.bat.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥", + "online": "⚡︎", + "offline": " " + } + }, + "powerline.segments.common.sys.uptime": { + "before": "↑" + }, + "powerline.segments.common.mail.email_imap_alert": { + "before": "M " + }, + "powerline.segments.common.env.virtualenv": { + "before": "E " + }, + "powerline.segments.common.wthr.weather": { + "args": { + "icons": { + "day": "D", + "blustery": "W", + "rainy": "R", + "cloudy": "c", + "snowy": "*", + "stormy": "S", + "foggy": "f", + "sunny": "s", + "night": "N", + "windy": "w", + "not_available": "-", + "unknown": "!" + } + } + }, + "powerline.segments.common.time.fuzzy_time": { + "args": { + "unicode_text": true + } + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NML", + "no": "NOP", + "v": "VIS", + "V": "VLN", + "^V": "VBL", + "s": "SEL", + "S": "SLN", + "^S": "SBL", + "i": "INS", + "ic": "I-C", + "ix": "I^X", + "R": "REP", + "Rv": "VRP", + "Rc": "R-C", + "Rx": "R^X", + "c": "CMD", + "cv": "VEX", + "ce": " EX", + "r": "PRT", + "rm": "MOR", + "r?": "CON", + "!": " SH" + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows}×{vcols}", + "v_text_oneline": "↔{vcols}", + "v_text_multiline": "↕{rows}", + "V_text": "⇕{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "RO" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + }, + + "powerline.segments.i3wm.scratchpad": { + "args": { + "icons": { + "fresh": "●", + "changed": "○" + } + } + } + } +} diff --git a/powerline/config_files/themes/vim/__main__.json b/powerline/config_files/themes/vim/__main__.json new file mode 100644 index 0000000..7cd3305 --- /dev/null +++ b/powerline/config_files/themes/vim/__main__.json @@ -0,0 +1,10 @@ +{ + "segment_data": { + "line_percent": { + "args": { + "gradient": true + }, + "after": "%" + } + } +} diff --git a/powerline/config_files/themes/vim/cmdwin.json b/powerline/config_files/themes/vim/cmdwin.json new file mode 100644 index 0000000..e6a05b0 --- /dev/null +++ b/powerline/config_files/themes/vim/cmdwin.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "contents": "Command Line", + "highlight_groups": ["file_name"] + }, + { + "type": "string", + "highlight_groups": ["background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ] + } +} diff --git a/powerline/config_files/themes/vim/default.json b/powerline/config_files/themes/vim/default.json new file mode 100644 index 0000000..9b0c744 --- /dev/null +++ b/powerline/config_files/themes/vim/default.json @@ -0,0 +1,128 @@ +{ + "segments": { + "left": [ + { + "function": "mode", + "exclude_modes": ["nc"] + }, + { + "function": "visual_range", + "include_modes": ["v", "V", "^V", "s", "S", "^S"], + "priority": 10 + }, + { + "function": "paste_indicator", + "exclude_modes": ["nc"], + "priority": 10 + }, + { + "function": "powerline.segments.vim.plugin.capslock.capslock_indicator", + "include_modes": ["i", "R", "Rv"], + "priority": 10 + }, + { + "function": "branch", + "exclude_modes": ["nc"], + "priority": 30 + }, + { + "function": "readonly_indicator", + "draw_soft_divider": false, + "after": " " + }, + { + "function": "file_scheme", + "priority": 20 + }, + { + "function": "file_directory", + "priority": 40, + "draw_soft_divider": false + }, + { + "function": "file_name", + "draw_soft_divider": false + }, + { + "function": "file_vcs_status", + "before": " ", + "draw_soft_divider": false + }, + { + "function": "modified_indicator", + "before": " " + }, + { + "exclude_modes": ["i", "R", "Rv"], + "function": "trailing_whitespace", + "display": false, + "priority": 60 + }, + { + "exclude_modes": ["nc"], + "function": "powerline.segments.vim.plugin.syntastic.syntastic", + "priority": 50 + }, + { + "exclude_modes": ["nc"], + "function": "powerline.segments.vim.plugin.tagbar.current_tag", + "draw_soft_divider": false, + "priority": 50 + }, + { + "type": "string", + "highlight_groups": ["background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + { + "function": "file_format", + "draw_soft_divider": false, + "exclude_modes": ["nc"], + "priority": 60 + }, + { + "function": "file_encoding", + "exclude_modes": ["nc"], + "priority": 60 + }, + { + "function": "file_type", + "exclude_modes": ["nc"], + "priority": 60 + }, + { + "function": "line_percent", + "priority": 50, + "width": 4, + "align": "r" + }, + { + "function": "csv_col_current", + "priority": 30 + }, + { + "type": "string", + "name": "line_current_symbol", + "highlight_groups": ["line_current_symbol", "line_current"] + }, + { + "function": "line_current", + "draw_soft_divider": false, + "width": 3, + "align": "r" + }, + { + "function": "virtcol_current", + "draw_soft_divider": false, + "priority": 20, + "before": ":", + "width": 3, + "align": "l" + } + ] + } +} diff --git a/powerline/config_files/themes/vim/help.json b/powerline/config_files/themes/vim/help.json new file mode 100644 index 0000000..45c9458 --- /dev/null +++ b/powerline/config_files/themes/vim/help.json @@ -0,0 +1,36 @@ +{ + "segments": { + "left": [ + { + "function": "file_name", + "draw_soft_divider": false + }, + { + "type": "string", + "highlight_groups": ["background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + { + "function": "line_percent", + "priority": 30, + "width": 4, + "align": "r" + }, + { + "type": "string", + "name": "line_current_symbol", + "highlight_groups": ["line_current_symbol", "line_current"] + }, + { + "function": "line_current", + "draw_soft_divider": false, + "width": 3, + "align": "r" + } + ] + } +} diff --git a/powerline/config_files/themes/vim/plugin_commandt.json b/powerline/config_files/themes/vim/plugin_commandt.json new file mode 100644 index 0000000..dd6748f --- /dev/null +++ b/powerline/config_files/themes/vim/plugin_commandt.json @@ -0,0 +1,26 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "contents": "Command-T", + "highlight_groups": ["commandt:label"] + }, + { + "function": "powerline.segments.vim.plugin.commandt.finder" + }, + { + "function": "powerline.segments.vim.plugin.commandt.path" + }, + { + "type": "string", + "highlight_groups": ["commandt:background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + ] + } +} diff --git a/powerline/config_files/themes/vim/plugin_gundo-preview.json b/powerline/config_files/themes/vim/plugin_gundo-preview.json new file mode 100644 index 0000000..ad8432c --- /dev/null +++ b/powerline/config_files/themes/vim/plugin_gundo-preview.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["gundo:name", "file_name"], + "contents": "Undo diff" + }, + { + "type": "string", + "highlight_groups": ["gundo:background", "background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ] + } +} diff --git a/powerline/config_files/themes/vim/plugin_gundo.json b/powerline/config_files/themes/vim/plugin_gundo.json new file mode 100644 index 0000000..a03b256 --- /dev/null +++ b/powerline/config_files/themes/vim/plugin_gundo.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["gundo:name", "file_name"], + "contents": "Undo tree" + }, + { + "type": "string", + "highlight_groups": ["gundo:background", "background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ] + } +} diff --git a/powerline/config_files/themes/vim/plugin_nerdtree.json b/powerline/config_files/themes/vim/plugin_nerdtree.json new file mode 100644 index 0000000..896d393 --- /dev/null +++ b/powerline/config_files/themes/vim/plugin_nerdtree.json @@ -0,0 +1,17 @@ +{ + "default_module": "powerline.segments.vim.plugin.nerdtree", + "segments": { + "left": [ + { + "function": "nerdtree" + }, + { + "type": "string", + "highlight_groups": ["background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ] + } +} diff --git a/powerline/config_files/themes/vim/quickfix.json b/powerline/config_files/themes/vim/quickfix.json new file mode 100644 index 0000000..ae4d5a5 --- /dev/null +++ b/powerline/config_files/themes/vim/quickfix.json @@ -0,0 +1,40 @@ +{ + "segment_data": { + "buffer_name": { + "contents": "Location List" + } + }, + "segments": { + "left": [ + { + "type": "string", + "name": "buffer_name", + "highlight_groups": ["file_name"] + }, + { + "function": "window_title", + "draw_soft_divider": false + }, + { + "type": "string", + "highlight_groups": ["background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + { + "type": "string", + "name": "line_current_symbol", + "highlight_groups": ["line_current_symbol", "line_current"] + }, + { + "function": "line_current", + "draw_soft_divider": false, + "width": 3, + "align": "r" + } + ] + } +} diff --git a/powerline/config_files/themes/vim/tabline.json b/powerline/config_files/themes/vim/tabline.json new file mode 100644 index 0000000..1e3130e --- /dev/null +++ b/powerline/config_files/themes/vim/tabline.json @@ -0,0 +1,93 @@ +{ + "default_module": "powerline.segments.vim", + "segments": { + "left": [ + { + "type": "segment_list", + "function": "powerline.listers.vim.tablister", + "exclude_function": "single_tab", + "segments": [ + { + "function": "tab" + }, + { + "function": "tabnr", + "after": " ", + "priority": 5 + }, + { + "function": "file_directory", + "priority": 40 + }, + { + "function": "file_name", + "args": { + "display_no_file": true + }, + "priority": 10 + }, + { + "function": "tab_modified_indicator", + "priority": 5 + } + ] + }, + { + "function": "tab", + "args": { + "end": true + } + }, + { + "type": "segment_list", + "function": "powerline.listers.vim.bufferlister", + "include_function": "single_tab", + "segments": [ + { + "function": "bufnr", + "after": " ", + "priority": 5 + }, + { + "function": "file_directory", + "priority": 40 + }, + { + "function": "file_name", + "args": { + "display_no_file": true + }, + "priority": 10 + }, + { + "function": "modified_indicator", + "priority": 5 + } + ] + }, + { + "type": "string", + "highlight_groups": ["tab:background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + { + "type": "string", + "contents": "Bufs", + "name": "single_tab", + "highlight_groups": ["single_tab"], + "include_function": "single_tab" + }, + { + "type": "string", + "contents": "Tabs", + "name": "many_tabs", + "highlight_groups": ["many_tabs"], + "exclude_function": "single_tab" + } + ] + } +} diff --git a/powerline/config_files/themes/wm/default.json b/powerline/config_files/themes/wm/default.json new file mode 100644 index 0000000..579080d --- /dev/null +++ b/powerline/config_files/themes/wm/default.json @@ -0,0 +1,29 @@ +{ + "segments": { + "right": [ + { + "function": "powerline.segments.common.wthr.weather", + "priority": 50 + }, + { + "function": "powerline.segments.common.time.date" + }, + { + "function": "powerline.segments.common.time.date", + "name": "time", + "args": { + "format": "%H:%M", + "istime": true + } + }, + { + "function": "powerline.segments.common.mail.email_imap_alert", + "priority": 10, + "args": { + "username": "", + "password": "" + } + } + ] + } +} diff --git a/powerline/dist/systemd/powerline-daemon.service b/powerline/dist/systemd/powerline-daemon.service new file mode 100644 index 0000000..96b685d --- /dev/null +++ b/powerline/dist/systemd/powerline-daemon.service @@ -0,0 +1,10 @@ +[Unit] +Description=powerline-daemon - Daemon that improves powerline performance +Documentation=man:powerline-daemon(1) +Documentation=https://powerline.readthedocs.org/en/latest/ + +[Service] +ExecStart=/usr/bin/powerline-daemon --foreground + +[Install] +WantedBy=default.target diff --git a/powerline/ipython.py b/powerline/ipython.py new file mode 100644 index 0000000..cb84fc7 --- /dev/null +++ b/powerline/ipython.py @@ -0,0 +1,68 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline import Powerline +from powerline.lib.dict import mergedicts +from powerline.lib.unicode import string + + +class IPythonInfo(object): + def __init__(self, shell): + self._shell = shell + + @property + def prompt_count(self): + return self._shell.execution_count + + +# HACK: ipython tries to only leave us with plain ASCII +class RewriteResult(object): + def __init__(self, prompt): + self.prompt = string(prompt) + + def __str__(self): + return self.prompt + + def __add__(self, s): + if type(s) is not str: + try: + s = s.encode('utf-8') + except AttributeError: + raise NotImplementedError + return RewriteResult(self.prompt + s) + + +class IPythonPowerline(Powerline): + def init(self, **kwargs): + super(IPythonPowerline, self).init( + 'ipython', + use_daemon_threads=True, + **kwargs + ) + + def get_config_paths(self): + if self.config_paths: + return self.config_paths + else: + return super(IPythonPowerline, self).get_config_paths() + + def get_local_themes(self, local_themes): + return dict(((type, {'config': self.load_theme_config(name)}) for type, name in local_themes.items())) + + def load_main_config(self): + r = super(IPythonPowerline, self).load_main_config() + if self.config_overrides: + mergedicts(r, self.config_overrides) + return r + + def load_theme_config(self, name): + r = super(IPythonPowerline, self).load_theme_config(name) + if name in self.theme_overrides: + mergedicts(r, self.theme_overrides[name]) + return r + + def do_setup(self, wrefs): + for wref in wrefs: + obj = wref() + if obj is not None: + setattr(obj, 'powerline', self) diff --git a/powerline/lemonbar.py b/powerline/lemonbar.py new file mode 100644 index 0000000..b49f86b --- /dev/null +++ b/powerline/lemonbar.py @@ -0,0 +1,21 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline import Powerline +from powerline.lib.dict import mergedicts + + +class LemonbarPowerline(Powerline): + def init(self): + super(LemonbarPowerline, self).init(ext='wm', renderer_module='lemonbar') + + get_encoding = staticmethod(lambda: 'utf-8') + + def get_local_themes(self, local_themes): + if not local_themes: + return {} + + return dict(( + (key, {'config': self.load_theme_config(val)}) + for key, val in local_themes.items() + )) diff --git a/powerline/lib/__init__.py b/powerline/lib/__init__.py new file mode 100644 index 0000000..2a5fbd0 --- /dev/null +++ b/powerline/lib/__init__.py @@ -0,0 +1,28 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from functools import wraps + + +def wraps_saveargs(wrapped): + def dec(wrapper): + r = wraps(wrapped)(wrapper) + r.powerline_origin = getattr(wrapped, 'powerline_origin', wrapped) + return r + return dec + + +def add_divider_highlight_group(highlight_group): + def dec(func): + @wraps_saveargs(func) + def f(**kwargs): + r = func(**kwargs) + if r: + return [{ + 'contents': r, + 'divider_highlight_group': highlight_group, + }] + else: + return None + return f + return dec diff --git a/powerline/lib/config.py b/powerline/lib/config.py new file mode 100644 index 0000000..0c95e47 --- /dev/null +++ b/powerline/lib/config.py @@ -0,0 +1,218 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import json +import codecs + +from copy import deepcopy +from threading import Event, Lock +from collections import defaultdict + +from powerline.lib.threaded import MultiRunnedThread +from powerline.lib.watcher import create_file_watcher + + +def open_file(path): + return codecs.open(path, encoding='utf-8') + + +def load_json_config(config_file_path, load=json.load, open_file=open_file): + with open_file(config_file_path) as config_file_fp: + return load(config_file_fp) + + +class DummyWatcher(object): + def __call__(self, *args, **kwargs): + return False + + def watch(self, *args, **kwargs): + pass + + +class DeferredWatcher(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + self.calls = [] + + def __call__(self, *args, **kwargs): + self.calls.append(('__call__', args, kwargs)) + + def watch(self, *args, **kwargs): + self.calls.append(('watch', args, kwargs)) + + def unwatch(self, *args, **kwargs): + self.calls.append(('unwatch', args, kwargs)) + + def transfer_calls(self, watcher): + for attr, args, kwargs in self.calls: + getattr(watcher, attr)(*args, **kwargs) + + +class ConfigLoader(MultiRunnedThread): + def __init__(self, shutdown_event=None, watcher=None, watcher_type=None, load=load_json_config, run_once=False): + super(ConfigLoader, self).__init__() + self.shutdown_event = shutdown_event or Event() + if run_once: + self.watcher = DummyWatcher() + self.watcher_type = 'dummy' + else: + self.watcher = watcher or DeferredWatcher() + if watcher: + if not watcher_type: + raise ValueError('When specifying watcher you must also specify watcher type') + self.watcher_type = watcher_type + else: + self.watcher_type = 'deferred' + self._load = load + + self.pl = None + self.interval = None + + self.lock = Lock() + + self.watched = defaultdict(set) + self.missing = defaultdict(set) + self.loaded = {} + + def set_watcher(self, watcher_type, force=False): + if watcher_type == self.watcher_type: + return + watcher = create_file_watcher(self.pl, watcher_type) + with self.lock: + if self.watcher_type == 'deferred': + self.watcher.transfer_calls(watcher) + self.watcher = watcher + self.watcher_type = watcher_type + + def set_pl(self, pl): + self.pl = pl + + def set_interval(self, interval): + self.interval = interval + + def register(self, function, path): + '''Register function that will be run when file changes. + + :param function function: + Function that will be called when file at the given path changes. + :param str path: + Path that will be watched for. + ''' + with self.lock: + self.watched[path].add(function) + self.watcher.watch(path) + + def register_missing(self, condition_function, function, key): + '''Register any function that will be called with given key each + interval seconds (interval is defined at __init__). Its result is then + passed to ``function``, but only if the result is true. + + :param function condition_function: + Function which will be called each ``interval`` seconds. All + exceptions from it will be logged and ignored. IOError exception + will be ignored without logging. + :param function function: + Function which will be called if condition_function returns + something that is true. Accepts result of condition_function as an + argument. + :param str key: + Any value, it will be passed to condition_function on each call. + + Note: registered functions will be automatically removed if + condition_function results in something true. + ''' + with self.lock: + self.missing[key].add((condition_function, function)) + + def unregister_functions(self, removed_functions): + '''Unregister files handled by these functions. + + :param set removed_functions: + Set of functions previously passed to ``.register()`` method. + ''' + with self.lock: + for path, functions in list(self.watched.items()): + functions -= removed_functions + if not functions: + self.watched.pop(path) + self.loaded.pop(path, None) + + def unregister_missing(self, removed_functions): + '''Unregister files handled by these functions. + + :param set removed_functions: + Set of pairs (2-tuples) representing ``(condition_function, + function)`` function pairs previously passed as an arguments to + ``.register_missing()`` method. + ''' + with self.lock: + for key, functions in list(self.missing.items()): + functions -= removed_functions + if not functions: + self.missing.pop(key) + + def load(self, path): + try: + # No locks: GIL does what we need + return deepcopy(self.loaded[path]) + except KeyError: + r = self._load(path) + self.loaded[path] = deepcopy(r) + return r + + def update(self): + toload = [] + with self.lock: + for path, functions in self.watched.items(): + for function in functions: + try: + modified = self.watcher(path) + except OSError as e: + modified = True + self.exception('Error while running watcher for path {0}: {1}', path, str(e)) + else: + if modified: + toload.append(path) + if modified: + function(path) + with self.lock: + for key, functions in list(self.missing.items()): + for condition_function, function in list(functions): + try: + path = condition_function(key) + except IOError: + pass + except Exception as e: + self.exception('Error while running condition function for key {0}: {1}', key, str(e)) + else: + if path: + toload.append(path) + function(path) + functions.remove((condition_function, function)) + if not functions: + self.missing.pop(key) + for path in toload: + try: + self.loaded[path] = deepcopy(self._load(path)) + except Exception as e: + self.exception('Error while loading {0}: {1}', path, str(e)) + try: + self.loaded.pop(path) + except KeyError: + pass + try: + self.loaded.pop(path) + except KeyError: + pass + + def run(self): + while self.interval is not None and not self.shutdown_event.is_set(): + self.update() + self.shutdown_event.wait(self.interval) + + def exception(self, msg, *args, **kwargs): + if self.pl: + self.pl.exception(msg, prefix='config_loader', *args, **kwargs) + else: + raise diff --git a/powerline/lib/debug.py b/powerline/lib/debug.py new file mode 100755 index 0000000..515e8c4 --- /dev/null +++ b/powerline/lib/debug.py @@ -0,0 +1,97 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import gc +import sys + +from types import FrameType +from itertools import chain + + +# From http://code.activestate.com/recipes/523004-find-cyclical-references/ +def print_cycles(objects, outstream=sys.stdout, show_progress=False): + '''Find reference cycles + + :param list objects: + A list of objects to find cycles in. It is often useful to pass in + gc.garbage to find the cycles that are preventing some objects from + being garbage collected. + :param file outstream: + The stream for output. + :param bool show_progress: + If True, print the number of objects reached as they are found. + ''' + def print_path(path): + for i, step in enumerate(path): + # next “wraps around” + next = path[(i + 1) % len(path)] + + outstream.write(' %s -- ' % str(type(step))) + written = False + if isinstance(step, dict): + for key, val in step.items(): + if val is next: + outstream.write('[%s]' % repr(key)) + written = True + break + if key is next: + outstream.write('[key] = %s' % repr(val)) + written = True + break + elif isinstance(step, (list, tuple)): + for i, item in enumerate(step): + if item is next: + outstream.write('[%d]' % i) + written = True + elif getattr(type(step), '__getattribute__', None) in (object.__getattribute__, type.__getattribute__): + for attr in chain(dir(step), getattr(step, '__dict__', ())): + if getattr(step, attr, None) is next: + try: + outstream.write('%r.%s' % (step, attr)) + except TypeError: + outstream.write('.%s' % (step, attr)) + written = True + break + if not written: + outstream.write(repr(step)) + outstream.write(' ->\n') + outstream.write('\n') + + def recurse(obj, start, all, current_path): + if show_progress: + outstream.write('%d\r' % len(all)) + + all[id(obj)] = None + + referents = gc.get_referents(obj) + for referent in referents: + # If we’ve found our way back to the start, this is + # a cycle, so print it out + if referent is start: + try: + outstream.write('Cyclic reference: %r\n' % referent) + except TypeError: + try: + outstream.write('Cyclic reference: %i (%r)\n' % (id(referent), type(referent))) + except TypeError: + outstream.write('Cyclic reference: %i\n' % id(referent)) + print_path(current_path) + + # Don’t go back through the original list of objects, or + # through temporary references to the object, since those + # are just an artifact of the cycle detector itself. + elif referent is objects or isinstance(referent, FrameType): + continue + + # We haven’t seen this object before, so recurse + elif id(referent) not in all: + recurse(referent, start, all, current_path + (obj,)) + + for obj in objects: + # We are not interested in non-powerline cyclic references + try: + if not type(obj).__module__.startswith('powerline'): + continue + except AttributeError: + continue + recurse(obj, obj, {}, ()) diff --git a/powerline/lib/dict.py b/powerline/lib/dict.py new file mode 100644 index 0000000..c06ab30 --- /dev/null +++ b/powerline/lib/dict.py @@ -0,0 +1,88 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + + +REMOVE_THIS_KEY = object() + + +def mergeargs(argvalue, remove=False): + if not argvalue: + return None + r = {} + for subval in argvalue: + mergedicts(r, dict([subval]), remove=remove) + return r + + +def _clear_special_values(d): + '''Remove REMOVE_THIS_KEY values from dictionary + ''' + l = [d] + while l: + i = l.pop() + pops = [] + for k, v in i.items(): + if v is REMOVE_THIS_KEY: + pops.append(k) + elif isinstance(v, dict): + l.append(v) + for k in pops: + i.pop(k) + + +def mergedicts(d1, d2, remove=True): + '''Recursively merge two dictionaries + + First dictionary is modified in-place. + ''' + _setmerged(d1, d2) + for k in d2: + if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict): + mergedicts(d1[k], d2[k], remove) + elif remove and d2[k] is REMOVE_THIS_KEY: + d1.pop(k, None) + else: + if remove and isinstance(d2[k], dict): + _clear_special_values(d2[k]) + d1[k] = d2[k] + + +def mergedefaults(d1, d2): + '''Recursively merge two dictionaries, keeping existing values + + First dictionary is modified in-place. + ''' + for k in d2: + if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict): + mergedefaults(d1[k], d2[k]) + else: + d1.setdefault(k, d2[k]) + + +def _setmerged(d1, d2): + if hasattr(d1, 'setmerged'): + d1.setmerged(d2) + + +def mergedicts_copy(d1, d2): + '''Recursively merge two dictionaries. + + Dictionaries are not modified. Copying happens only if necessary. Assumes + that first dictionary supports .copy() method. + ''' + ret = d1.copy() + _setmerged(ret, d2) + for k in d2: + if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict): + ret[k] = mergedicts_copy(d1[k], d2[k]) + else: + ret[k] = d2[k] + return ret + + +def updated(d, *args, **kwargs): + '''Copy dictionary and update it with provided arguments + ''' + d = d.copy() + d.update(*args, **kwargs) + return d diff --git a/powerline/lib/encoding.py b/powerline/lib/encoding.py new file mode 100644 index 0000000..76a51d8 --- /dev/null +++ b/powerline/lib/encoding.py @@ -0,0 +1,125 @@ +# 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. + ''' + if hasattr(locale, 'LC_MESSAGES'): + return ( + locale.getlocale(locale.LC_MESSAGES)[1] + or locale.getdefaultlocale()[1] + or 'ascii' + ) + + return ( + 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. + ''' + if hasattr(locale, 'LC_MESSAGES'): + return ( + locale.getlocale(locale.LC_MESSAGES)[1] + or locale.getdefaultlocale()[1] + or 'latin1' + ) + + return ( + locale.getdefaultlocale()[1] + or 'latin1' + ) + + +def get_preferred_arguments_encoding(): + '''Get encoding that should be used for command-line arguments + + .. warning:: + Falls back to latin1 so that function is less likely to throw as + non-ASCII command-line arguments most likely contain non-ASCII + filenames and screwing them up due to unidentified locale is not much of + a problem. + ''' + return ( + 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' + ) + + +def get_unicode_writer(stream=sys.stdout, encoding=None, errors='replace'): + '''Get function which will write unicode string to the given stream + + Writing is done using encoding returned by + :py:func:`get_preferred_output_encoding`. + + :param file stream: + Stream to write to. Default value is :py:attr:`sys.stdout`. + :param str encoding: + Determines which encoding to use. If this argument is specified then + :py:func:`get_preferred_output_encoding` is not used. + :param str errors: + Determines what to do with characters which cannot be encoded. See + ``errors`` argument of :py:func:`codecs.encode`. + + :return: Callable which writes unicode string to the given stream using + the preferred output encoding. + ''' + encoding = encoding or get_preferred_output_encoding() + if sys.version_info < (3,) or not hasattr(stream, 'buffer'): + return lambda s: stream.write(s.encode(encoding, errors)) + else: + return lambda s: stream.buffer.write(s.encode(encoding, errors)) diff --git a/powerline/lib/humanize_bytes.py b/powerline/lib/humanize_bytes.py new file mode 100644 index 0000000..c98a117 --- /dev/null +++ b/powerline/lib/humanize_bytes.py @@ -0,0 +1,25 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from math import log + + +unit_list = tuple(zip(['', 'k', 'M', 'G', 'T', 'P'], [0, 0, 1, 2, 2, 2])) + + +def humanize_bytes(num, suffix='B', si_prefix=False): + '''Return a human friendly byte representation. + + Modified version from http://stackoverflow.com/questions/1094841 + ''' + if num == 0: + return '0 ' + suffix + div = 1000 if si_prefix else 1024 + exponent = min(int(log(num, div)) if num else 0, len(unit_list) - 1) + quotient = float(num) / div ** exponent + unit, decimals = unit_list[exponent] + if unit and not si_prefix: + unit = unit.upper() + 'i' + return ('{{quotient:.{decimals}f}} {{unit}}{{suffix}}' + .format(decimals=decimals) + .format(quotient=quotient, unit=unit, suffix=suffix)) diff --git a/powerline/lib/inotify.py b/powerline/lib/inotify.py new file mode 100644 index 0000000..8b74a7f --- /dev/null +++ b/powerline/lib/inotify.py @@ -0,0 +1,184 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os +import errno +import ctypes +import struct + +from ctypes.util import find_library + +from powerline.lib.encoding import get_preferred_file_name_encoding + + +__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + + +class INotifyError(Exception): + pass + + +_inotify = None + + +def load_inotify(): + ''' Initialize the inotify library ''' + global _inotify + if _inotify is None: + if hasattr(sys, 'getwindowsversion'): + # On windows abort before loading the C library. Windows has + # multiple, incompatible C runtimes, and we have no way of knowing + # if the one chosen by ctypes is compatible with the currently + # loaded one. + raise INotifyError('INotify not available on windows') + if sys.platform == 'darwin': + raise INotifyError('INotify not available on OS X') + if not hasattr(ctypes, 'c_ssize_t'): + raise INotifyError('You need python >= 2.7 to use inotify') + name = find_library('c') + if not name: + raise INotifyError('Cannot find C library') + libc = ctypes.CDLL(name, use_errno=True) + for function in ('inotify_add_watch', 'inotify_init1', 'inotify_rm_watch'): + if not hasattr(libc, function): + raise INotifyError('libc is too old') + # inotify_init1() + prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, use_errno=True) + init1 = prototype(('inotify_init1', libc), ((1, 'flags', 0),)) + + # inotify_add_watch() + 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), ( + (1, 'fd'), (1, 'pathname'), (1, 'mask'))) + + # inotify_rm_watch() + prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, use_errno=True) + rm_watch = prototype(('inotify_rm_watch', libc), ( + (1, 'fd'), (1, 'wd'))) + + # read() + 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), ( + (1, 'fd'), (1, 'buf'), (1, 'count'))) + _inotify = (init1, add_watch, rm_watch, read) + return _inotify + + +class INotify(object): + + # See <sys/inotify.h> for the flags defined below + + # Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH. + ACCESS = 0x00000001 # File was accessed. + MODIFY = 0x00000002 # File was modified. + ATTRIB = 0x00000004 # Metadata changed. + CLOSE_WRITE = 0x00000008 # Writtable file was closed. + CLOSE_NOWRITE = 0x00000010 # Unwrittable file closed. + OPEN = 0x00000020 # File was opened. + MOVED_FROM = 0x00000040 # File was moved from X. + MOVED_TO = 0x00000080 # File was moved to Y. + CREATE = 0x00000100 # Subfile was created. + DELETE = 0x00000200 # Subfile was deleted. + DELETE_SELF = 0x00000400 # Self was deleted. + MOVE_SELF = 0x00000800 # Self was moved. + + # Events sent by the kernel. + UNMOUNT = 0x00002000 # Backing fs was unmounted. + Q_OVERFLOW = 0x00004000 # Event queued overflowed. + IGNORED = 0x00008000 # File was ignored. + + # Helper events. + CLOSE = (CLOSE_WRITE | CLOSE_NOWRITE) # Close. + MOVE = (MOVED_FROM | MOVED_TO) # Moves. + + # Special flags. + ONLYDIR = 0x01000000 # Only watch the path if it is a directory. + DONT_FOLLOW = 0x02000000 # Do not follow a sym link. + EXCL_UNLINK = 0x04000000 # Exclude events on unlinked objects. + MASK_ADD = 0x20000000 # Add to the mask of an already existing watch. + ISDIR = 0x40000000 # Event occurred against dir. + ONESHOT = 0x80000000 # Only send event once. + + # All events which a program can wait on. + ALL_EVENTS = ( + ACCESS | MODIFY | ATTRIB | CLOSE_WRITE | CLOSE_NOWRITE | OPEN | + MOVED_FROM | MOVED_TO | CREATE | DELETE | DELETE_SELF | MOVE_SELF + ) + + # See <bits/inotify.h> + CLOEXEC = 0x80000 + NONBLOCK = 0x800 + + def __init__(self, cloexec=True, nonblock=True): + self._init1, self._add_watch, self._rm_watch, self._read = load_inotify() + flags = 0 + if cloexec: + flags |= self.CLOEXEC + if nonblock: + flags |= self.NONBLOCK + self._inotify_fd = self._init1(flags) + if self._inotify_fd == -1: + raise INotifyError(os.strerror(ctypes.get_errno())) + + self._buf = ctypes.create_string_buffer(5000) + self.fenc = get_preferred_file_name_encoding() + self.hdr = struct.Struct(b'iIII') + # We keep a reference to os to prevent it from being deleted + # during interpreter shutdown, which would lead to errors in the + # __del__ method + self.os = os + + def handle_error(self): + eno = ctypes.get_errno() + extra = '' + if eno == errno.ENOSPC: + extra = 'You may need to increase the inotify limits on your system, via /proc/sys/fs/inotify/max_user_*' + raise OSError(eno, self.os.strerror(eno) + str(extra)) + + def __del__(self): + # This method can be called during interpreter shutdown, which means we + # must do the absolute minimum here. Note that there could be running + # daemon threads that are trying to call other methods on this object. + try: + self.os.close(self._inotify_fd) + except (AttributeError, TypeError): + pass + + def close(self): + if hasattr(self, '_inotify_fd'): + self.os.close(self._inotify_fd) + del self.os + del self._add_watch + del self._rm_watch + del self._inotify_fd + + def read(self, get_name=True): + buf = [] + while True: + num = self._read(self._inotify_fd, self._buf, len(self._buf)) + if num == 0: + break + if num < 0: + en = ctypes.get_errno() + if en == errno.EAGAIN: + break # No more data + if en == errno.EINTR: + continue # Interrupted, try again + raise OSError(en, self.os.strerror(en)) + buf.append(self._buf.raw[:num]) + raw = b''.join(buf) + pos = 0 + lraw = len(raw) + while lraw - pos >= self.hdr.size: + wd, mask, cookie, name_len = self.hdr.unpack_from(raw, pos) + pos += self.hdr.size + name = None + if get_name: + name = raw[pos:pos + name_len].rstrip(b'\0') + pos += name_len + self.process_event(wd, mask, cookie, name) + + def process_event(self, *args): + raise NotImplementedError() diff --git a/powerline/lib/memoize.py b/powerline/lib/memoize.py new file mode 100644 index 0000000..cedbe45 --- /dev/null +++ b/powerline/lib/memoize.py @@ -0,0 +1,42 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from functools import wraps + +from powerline.lib.monotonic import monotonic + + +def default_cache_key(**kwargs): + return frozenset(kwargs.items()) + + +class memoize(object): + '''Memoization decorator with timeout.''' + def __init__(self, timeout, cache_key=default_cache_key, cache_reg_func=None): + self.timeout = timeout + self.cache_key = cache_key + self.cache = {} + self.cache_reg_func = cache_reg_func + + def __call__(self, func): + @wraps(func) + def decorated_function(**kwargs): + if self.cache_reg_func: + self.cache_reg_func(self.cache) + self.cache_reg_func = None + + key = self.cache_key(**kwargs) + try: + cached = self.cache.get(key, None) + except TypeError: + return func(**kwargs) + # Handle case when time() appears to be less then cached['time'] due + # to clock updates. Not applicable for monotonic clock, but this + # case is currently rare. + if cached is None or not (cached['time'] < monotonic() < cached['time'] + self.timeout): + cached = self.cache[key] = { + 'result': func(**kwargs), + 'time': monotonic(), + } + return cached['result'] + return decorated_function diff --git a/powerline/lib/monotonic.py b/powerline/lib/monotonic.py new file mode 100644 index 0000000..cd7c414 --- /dev/null +++ b/powerline/lib/monotonic.py @@ -0,0 +1,100 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + try: + # >=python-3.3, Unix + from time import clock_gettime + try: + # >={kernel}-sources-2.6.28 + from time import CLOCK_MONOTONIC_RAW as CLOCK_ID + except ImportError: + from time import CLOCK_MONOTONIC as CLOCK_ID + + monotonic = lambda: clock_gettime(CLOCK_ID) + except ImportError: + # >=python-3.3 + from time import monotonic +except ImportError: + import ctypes + import sys + + try: + if sys.platform == 'win32': + # Windows only + GetTickCount64 = ctypes.windll.kernel32.GetTickCount64 + GetTickCount64.restype = ctypes.c_ulonglong + + def monotonic(): + return GetTickCount64() / 1000 + + elif sys.platform == 'darwin': + # Mac OS X + from ctypes.util import find_library + + libc_name = find_library('c') + if not libc_name: + raise OSError + + libc = ctypes.CDLL(libc_name, use_errno=True) + + mach_absolute_time = libc.mach_absolute_time + mach_absolute_time.argtypes = () + mach_absolute_time.restype = ctypes.c_uint64 + + class mach_timebase_info_data_t(ctypes.Structure): + _fields_ = ( + ('numer', ctypes.c_uint32), + ('denom', ctypes.c_uint32), + ) + mach_timebase_info_data_p = ctypes.POINTER(mach_timebase_info_data_t) + + _mach_timebase_info = libc.mach_timebase_info + _mach_timebase_info.argtypes = (mach_timebase_info_data_p,) + _mach_timebase_info.restype = ctypes.c_int + + def mach_timebase_info(): + timebase = mach_timebase_info_data_t() + _mach_timebase_info(ctypes.byref(timebase)) + return (timebase.numer, timebase.denom) + + timebase = mach_timebase_info() + factor = timebase[0] / timebase[1] * 1e-9 + + def monotonic(): + return mach_absolute_time() * factor + else: + # linux only (no librt on OS X) + import os + + # See <bits/time.h> + CLOCK_MONOTONIC = 1 + CLOCK_MONOTONIC_RAW = 4 + + class timespec(ctypes.Structure): + _fields_ = ( + ('tv_sec', ctypes.c_long), + ('tv_nsec', ctypes.c_long) + ) + tspec = timespec() + + librt = ctypes.CDLL('librt.so.1', use_errno=True) + clock_gettime = librt.clock_gettime + clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)] + + if clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.pointer(tspec)) == 0: + # >={kernel}-sources-2.6.28 + clock_id = CLOCK_MONOTONIC_RAW + elif clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) == 0: + clock_id = CLOCK_MONOTONIC + else: + raise OSError + + def monotonic(): + if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) != 0: + errno_ = ctypes.get_errno() + raise OSError(errno_, os.strerror(errno_)) + return tspec.tv_sec + tspec.tv_nsec / 1e9 + + except: + from time import time as monotonic # NOQA diff --git a/powerline/lib/overrides.py b/powerline/lib/overrides.py new file mode 100644 index 0000000..3257d98 --- /dev/null +++ b/powerline/lib/overrides.py @@ -0,0 +1,80 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import json + +from powerline.lib.dict import REMOVE_THIS_KEY + + +def parse_value(s): + '''Convert string to Python object + + Rules: + + * Empty string means that corresponding key should be removed from the + dictionary. + * Strings that start with a minus, digit or with some character that starts + JSON collection or string object are parsed as JSON. + * JSON special values ``null``, ``true``, ``false`` (case matters) are + parsed as JSON. + * All other values are considered to be raw strings. + + :param str s: Parsed string. + + :return: Python object. + ''' + if not s: + return REMOVE_THIS_KEY + elif s[0] in '"{[0123456789-' or s in ('null', 'true', 'false'): + return json.loads(s) + else: + return s + + +def keyvaluesplit(s): + '''Split K1.K2=VAL into K1.K2 and parsed VAL + ''' + if '=' not in s: + raise TypeError('Option must look like option=json_value') + if s[0] == '_': + raise ValueError('Option names must not start with `_\'') + idx = s.index('=') + o = s[:idx] + val = parse_value(s[idx + 1:]) + return (o, val) + + +def parsedotval(s): + '''Parse K1.K2=VAL into {"K1":{"K2":VAL}} + + ``VAL`` is processed according to rules defined in :py:func:`parse_value`. + ''' + if type(s) is tuple: + o, val = s + val = parse_value(val) + else: + o, val = keyvaluesplit(s) + + keys = o.split('.') + if len(keys) > 1: + r = (keys[0], {}) + rcur = r[1] + for key in keys[1:-1]: + rcur[key] = {} + rcur = rcur[key] + rcur[keys[-1]] = val + return r + else: + return (o, val) + + +def parse_override_var(s): + '''Parse a semicolon-separated list of strings into a sequence of values + + Emits the same items in sequence as :py:func:`parsedotval` does. + ''' + return ( + parsedotval(item) + for item in s.split(';') + if item + ) diff --git a/powerline/lib/path.py b/powerline/lib/path.py new file mode 100644 index 0000000..49ff433 --- /dev/null +++ b/powerline/lib/path.py @@ -0,0 +1,18 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + + +def 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) diff --git a/powerline/lib/shell.py b/powerline/lib/shell.py new file mode 100644 index 0000000..2082e82 --- /dev/null +++ b/powerline/lib/shell.py @@ -0,0 +1,133 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os + +from subprocess import Popen, PIPE +from functools import partial + +from powerline.lib.encoding import get_preferred_input_encoding, get_preferred_output_encoding + + +if sys.platform.startswith('win32'): + # Prevent windows from launching consoles when calling commands + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx + Popen = partial(Popen, creationflags=0x08000000) + + +def run_cmd(pl, cmd, stdin=None, strip=True): + '''Run command and return its stdout, stripped + + If running command fails returns None and logs failure to ``pl`` argument. + + :param PowerlineLogger pl: + Logger used to log failures. + :param list cmd: + Command which will be run. + :param str stdin: + String passed to command. May be None. + :param bool strip: + True if the result should be stripped. + ''' + try: + p = Popen(cmd, shell=False, stdout=PIPE, stdin=PIPE) + except OSError as e: + pl.exception('Could not execute command ({0}): {1}', e, cmd) + return None + else: + stdout, err = p.communicate( + stdin if stdin is None else stdin.encode(get_preferred_output_encoding())) + stdout = stdout.decode(get_preferred_input_encoding()) + return stdout.strip() if strip else stdout + + +def asrun(pl, ascript): + '''Run the given AppleScript and return the standard output and error.''' + return run_cmd(pl, ['osascript', '-'], ascript) + + +def readlines(cmd, cwd): + '''Run command and read its output, line by line + + :param list cmd: + Command which will be run. + :param str cwd: + Working directory of the command which will be run. + ''' + p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd) + encoding = get_preferred_input_encoding() + p.stderr.close() + with p.stdout: + for line in p.stdout: + yield line[:-1].decode(encoding) + + +try: + from shutil import which +except ImportError: + # shutil.which was added in python-3.3. Here is what was added: + # Lib/shutil.py, commit 5abe28a9c8fe701ba19b1db5190863384e96c798 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + '''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 + file. + + ``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 + path. + ''' + # Check that a given file can be accessed with the correct mode. + # Additionally check that `file` is not a directory, as on Windows + # directories pass the os.access check. + def _access_check(fn, mode): + return ( + os.path.exists(fn) + and os.access(fn, mode) + and not os.path.isdir(fn) + ) + + # 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 + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get('PATH', os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == 'win32': + # The current directory takes precedence on Windows. + if os.curdir not in path: + path.insert(0, os.curdir) + + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get('PATHEXT', '').split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given 'python.exe'. + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # 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. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if normdir not in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None diff --git a/powerline/lib/threaded.py b/powerline/lib/threaded.py new file mode 100644 index 0000000..e5a6b3e --- /dev/null +++ b/powerline/lib/threaded.py @@ -0,0 +1,262 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from threading import Thread, Lock, Event +from types import MethodType + +from powerline.lib.monotonic import monotonic +from powerline.segments import Segment + + +class MultiRunnedThread(object): + daemon = True + + def __init__(self): + self.thread = None + + def is_alive(self): + return self.thread and self.thread.is_alive() + + def start(self): + self.shutdown_event.clear() + self.thread = Thread(target=self.run) + self.thread.daemon = self.daemon + self.thread.start() + + def join(self, *args, **kwargs): + if self.thread: + return self.thread.join(*args, **kwargs) + return None + + +class ThreadedSegment(Segment, MultiRunnedThread): + min_sleep_time = 0.1 + update_first = True + interval = 1 + daemon = False + + argmethods = ('render', 'set_state') + + def __init__(self): + super(ThreadedSegment, self).__init__() + self.run_once = True + self.crashed = False + self.crashed_value = None + self.update_value = None + self.updated = False + + def __call__(self, pl, update_first=True, **kwargs): + if self.run_once: + self.pl = pl + self.set_state(**kwargs) + update_value = self.get_update_value(True) + elif not self.is_alive(): + # Without this we will not have to wait long until receiving bug “I + # opened vim, but branch information is only shown after I move + # cursor”. + # + # If running once .update() is called in __call__. + self.start() + update_value = self.get_update_value(self.do_update_first) + else: + update_value = self.get_update_value(not self.updated) + + if self.crashed: + return self.crashed_value + + return self.render(update_value, update_first=update_first, pl=pl, **kwargs) + + def set_update_value(self): + try: + self.update_value = self.update(self.update_value) + except Exception as e: + self.exception('Exception while updating: {0}', str(e)) + self.crashed = True + except KeyboardInterrupt: + self.warn('Caught keyboard interrupt while updating') + self.crashed = True + else: + self.crashed = False + self.updated = True + + def get_update_value(self, update=False): + if update: + self.set_update_value() + return self.update_value + + def run(self): + if self.do_update_first: + start_time = monotonic() + while True: + self.shutdown_event.wait(max(self.interval - (monotonic() - start_time), self.min_sleep_time)) + if self.shutdown_event.is_set(): + break + start_time = monotonic() + self.set_update_value() + else: + while not self.shutdown_event.is_set(): + start_time = monotonic() + self.set_update_value() + self.shutdown_event.wait(max(self.interval - (monotonic() - start_time), self.min_sleep_time)) + + def shutdown(self): + self.shutdown_event.set() + if self.daemon and self.is_alive(): + # Give the worker thread a chance to shutdown, but don’t block for + # too long + self.join(0.01) + + def set_interval(self, interval=None): + # Allowing “interval” keyword in configuration. + # Note: Here **kwargs is needed to support foreign data, in subclasses + # it can be seen in a number of places in order to support + # .set_interval(). + interval = interval or getattr(self, 'interval') + self.interval = interval + + def set_state(self, interval=None, update_first=True, shutdown_event=None, **kwargs): + self.set_interval(interval) + self.shutdown_event = shutdown_event or Event() + self.do_update_first = update_first and self.update_first + self.updated = self.updated or (not self.do_update_first) + + def startup(self, pl, **kwargs): + self.run_once = False + self.pl = pl + self.daemon = pl.use_daemon_threads + + self.set_state(**kwargs) + + if not self.is_alive(): + self.start() + + def critical(self, *args, **kwargs): + self.pl.critical(prefix=self.__class__.__name__, *args, **kwargs) + + def exception(self, *args, **kwargs): + self.pl.exception(prefix=self.__class__.__name__, *args, **kwargs) + + def info(self, *args, **kwargs): + self.pl.info(prefix=self.__class__.__name__, *args, **kwargs) + + def error(self, *args, **kwargs): + self.pl.error(prefix=self.__class__.__name__, *args, **kwargs) + + def warn(self, *args, **kwargs): + self.pl.warn(prefix=self.__class__.__name__, *args, **kwargs) + + def debug(self, *args, **kwargs): + self.pl.debug(prefix=self.__class__.__name__, *args, **kwargs) + + def argspecobjs(self): + for name in self.argmethods: + try: + yield name, getattr(self, name) + except AttributeError: + pass + + def additional_args(self): + return (('interval', self.interval),) + + _omitted_args = { + 'render': (0,), + 'set_state': ('shutdown_event',), + } + + def omitted_args(self, name, method): + ret = self._omitted_args.get(name, ()) + if isinstance(getattr(self, name, None), MethodType): + ret = tuple((i + 1 if isinstance(i, int) else i for i in ret)) + return ret + + +class KwThreadedSegment(ThreadedSegment): + update_first = True + + argmethods = ('render', 'set_state', 'key', 'render_one') + + def __init__(self): + super(KwThreadedSegment, self).__init__() + self.updated = True + self.update_value = ({}, set()) + self.write_lock = Lock() + self.new_queries = [] + + @staticmethod + def key(**kwargs): + return frozenset(kwargs.items()) + + def render(self, update_value, update_first, key=None, after_update=False, **kwargs): + queries, crashed = update_value + if key is None: + key = self.key(**kwargs) + if key in crashed: + return self.crashed_value + + try: + update_state = queries[key][1] + except KeyError: + with self.write_lock: + self.new_queries.append(key) + if self.do_update_first or self.run_once: + if after_update: + self.error('internal error: value was not computed even though update_first was set') + update_state = None + else: + return self.render( + update_value=self.get_update_value(True), + update_first=False, + key=key, + after_update=True, + **kwargs + ) + else: + update_state = None + + return self.render_one(update_state, **kwargs) + + def update_one(self, crashed, updates, key): + try: + updates[key] = (monotonic(), self.compute_state(key)) + except Exception as e: + self.exception('Exception while computing state for {0!r}: {1}', key, str(e)) + crashed.add(key) + except KeyboardInterrupt: + self.warn('Interrupt while computing state for {0!r}', key) + crashed.add(key) + + def update(self, old_update_value): + updates = {} + crashed = set() + update_value = (updates, crashed) + queries = old_update_value[0] + + new_queries = self.new_queries + with self.write_lock: + self.new_queries = [] + + for key, (last_query_time, state) in queries.items(): + if last_query_time < monotonic() < last_query_time + self.interval: + updates[key] = (last_query_time, state) + else: + self.update_one(crashed, updates, key) + + for key in new_queries: + self.update_one(crashed, updates, key) + + return update_value + + def set_state(self, interval=None, update_first=True, shutdown_event=None, **kwargs): + self.set_interval(interval) + self.do_update_first = update_first and self.update_first + self.shutdown_event = shutdown_event or Event() + + @staticmethod + def render_one(update_state, **kwargs): + return update_state + + _omitted_args = { + 'render': ('update_value', 'key', 'after_update'), + 'set_state': ('shutdown_event',), + 'render_one': (0,), + } diff --git a/powerline/lib/unicode.py b/powerline/lib/unicode.py new file mode 100644 index 0000000..152bacd --- /dev/null +++ b/powerline/lib/unicode.py @@ -0,0 +1,283 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import codecs + +from unicodedata import east_asian_width, combining + +from powerline.lib.encoding import get_preferred_output_encoding + + +try: + from __builtin__ import unicode +except ImportError: + unicode = str + + +try: + from __builtin__ import unichr +except ImportError: + unichr = chr + + +if sys.maxunicode < 0x10FFFF: + _unichr = unichr + + def unichr(ch): + if ch <= sys.maxunicode: + return _unichr(ch) + else: + ch -= 0x10000 + return _unichr((ch >> 10) + 0xD800) + _unichr((ch & ((1 << 10) - 1)) + 0xDC00) + + +def u(s): + '''Return unicode instance assuming UTF-8 encoded string. + ''' + if type(s) is unicode: + return s + else: + 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): + '''Create new encode errors handling method similar to ``replace`` + + Like ``replace`` this method uses question marks in place of the characters + that cannot be represented in the requested encoding. Unlike ``replace`` the + amount of question marks is identical to the amount of display cells + offending character occupies. Thus encoding ``…`` (U+2026, HORIZONTAL + ELLIPSIS) to ``latin1`` will emit one question mark, but encoding ``A`` + (U+FF21, FULLWIDTH LATIN CAPITAL LETTER A) will emit two question marks. + + Since width of some characters depends on the terminal settings and + powerline knows how to respect them a single error handling method cannot be + used. Instead of it the generator function is used which takes ``strwidth`` + function (function that knows how to compute string width respecting all + needed settings) and emits new error handling method name. + + :param function strwidth: + Function that computs string width measured in display cells the string + occupies when displayed. + + :return: New error handling method name. + ''' + 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): + '''Return unicode instance without raising an exception. + + Order of assumptions: + * ASCII string or unicode object + * UTF-8 string + * Object with __str__() or __repr__() method that returns UTF-8 string or + unicode object (depending on python version) + * String in powerline.lib.encoding.get_preferred_output_encoding() encoding + * If everything failed use safe_unicode on last exception with which + everything failed + ''' + try: + try: + if type(s) is bytes: + return unicode(s, 'ascii') + else: + return unicode(s) + except UnicodeDecodeError: + try: + return unicode(s, 'utf-8') + except TypeError: + return unicode(str(s), 'utf-8') + except UnicodeDecodeError: + return unicode(s, get_preferred_output_encoding()) + except Exception as e: + return safe_unicode(e) + + +class FailedUnicode(unicode): + '''Builtin ``unicode`` subclass indicating fatal error + + If your code for some reason wants to determine whether `.render()` method + failed it should check returned string for being a FailedUnicode instance. + Alternatively you could subclass Powerline and override `.render()` method + to do what you like in place of catching the exception and returning + FailedUnicode. + ''' + pass + + +if sys.version_info < (3,): + def string(s): + if type(s) is not str: + return s.encode('utf-8') + else: + return s +else: + def string(s): + if type(s) is not str: + return s.decode('utf-8') + else: + return s + + +string.__doc__ = ( + '''Transform ``unicode`` or ``bytes`` object into ``str`` object + + On Python-2 this encodes ``unicode`` to ``bytes`` (which is ``str``) using + UTF-8 encoding; on Python-3 this decodes ``bytes`` to ``unicode`` (which is + ``str``) using UTF-8 encoding. + + Useful for functions that expect an ``str`` object in both unicode versions, + not caring about the semantic differences between them in Python-2 and + Python-3. + ''' +) + + +def surrogate_pair_to_character(high, low): + '''Transform a pair of surrogate codepoints to one codepoint + ''' + return 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00) + + +_strwidth_documentation = ( + '''Compute string width in display cells + + {0} + + :param dict width_data: + Dictionary which maps east_asian_width property values to strings + lengths. It is expected to contain the following keys and values (from + `East Asian Width annex <http://www.unicode.org/reports/tr11/>`_): + + === ====== =========================================================== + Key Value Description + === ====== =========================================================== + F 2 Fullwidth: all characters that are defined as Fullwidth in + the Unicode Standard [Unicode] by having a compatibility + decomposition of type <wide> to characters elsewhere in the + Unicode Standard that are implicitly narrow but unmarked. + H 1 Halfwidth: all characters that are explicitly defined as + Halfwidth in the Unicode Standard by having a compatibility + decomposition of type <narrow> to characters elsewhere in + the Unicode Standard that are implicitly wide but unmarked, + plus U+20A9 ₩ WON SIGN. + W 2 Wide: all other characters that are always wide. These + characters occur only in the context of East Asian + typography where they are wide characters (such as the + Unified Han Ideographs or Squared Katakana Symbols). This + category includes characters that have explicit halfwidth + counterparts. + Na 1 Narrow: characters that are always narrow and have explicit + fullwidth or wide counterparts. These characters are + implicitly narrow in East Asian typography and legacy + character sets because they have explicit fullwidth or wide + counterparts. All of ASCII is an example of East Asian + Narrow characters. + A 1 or 2 Ambigious: characters that may sometimes be wide and + sometimes narrow. Ambiguous characters require additional + information not contained in the character code to further + resolve their width. This information is usually defined in + terminal setting that should in turn respect glyphs widths + in used fonts. Also see :ref:`ambiwidth configuration + option <config-common-ambiwidth>`. + N 1 Neutral characters: character that does not occur in legacy + East Asian character sets. + === ====== =========================================================== + + :param unicode string: + String whose width will be calculated. + + :return: unsigned integer.''') + + +def strwidth_ucs_4(width_data, string): + return sum((( + ( + 0 + ) if combining(symbol) else ( + width_data[east_asian_width(symbol)] + ) + ) for symbol in string)) + + +strwidth_ucs_4.__doc__ = _strwidth_documentation.format( + '''This version of function expects that characters above 0xFFFF are + represented using one symbol. This is only the case in UCS-4 Python builds. + + .. note: + Even in UCS-4 Python builds it is possible to represent characters above + 0xFFFF using surrogate pairs. Characters represented this way are not + supported.''') + + +def strwidth_ucs_2(width_data, string): + return sum((( + ( + width_data[east_asian_width(string[i - 1] + symbol)] + ) if 0xDC00 <= ord(symbol) <= 0xDFFF else ( + 0 + ) if combining(symbol) or 0xD800 <= ord(symbol) <= 0xDBFF else ( + width_data[east_asian_width(symbol)] + ) + ) for i, symbol in enumerate(string))) + + +strwidth_ucs_2.__doc__ = _strwidth_documentation.format( + '''This version of function expects that characters above 0xFFFF are + represented using two symbols forming a surrogate pair, which is the only + option in UCS-2 Python builds. It still works correctly in UCS-4 Python + builds, but is slower then its UCS-4 counterpart.''') diff --git a/powerline/lib/url.py b/powerline/lib/url.py new file mode 100644 index 0000000..f25919c --- /dev/null +++ b/powerline/lib/url.py @@ -0,0 +1,17 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + from urllib.error import HTTPError # NOQA + from urllib.request import urlopen # NOQA + from urllib.parse import urlencode as urllib_urlencode # NOQA +except ImportError: + from urllib2 import urlopen, HTTPError # NOQA + from urllib import urlencode as urllib_urlencode # NOQA + + +def urllib_read(url): + try: + return urlopen(url, timeout=10).read().decode('utf-8') + except HTTPError: + return diff --git a/powerline/lib/vcs/__init__.py b/powerline/lib/vcs/__init__.py new file mode 100644 index 0000000..1b7f5e2 --- /dev/null +++ b/powerline/lib/vcs/__init__.py @@ -0,0 +1,276 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import errno + +from threading import Lock +from collections import defaultdict + +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): + if os.path.isdir(path): + yield path + while True: + if os.path.ismount(path): + break + old_path = path + path = os.path.dirname(path) + if path == old_path or not path: + break + yield path + + +_file_watcher = None + + +def file_watcher(create_watcher): + global _file_watcher + if _file_watcher is None: + _file_watcher = create_watcher() + return _file_watcher + + +_branch_watcher = None + + +def branch_watcher(create_watcher): + global _branch_watcher + if _branch_watcher is None: + _branch_watcher = create_watcher() + return _branch_watcher + + +branch_name_cache = {} +branch_lock = Lock() +file_status_lock = Lock() + + +def get_branch_name(directory, config_file, get_func, create_watcher): + global branch_name_cache + with branch_lock: + # Check if the repo directory was moved/deleted + fw = branch_watcher(create_watcher) + is_watched = fw.is_watching(directory) + try: + changed = fw(directory) + except OSError as e: + if getattr(e, 'errno', None) != errno.ENOENT: + raise + changed = True + if changed: + branch_name_cache.pop(config_file, None) + # Remove the watches for this repo + if is_watched: + fw.unwatch(directory) + fw.unwatch(config_file) + else: + # Check if the config file has changed + try: + changed = fw(config_file) + except OSError as e: + if getattr(e, 'errno', None) != errno.ENOENT: + raise + # Config file does not exist (happens for mercurial) + if config_file not in branch_name_cache: + branch_name_cache[config_file] = out_u(get_func(directory, config_file)) + if changed: + # Config file has changed or was not tracked + branch_name_cache[config_file] = out_u(get_func(directory, config_file)) + return branch_name_cache[config_file] + + +class FileStatusCache(dict): + def __init__(self): + self.dirstate_map = defaultdict(set) + self.ignore_map = defaultdict(set) + self.keypath_ignore_map = {} + + def update_maps(self, keypath, directory, dirstate_file, ignore_file_name, extra_ignore_files): + parent = keypath + ignore_files = set() + while parent != directory: + nparent = os.path.dirname(keypath) + if nparent == parent: + break + parent = nparent + ignore_files.add(join(parent, ignore_file_name)) + for f in extra_ignore_files: + ignore_files.add(f) + self.keypath_ignore_map[keypath] = ignore_files + for ignf in ignore_files: + self.ignore_map[ignf].add(keypath) + self.dirstate_map[dirstate_file].add(keypath) + + def invalidate(self, dirstate_file=None, ignore_file=None): + for keypath in self.dirstate_map[dirstate_file]: + self.pop(keypath, None) + for keypath in self.ignore_map[ignore_file]: + self.pop(keypath, None) + + def ignore_files(self, keypath): + for ignf in self.keypath_ignore_map[keypath]: + yield ignf + + +file_status_cache = FileStatusCache() + + +def get_file_status(directory, dirstate_file, file_path, ignore_file_name, get_func, create_watcher, extra_ignore_files=()): + global file_status_cache + 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) + + with file_status_lock: + # Optimize case of keypath not being cached + if keypath not in file_status_cache: + file_status_cache[keypath] = ans = get_func(directory, file_path) + return ans + + # Check if any relevant files have changed + file_changed = file_watcher(create_watcher) + changed = False + # Check if dirstate has changed + try: + changed = file_changed(dirstate_file) + except OSError as e: + if getattr(e, 'errno', None) != errno.ENOENT: + raise + # The .git index file does not exist for a new git repo + return get_func(directory, file_path) + + if changed: + # Remove all cached values for files that depend on this + # dirstate_file + file_status_cache.invalidate(dirstate_file=dirstate_file) + else: + # Check if the file itself has changed + try: + changed ^= file_changed(keypath) + except OSError as e: + if getattr(e, 'errno', None) != errno.ENOENT: + raise + # Do not call get_func again for a non-existant file + if keypath not in file_status_cache: + file_status_cache[keypath] = get_func(directory, file_path) + return file_status_cache[keypath] + + if changed: + file_status_cache.pop(keypath, None) + else: + # Check if one of the ignore files has changed + for ignf in file_status_cache.ignore_files(keypath): + try: + changed ^= file_changed(ignf) + except OSError as e: + if getattr(e, 'errno', None) != errno.ENOENT: + raise + if changed: + # Invalidate cache for all files that might be affected + # by this ignore file + file_status_cache.invalidate(ignore_file=ignf) + break + + try: + return file_status_cache[keypath] + except KeyError: + file_status_cache[keypath] = ans = get_func(directory, file_path) + return ans + + +class TreeStatusCache(dict): + def __init__(self, pl): + self.tw = create_tree_watcher(pl) + self.pl = pl + + def cache_and_get(self, key, status): + ans = self.get(key, self) + if ans is self: + ans = self[key] = status() + return ans + + def __call__(self, repo): + key = repo.directory + try: + if self.tw(key, ignore_event=getattr(repo, 'ignore_event', None)): + self.pop(key, None) + except OSError as e: + self.pl.warn('Failed to check {0} for changes, with error: {1}', key, str(e)) + return self.cache_and_get(key, repo.status) + + +_tree_status_cache = None + + +def tree_status(repo, pl): + global _tree_status_cache + if _tree_status_cache is None: + _tree_status_cache = TreeStatusCache(pl) + return _tree_status_cache(repo) + + +vcs_props = ( + ('git', '.git', os.path.exists), + ('mercurial', '.hg', os.path.isdir), + ('bzr', '.bzr', os.path.isdir), +) + + +vcs_props_bytes = [ + (vcs, vcs_dir.encode('ascii'), check) + for vcs, vcs_dir, check in vcs_props +] + + +def guess(path, create_watcher): + for directory in generate_directories(path): + for vcs, vcs_dir, check in (vcs_props_bytes if isinstance(path, bytes) else vcs_props): + repo_dir = os.path.join(directory, vcs_dir) + if check(repo_dir): + if os.path.isdir(repo_dir) and not os.access(repo_dir, os.X_OK): + continue + try: + if vcs not in globals(): + globals()[vcs] = getattr(__import__(str('powerline.lib.vcs'), fromlist=[str(vcs)]), str(vcs)) + return globals()[vcs].Repository(directory, create_watcher) + except: + pass + return None + + +def get_fallback_create_watcher(): + from powerline.lib.watcher import create_file_watcher + from powerline import get_fallback_logger + from functools import partial + return partial(create_file_watcher, get_fallback_logger(), 'auto') + + +def debug(): + '''Test run guess(), repo.branch() and repo.status() + + To use:: + python -c 'from powerline.lib.vcs import debug; debug()' some_file_to_watch. + ''' + import sys + dest = sys.argv[-1] + repo = guess(os.path.abspath(dest), get_fallback_create_watcher) + if repo is None: + print ('%s is not a recognized vcs repo' % dest) + raise SystemExit(1) + print ('Watching %s' % dest) + print ('Press Ctrl-C to exit.') + try: + while True: + if os.path.isdir(dest): + print ('Branch name: %s Status: %s' % (repo.branch(), repo.status())) + else: + print ('File status: %s' % repo.status(dest)) + raw_input('Press Enter to check again: ') + except KeyboardInterrupt: + pass + except EOFError: + pass diff --git a/powerline/lib/vcs/bzr.py b/powerline/lib/vcs/bzr.py new file mode 100644 index 0000000..e47d8b2 --- /dev/null +++ b/powerline/lib/vcs/bzr.py @@ -0,0 +1,108 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re + +from io import StringIO + +from bzrlib import (workingtree, status, library_state, trace, ui) + +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): + def write(self, arg): + if isinstance(arg, bytes): + arg = arg.decode(get_preferred_file_contents_encoding(), 'replace') + return super(CoerceIO, self).write(arg) + + +nick_pat = re.compile(br'nickname\s*=\s*(.+)') + + +def branch_name_from_config_file(directory, config_file): + ans = None + try: + with open(config_file, 'rb') as f: + for line in f: + m = nick_pat.match(line) + if m is not None: + ans = m.group(1).strip().decode(get_preferred_file_contents_encoding(), 'replace') + break + except Exception: + pass + return ans or os.path.basename(directory) + + +state = None + + +class Repository(object): + def __init__(self, directory, create_watcher): + self.directory = os.path.abspath(directory) + self.create_watcher = create_watcher + + def status(self, path=None): + '''Return status of repository or file. + + Without file argument: returns status of the repository: + + :'D?': dirty (tracked modified files: added, removed, deleted, modified), + :'?U': untracked-dirty (added, but not tracked files) + :None: clean (status is empty) + + With file argument: returns status of this file: The status codes are + those returned by bzr status -S + ''' + if path is not None: + return get_file_status( + directory=self.directory, + dirstate_file=join(self.directory, '.bzr', 'checkout', 'dirstate'), + file_path=path, + ignore_file_name='.bzrignore', + get_func=self.do_status, + create_watcher=self.create_watcher, + ) + return self.do_status(self.directory, path) + + def do_status(self, directory, path): + try: + return self._status(self.directory, path) + except Exception: + pass + + def _status(self, directory, path): + global state + if state is None: + state = library_state.BzrLibraryState(ui=ui.SilentUIFactory, trace=trace.DefaultConfig()) + buf = CoerceIO() + w = workingtree.WorkingTree.open(directory) + status.show_tree_status(w, specific_files=[path] if path else None, to_file=buf, short=True) + raw = buf.getvalue() + if not raw.strip(): + return + if path: + ans = raw[:2] + if ans == 'I ': # Ignored + ans = None + return ans + dirtied = untracked = ' ' + for line in raw.splitlines(): + if len(line) > 1 and line[1] in 'ACDMRIN': + dirtied = 'D' + elif line and line[0] == '?': + untracked = 'U' + ans = dirtied + untracked + return ans if ans.strip() else None + + def branch(self): + config_file = join(self.directory, '.bzr', 'branch', 'branch.conf') + return get_branch_name( + directory=self.directory, + config_file=config_file, + get_func=branch_name_from_config_file, + create_watcher=self.create_watcher, + ) diff --git a/powerline/lib/vcs/git.py b/powerline/lib/vcs/git.py new file mode 100644 index 0000000..9bdf56e --- /dev/null +++ b/powerline/lib/vcs/git.py @@ -0,0 +1,208 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re + +from powerline.lib.vcs import get_branch_name, get_file_status +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/(.+)') + + +def branch_name_from_config_file(directory, config_file): + try: + with open(config_file, 'rb') as f: + raw = f.read() + except EnvironmentError: + return os.path.basename(directory) + m = _ref_pat.match(raw) + if m is not None: + return m.group(1).decode(get_preferred_file_contents_encoding(), 'replace') + return raw[:7] + + +def git_directory(directory): + path = join(directory, '.git') + if os.path.isfile(path): + with open(path, 'rb') as f: + raw = f.read() + if not raw.startswith(b'gitdir: '): + raise IOError('invalid gitfile format') + raw = raw[8:] + if raw[-1:] == b'\n': + raw = raw[:-1] + if not isinstance(path, bytes): + raw = raw.decode(get_preferred_file_name_encoding()) + if not raw: + raise IOError('no path in gitfile') + return os.path.abspath(os.path.join(directory, raw)) + else: + return path + + +class GitRepository(object): + __slots__ = ('directory', 'create_watcher') + + def __init__(self, directory, create_watcher): + self.directory = os.path.abspath(directory) + self.create_watcher = create_watcher + + def status(self, path=None): + '''Return status of repository or file. + + Without file argument: returns status of the repository: + + :First column: working directory status (D: dirty / space) + :Second column: index status (I: index dirty / space) + :Third column: presence of untracked files (U: untracked files / space) + :None: repository clean + + With file argument: returns status of this file. Output is + equivalent to the first two columns of ``git status --porcelain`` + (except for merge statuses as they are not supported by libgit2). + ''' + if path: + gitd = git_directory(self.directory) + # 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 + # for some reason I cannot be bothered to figure out. + return get_file_status( + directory=self.directory, + dirstate_file=join(gitd, 'index'), + file_path=path, + ignore_file_name='.gitignore', + get_func=self.do_status, + create_watcher=self.create_watcher, + extra_ignore_files=tuple(join(gitd, x) for x in ('logs/HEAD', 'info/exclude')), + ) + return self.do_status(self.directory, path) + + def branch(self): + directory = git_directory(self.directory) + head = join(directory, 'HEAD') + return get_branch_name( + directory=directory, + config_file=head, + get_func=branch_name_from_config_file, + create_watcher=self.create_watcher, + ) + + +try: + import pygit2 as git + + class Repository(GitRepository): + @staticmethod + def ignore_event(path, name): + return False + + def stash(self): + try: + stashref = git.Repository(git_directory(self.directory)).lookup_reference('refs/stash') + except KeyError: + return 0 + return sum(1 for _ in stashref.log()) + + def do_status(self, directory, path): + if path: + try: + status = git.Repository(directory).status_file(path) + except (KeyError, ValueError): + return None + + if status == git.GIT_STATUS_CURRENT: + return None + else: + if status & git.GIT_STATUS_WT_NEW: + return '??' + if status & git.GIT_STATUS_IGNORED: + return '!!' + + if status & git.GIT_STATUS_INDEX_NEW: + index_status = 'A' + elif status & git.GIT_STATUS_INDEX_DELETED: + index_status = 'D' + elif status & git.GIT_STATUS_INDEX_MODIFIED: + index_status = 'M' + else: + index_status = ' ' + + if status & git.GIT_STATUS_WT_DELETED: + wt_status = 'D' + elif status & git.GIT_STATUS_WT_MODIFIED: + wt_status = 'M' + else: + wt_status = ' ' + + return index_status + wt_status + else: + wt_column = ' ' + index_column = ' ' + untracked_column = ' ' + for status in git.Repository(directory).status().values(): + if status & git.GIT_STATUS_WT_NEW: + untracked_column = 'U' + continue + + if status & (git.GIT_STATUS_WT_DELETED | git.GIT_STATUS_WT_MODIFIED): + wt_column = 'D' + + if status & ( + git.GIT_STATUS_INDEX_NEW + | git.GIT_STATUS_INDEX_MODIFIED + | git.GIT_STATUS_INDEX_DELETED + ): + index_column = 'I' + r = wt_column + index_column + untracked_column + return r if r != ' ' else None +except ImportError: + 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 + def ignore_event(path, name): + # Ignore changes to the index.lock file, since they happen + # frequently and dont indicate an actual change in the working tree + # status + return path.endswith('.git') and name == 'index.lock' + + def _gitcmd(self, directory, *args): + return readlines(('git',) + args, directory) + + def stash(self): + return sum(1 for _ in self._gitcmd(self.directory, 'stash', 'list')) + + def do_status(self, directory, path): + if path: + try: + return next(self._gitcmd(directory, 'status', '--porcelain', '--ignored', '--', path))[:2] + except StopIteration: + return None + else: + wt_column = ' ' + index_column = ' ' + untracked_column = ' ' + for line in self._gitcmd(directory, 'status', '--porcelain'): + if line[0] == '?': + untracked_column = 'U' + continue + elif line[0] == '!': + continue + + if line[0] != ' ': + index_column = 'I' + + if line[1] != ' ': + wt_column = 'D' + + r = wt_column + index_column + untracked_column + return r if r != ' ' else None diff --git a/powerline/lib/vcs/mercurial.py b/powerline/lib/vcs/mercurial.py new file mode 100644 index 0000000..09b6e0b --- /dev/null +++ b/powerline/lib/vcs/mercurial.py @@ -0,0 +1,88 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +import hglib + +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): + try: + with open(config_file, 'rb') as f: + raw = f.read() + return raw.decode(get_preferred_file_contents_encoding(), 'replace').strip() + except Exception: + return 'default' + + +class Repository(object): + __slots__ = ('directory', 'create_watcher') + + # hg status -> (powerline file status, repo status flag) + statuses = { + b'M': ('M', 1), b'A': ('A', 1), b'R': ('R', 1), b'!': ('D', 1), + b'?': ('U', 2), b'I': ('I', 0), b'C': ('', 0), + } + repo_statuses_str = (None, 'D ', ' U', 'DU') + + def __init__(self, directory, create_watcher): + self.directory = os.path.abspath(directory) + self.create_watcher = create_watcher + + def _repo(self, directory): + # Cannot create this object once and use always: when repository updates + # functions emit invalid results + return hglib.open(directory) + + def status(self, path=None): + '''Return status of repository or file. + + Without file argument: returns status of the repository: + + :'D?': dirty (tracked modified files: added, removed, deleted, modified), + :'?U': untracked-dirty (added, but not tracked files) + :None: clean (status is empty) + + With file argument: returns status of this file: `M`odified, `A`dded, + `R`emoved, `D`eleted (removed from filesystem, but still tracked), + `U`nknown, `I`gnored, (None)Clean. + ''' + if path: + return get_file_status( + directory=self.directory, + dirstate_file=join(self.directory, '.hg', 'dirstate'), + file_path=path, + ignore_file_name='.hgignore', + get_func=self.do_status, + create_watcher=self.create_watcher, + ) + return self.do_status(self.directory, path) + + def do_status(self, directory, path): + with self._repo(directory) as repo: + if path: + path = os.path.join(directory, path) + statuses = repo.status(include=path, all=True) + for status, paths in statuses: + if paths: + return self.statuses[status][0] + return None + else: + resulting_status = 0 + for status, paths in repo.status(all=True): + if paths: + resulting_status |= self.statuses[status][1] + return self.repo_statuses_str[resulting_status] + + def branch(self): + config_file = join(self.directory, '.hg', 'branch') + return get_branch_name( + directory=self.directory, + config_file=config_file, + get_func=branch_name_from_config_file, + create_watcher=self.create_watcher, + ) diff --git a/powerline/lib/watcher/__init__.py b/powerline/lib/watcher/__init__.py new file mode 100644 index 0000000..4fe9896 --- /dev/null +++ b/powerline/lib/watcher/__init__.py @@ -0,0 +1,76 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +from powerline.lib.watcher.stat import StatFileWatcher +from powerline.lib.watcher.inotify import INotifyFileWatcher +from powerline.lib.watcher.tree import TreeWatcher +from powerline.lib.watcher.uv import UvFileWatcher, UvNotFound +from powerline.lib.inotify import INotifyError + + +def create_file_watcher(pl, watcher_type='auto', expire_time=10): + '''Create an object that can watch for changes to specified files + + Use ``.__call__()`` method of the returned object to start watching the file + or check whether file has changed since last call. + + Use ``.unwatch()`` method of the returned object to stop watching the file. + + Uses inotify if available, then pyuv, otherwise tracks mtimes. expire_time + is the number of minutes after the last query for a given path for the + inotify watch for that path to be automatically removed. This conserves + kernel resources. + + :param PowerlineLogger pl: + Logger. + :param str watcher_type + One of ``inotify`` (linux only), ``uv``, ``stat``, ``auto``. Determines + what watcher will be used. ``auto`` will use ``inotify`` if available, + then ``libuv`` and then fall back to ``stat``. + :param int expire_time: + Number of minutes since last ``.__call__()`` before inotify watcher will + stop watching given file. + ''' + if watcher_type == 'stat': + pl.debug('Using requested stat-based watcher', prefix='watcher') + return StatFileWatcher() + if watcher_type == 'inotify': + # Explicitly selected inotify watcher: do not catch INotifyError then. + pl.debug('Using requested inotify watcher', prefix='watcher') + return INotifyFileWatcher(expire_time=expire_time) + elif watcher_type == 'uv': + pl.debug('Using requested uv watcher', prefix='watcher') + return UvFileWatcher() + + if sys.platform.startswith('linux'): + try: + pl.debug('Trying to use inotify watcher', prefix='watcher') + return INotifyFileWatcher(expire_time=expire_time) + except INotifyError: + pl.info('Failed to create inotify watcher', prefix='watcher') + + try: + pl.debug('Using libuv-based watcher') + return UvFileWatcher() + except UvNotFound: + pl.debug('Failed to import pyuv') + + pl.debug('Using stat-based watcher') + return StatFileWatcher() + + +def create_tree_watcher(pl, watcher_type='auto', expire_time=10): + '''Create an object that can watch for changes in specified directories + + :param PowerlineLogger pl: + Logger. + :param str watcher_type: + Watcher type. Currently the only supported types are ``inotify`` (linux + only), ``uv``, ``dummy`` and ``auto``. + :param int expire_time: + Number of minutes since last ``.__call__()`` before inotify watcher will + stop watching given file. + ''' + return TreeWatcher(pl, watcher_type, expire_time) diff --git a/powerline/lib/watcher/inotify.py b/powerline/lib/watcher/inotify.py new file mode 100644 index 0000000..4a134e8 --- /dev/null +++ b/powerline/lib/watcher/inotify.py @@ -0,0 +1,268 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import errno +import os +import ctypes + +from threading import RLock + +from powerline.lib.inotify import INotify +from powerline.lib.monotonic import monotonic +from powerline.lib.path import realpath + + +class INotifyFileWatcher(INotify): + def __init__(self, expire_time=10): + super(INotifyFileWatcher, self).__init__() + self.watches = {} + self.modified = {} + self.last_query = {} + self.lock = RLock() + self.expire_time = expire_time * 60 + + def expire_watches(self): + now = monotonic() + for path, last_query in tuple(self.last_query.items()): + if last_query - now > self.expire_time: + self.unwatch(path) + + def process_event(self, wd, mask, cookie, name): + if wd == -1 and (mask & self.Q_OVERFLOW): + # We missed some INOTIFY events, so we dont + # know the state of any tracked files. + for path in tuple(self.modified): + if os.path.exists(path): + self.modified[path] = True + else: + self.watches.pop(path, None) + self.modified.pop(path, None) + self.last_query.pop(path, None) + return + + for path, num in tuple(self.watches.items()): + if num == wd: + if mask & self.IGNORED: + self.watches.pop(path, None) + self.modified.pop(path, None) + self.last_query.pop(path, None) + else: + if mask & self.ATTRIB: + # The watched file could have had its inode changed, in + # which case we will not get any more events for this + # file, so re-register the watch. For example by some + # other file being renamed as this file. + try: + self.unwatch(path) + except OSError: + pass + try: + self.watch(path) + except OSError as e: + if getattr(e, 'errno', None) != errno.ENOENT: + raise + else: + self.modified[path] = True + else: + self.modified[path] = True + + def unwatch(self, path): + ''' Remove the watch for path. Raises an OSError if removing the watch + fails for some reason. ''' + path = realpath(path) + with self.lock: + self.modified.pop(path, None) + self.last_query.pop(path, None) + wd = self.watches.pop(path, None) + if wd is not None: + if self._rm_watch(self._inotify_fd, wd) != 0: + self.handle_error() + + def watch(self, path): + ''' Register a watch for the file/directory named path. Raises an OSError if path + does not exist. ''' + path = realpath(path) + with self.lock: + if path not in self.watches: + bpath = path if isinstance(path, bytes) else path.encode(self.fenc) + flags = self.MOVE_SELF | self.DELETE_SELF + buf = ctypes.c_char_p(bpath) + # Try watching path as a directory + wd = self._add_watch(self._inotify_fd, buf, flags | self.ONLYDIR) + if wd == -1: + eno = ctypes.get_errno() + if eno != errno.ENOTDIR: + self.handle_error() + # Try watching path as a file + flags |= (self.MODIFY | self.ATTRIB) + wd = self._add_watch(self._inotify_fd, buf, flags) + if wd == -1: + self.handle_error() + self.watches[path] = wd + self.modified[path] = False + + def is_watching(self, path): + with self.lock: + return realpath(path) in self.watches + + def __call__(self, path): + ''' Return True if path has been modified since the last call. Can + raise OSError if the path does not exist. ''' + path = realpath(path) + with self.lock: + self.last_query[path] = monotonic() + self.expire_watches() + if path not in self.watches: + # Try to re-add the watch, it will fail if the file does not + # exist/you dont have permission + self.watch(path) + return True + self.read(get_name=False) + if path not in self.modified: + # An ignored event was received which means the path has been + # automatically unwatched + return True + ans = self.modified[path] + if ans: + self.modified[path] = False + return ans + + def close(self): + with self.lock: + for path in tuple(self.watches): + try: + self.unwatch(path) + except OSError: + pass + super(INotifyFileWatcher, self).close() + + +class NoSuchDir(ValueError): + pass + + +class BaseDirChanged(ValueError): + pass + + +class DirTooLarge(ValueError): + def __init__(self, bdir): + ValueError.__init__(self, 'The directory {0} is too large to monitor. Try increasing the value in /proc/sys/fs/inotify/max_user_watches'.format(bdir)) + + +class INotifyTreeWatcher(INotify): + is_dummy = False + + def __init__(self, basedir, ignore_event=None): + super(INotifyTreeWatcher, self).__init__() + self.basedir = realpath(basedir) + self.watch_tree() + self.modified = True + self.ignore_event = (lambda path, name: False) if ignore_event is None else ignore_event + + def watch_tree(self): + self.watched_dirs = {} + self.watched_rmap = {} + try: + self.add_watches(self.basedir) + except OSError as e: + if e.errno == errno.ENOSPC: + raise DirTooLarge(self.basedir) + + def add_watches(self, base, top_level=True): + ''' Add watches for this directory and all its descendant directories, + recursively. ''' + base = realpath(base) + # There may exist a link which leads to an endless + # add_watches loop or to maximum recursion depth exceeded + if not top_level and base in self.watched_dirs: + return + try: + is_dir = self.add_watch(base) + except OSError as e: + if e.errno == errno.ENOENT: + # The entry could have been deleted between listdir() and + # add_watch(). + if top_level: + raise NoSuchDir('The dir {0} does not exist'.format(base)) + return + if e.errno == errno.EACCES: + # We silently ignore entries for which we dont have permission, + # unless they are the top level dir + if top_level: + raise NoSuchDir('You do not have permission to monitor {0}'.format(base)) + return + raise + else: + if is_dir: + try: + files = os.listdir(base) + except OSError as e: + if e.errno in (errno.ENOTDIR, errno.ENOENT): + # The dir was deleted/replaced between the add_watch() + # and listdir() + if top_level: + raise NoSuchDir('The dir {0} does not exist'.format(base)) + return + raise + for x in files: + self.add_watches(os.path.join(base, x), top_level=False) + elif top_level: + # The top level dir is a file, not good. + raise NoSuchDir('The dir {0} does not exist'.format(base)) + + def add_watch(self, path): + bpath = path if isinstance(path, bytes) else path.encode(self.fenc) + wd = self._add_watch( + self._inotify_fd, + ctypes.c_char_p(bpath), + + # Ignore symlinks and watch only directories + self.DONT_FOLLOW | self.ONLYDIR | + + self.MODIFY | self.CREATE | self.DELETE | + self.MOVE_SELF | self.MOVED_FROM | self.MOVED_TO | + self.ATTRIB | self.DELETE_SELF + ) + if wd == -1: + eno = ctypes.get_errno() + if eno == errno.ENOTDIR: + return False + raise OSError(eno, 'Failed to add watch for: {0}: {1}'.format(path, self.os.strerror(eno))) + self.watched_dirs[path] = wd + self.watched_rmap[wd] = path + return True + + def process_event(self, wd, mask, cookie, name): + if wd == -1 and (mask & self.Q_OVERFLOW): + # We missed some INOTIFY events, so we dont + # know the state of any tracked dirs. + self.watch_tree() + self.modified = True + return + path = self.watched_rmap.get(wd, None) + if path is not None: + if not self.ignore_event(path, name): + self.modified = True + if mask & self.CREATE: + # A new sub-directory might have been created, monitor it. + try: + if not isinstance(path, bytes): + name = name.decode(self.fenc) + self.add_watch(os.path.join(path, name)) + except OSError as e: + if e.errno == errno.ENOENT: + # Deleted before add_watch() + pass + elif e.errno == errno.ENOSPC: + raise DirTooLarge(self.basedir) + else: + raise + if (mask & self.DELETE_SELF or mask & self.MOVE_SELF) and path == self.basedir: + raise BaseDirChanged('The directory %s was moved/deleted' % path) + + def __call__(self): + self.read() + ret = self.modified + self.modified = False + return ret diff --git a/powerline/lib/watcher/stat.py b/powerline/lib/watcher/stat.py new file mode 100644 index 0000000..0c08971 --- /dev/null +++ b/powerline/lib/watcher/stat.py @@ -0,0 +1,44 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from threading import RLock + +from powerline.lib.path import realpath + + +class StatFileWatcher(object): + def __init__(self): + self.watches = {} + self.lock = RLock() + + def watch(self, path): + path = realpath(path) + with self.lock: + self.watches[path] = os.path.getmtime(path) + + def unwatch(self, path): + path = realpath(path) + with self.lock: + self.watches.pop(path, None) + + def is_watching(self, path): + with self.lock: + return realpath(path) in self.watches + + def __call__(self, path): + path = realpath(path) + with self.lock: + if path not in self.watches: + self.watches[path] = os.path.getmtime(path) + return True + mtime = os.path.getmtime(path) + if mtime != self.watches[path]: + self.watches[path] = mtime + return True + return False + + def close(self): + with self.lock: + self.watches.clear() diff --git a/powerline/lib/watcher/tree.py b/powerline/lib/watcher/tree.py new file mode 100644 index 0000000..7d2b83f --- /dev/null +++ b/powerline/lib/watcher/tree.py @@ -0,0 +1,90 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +from powerline.lib.monotonic import monotonic +from powerline.lib.inotify import INotifyError +from powerline.lib.path import realpath +from powerline.lib.watcher.inotify import INotifyTreeWatcher, DirTooLarge, NoSuchDir, BaseDirChanged +from powerline.lib.watcher.uv import UvTreeWatcher, UvNotFound + + +class DummyTreeWatcher(object): + is_dummy = True + + def __init__(self, basedir): + self.basedir = realpath(basedir) + + def __call__(self): + return False + + +class TreeWatcher(object): + def __init__(self, pl, watcher_type, expire_time): + self.watches = {} + self.last_query_times = {} + self.expire_time = expire_time * 60 + self.pl = pl + self.watcher_type = watcher_type + + def get_watcher(self, path, ignore_event): + if self.watcher_type == 'inotify': + return INotifyTreeWatcher(path, ignore_event=ignore_event) + if self.watcher_type == 'uv': + return UvTreeWatcher(path, ignore_event=ignore_event) + if self.watcher_type == 'dummy': + return DummyTreeWatcher(path) + # FIXME + if self.watcher_type == 'stat': + return DummyTreeWatcher(path) + if self.watcher_type == 'auto': + if sys.platform.startswith('linux'): + try: + return INotifyTreeWatcher(path, ignore_event=ignore_event) + except (INotifyError, DirTooLarge) as e: + if not isinstance(e, INotifyError): + self.pl.warn('Failed to watch path: {0} with error: {1}'.format(path, e)) + try: + return UvTreeWatcher(path, ignore_event=ignore_event) + except UvNotFound: + pass + return DummyTreeWatcher(path) + else: + raise ValueError('Unknown watcher type: {0}'.format(self.watcher_type)) + + def watch(self, path, ignore_event=None): + path = realpath(path) + w = self.get_watcher(path, ignore_event) + self.watches[path] = w + return w + + def expire_old_queries(self): + pop = [] + now = monotonic() + for path, lt in self.last_query_times.items(): + if now - lt > self.expire_time: + pop.append(path) + for path in pop: + del self.last_query_times[path] + + def __call__(self, path, ignore_event=None): + path = realpath(path) + self.expire_old_queries() + self.last_query_times[path] = monotonic() + w = self.watches.get(path, None) + if w is None: + try: + self.watch(path, ignore_event=ignore_event) + except NoSuchDir: + pass + return True + try: + return w() + except BaseDirChanged: + self.watches.pop(path, None) + return True + except DirTooLarge as e: + self.pl.warn(str(e)) + self.watches[path] = DummyTreeWatcher(path) + return False diff --git a/powerline/lib/watcher/uv.py b/powerline/lib/watcher/uv.py new file mode 100644 index 0000000..272db0f --- /dev/null +++ b/powerline/lib/watcher/uv.py @@ -0,0 +1,207 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from collections import defaultdict +from threading import RLock +from functools import partial +from threading import Thread +from errno import ENOENT + +from powerline.lib.path import realpath +from powerline.lib.encoding import get_preferred_file_name_encoding + + +class UvNotFound(NotImplementedError): + pass + + +pyuv = None +pyuv_version_info = None + + +def import_pyuv(): + global pyuv + global pyuv_version_info + if not pyuv: + try: + pyuv = __import__('pyuv') + except ImportError: + raise UvNotFound + else: + pyuv_version_info = tuple((int(c) for c in pyuv.__version__.split('.'))) + + +class UvThread(Thread): + daemon = True + + def __init__(self, loop): + self.uv_loop = loop + self.async_handle = pyuv.Async(loop, self._async_cb) + super(UvThread, self).__init__() + + def _async_cb(self, handle): + self.uv_loop.stop() + self.async_handle.close() + + def run(self): + self.uv_loop.run() + + def join(self): + self.async_handle.send() + return super(UvThread, self).join() + + +_uv_thread = None + + +def start_uv_thread(): + global _uv_thread + if _uv_thread is None: + loop = pyuv.Loop() + _uv_thread = UvThread(loop) + _uv_thread.start() + 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): + def __init__(self): + import_pyuv() + self.watches = {} + self.lock = RLock() + self.loop = start_uv_thread() + self.fenc = get_preferred_file_name_encoding() + if pyuv_version_info >= (1, 0): + self._start_watch = self._start_watch_1_x + else: + self._start_watch = self._start_watch_0_x + + def _start_watch_1_x(self, path): + handle = pyuv.fs.FSEvent(self.loop) + handle.start(path, 0, partial(self._record_event, path)) + self.watches[path] = handle + + def _start_watch_0_x(self, path): + self.watches[path] = pyuv.fs.FSEvent( + self.loop, + path, + partial(self._record_event, path), + pyuv.fs.UV_CHANGE | pyuv.fs.UV_RENAME + ) + + def watch(self, path): + path = normpath(path, self.fenc) + with self.lock: + if path not in self.watches: + try: + self._start_watch(path) + except pyuv.error.FSEventError as e: + code = e.args[0] + if code == pyuv.errno.UV_ENOENT: + raise OSError(ENOENT, 'No such file or directory: ' + path) + else: + raise + + def unwatch(self, path): + path = normpath(path, self.fenc) + with self.lock: + try: + watch = self.watches.pop(path) + except KeyError: + return + watch.close(partial(self._stopped_watching, path)) + + def is_watching(self, path): + with self.lock: + return normpath(path, self.fenc) in self.watches + + def __del__(self): + try: + lock = self.lock + except AttributeError: + pass + else: + with lock: + while self.watches: + path, watch = self.watches.popitem() + watch.close(partial(self._stopped_watching, path)) + + +class UvFileWatcher(UvWatcher): + def __init__(self): + super(UvFileWatcher, self).__init__() + self.events = defaultdict(list) + + def _record_event(self, path, fsevent_handle, filename, events, error): + with self.lock: + self.events[path].append(events) + if events | pyuv.fs.UV_RENAME: + if not os.path.exists(path): + self.watches.pop(path).close() + + def _stopped_watching(self, path, *args): + self.events.pop(path, None) + + def __call__(self, path): + path = normpath(path, self.fenc) + with self.lock: + events = self.events.pop(path, None) + if events: + return True + if path not in self.watches: + self.watch(path) + return True + return False + + +class UvTreeWatcher(UvWatcher): + is_dummy = False + + def __init__(self, basedir, ignore_event=None): + super(UvTreeWatcher, self).__init__() + self.ignore_event = ignore_event or (lambda path, name: False) + self.basedir = normpath(basedir, self.fenc) + self.modified = True + self.watch_directory(self.basedir) + + def watch_directory(self, path): + for root, dirs, files in os.walk(normpath(path, self.fenc)): + self.watch_one_directory(root) + + def watch_one_directory(self, dirname): + try: + self.watch(dirname) + except OSError: + pass + + def _stopped_watching(self, path, *args): + pass + + def _record_event(self, path, fsevent_handle, filename, events, error): + if not self.ignore_event(path, filename): + self.modified = True + if events == pyuv.fs.UV_CHANGE | pyuv.fs.UV_RENAME: + # Stat changes to watched directory are UV_CHANGE|UV_RENAME. It + # is weird. + pass + elif events | pyuv.fs.UV_RENAME: + if not os.path.isdir(path): + self.unwatch(path) + else: + full_name = os.path.join(path, filename) + if os.path.isdir(full_name): + # For some reason mkdir and rmdir both fall into this + # category + self.watch_directory(full_name) + + def __call__(self): + return self.__dict__.pop('modified', False) diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py new file mode 100644 index 0000000..8c68271 --- /dev/null +++ b/powerline/lint/__init__.py @@ -0,0 +1,625 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import logging + +from collections import defaultdict +from itertools import chain +from functools import partial + +from powerline import generate_config_finder, get_config_paths, load_config +from powerline.segments.vim import vim_modes +from powerline.lib.dict import mergedicts_copy +from powerline.lib.config import ConfigLoader +from powerline.lib.unicode import unicode +from powerline.lib.path import join +from powerline.lint.markedjson import load +from powerline.lint.markedjson.error import echoerr, EchoErr, MarkedError +from powerline.lint.checks import (check_matcher_func, check_ext, check_config, check_top_theme, + check_color, check_translated_group_name, check_group, + check_segment_module, check_exinclude_function, type_keys, + check_segment_function, check_args, get_one_segment_function, + check_highlight_groups, check_highlight_group, check_full_segment_data, + get_all_possible_functions, check_segment_data_key, register_common_name, + highlight_group_spec, check_log_file_level, check_logging_handler) +from powerline.lint.spec import Spec +from powerline.lint.context import Context + + +def open_file(path): + return open(path, 'rb') + + +def generate_json_config_loader(lhadproblem): + def load_json_config(config_file_path, load=load, open_file=open_file): + with open_file(config_file_path) as config_file_fp: + r, hadproblem = load(config_file_fp) + if hadproblem: + lhadproblem[0] = True + return r + return load_json_config + + +function_name_re = '^(\w+\.)*[a-zA-Z_]\w*$' + + +divider_spec = Spec().printable().len( + 'le', 3, (lambda value: 'Divider {0!r} is too large!'.format(value))).copy +ext_theme_spec = Spec().type(unicode).func(lambda *args: check_config('themes', *args)).copy +top_theme_spec = Spec().type(unicode).func(check_top_theme).copy +ext_spec = Spec( + colorscheme=Spec().type(unicode).func( + (lambda *args: check_config('colorschemes', *args)) + ), + theme=ext_theme_spec(), + top_theme=top_theme_spec().optional(), +).copy +gen_components_spec = (lambda *components: Spec().list(Spec().type(unicode).oneof(set(components)))) +log_level_spec = Spec().re('^[A-Z]+$').func( + (lambda value, *args: (True, True, not hasattr(logging, value))), + (lambda value: 'unknown debugging level {0}'.format(value)) +).copy +log_format_spec = Spec().type(unicode).copy +main_spec = (Spec( + common=Spec( + default_top_theme=top_theme_spec().optional(), + term_truecolor=Spec().type(bool).optional(), + term_escape_style=Spec().type(unicode).oneof(set(('auto', 'xterm', 'fbterm'))).optional(), + # Python is capable of loading from zip archives. Thus checking path + # only for existence of the path, not for it being a directory + paths=Spec().list( + (lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))), + (lambda value: 'path does not exist: {0}'.format(value)) + ).optional(), + log_file=Spec().either( + Spec().type(unicode).func( + ( + lambda value, *args: ( + True, + True, + not os.path.isdir(os.path.dirname(os.path.expanduser(value))) + ) + ), + (lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value))) + ), + Spec().list(Spec().either( + Spec().type(unicode, type(None)), + Spec().tuple( + Spec().re(function_name_re).func(check_logging_handler), + Spec().tuple( + Spec().type(list).optional(), + Spec().type(dict).optional(), + ), + log_level_spec().func(check_log_file_level).optional(), + log_format_spec().optional(), + ), + )) + ).optional(), + log_level=log_level_spec().optional(), + log_format=log_format_spec().optional(), + interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(), + reload_config=Spec().type(bool).optional(), + watcher=Spec().type(unicode).oneof(set(('auto', 'inotify', 'stat'))).optional(), + ).context_message('Error while loading common configuration (key {key})'), + ext=Spec( + vim=ext_spec().update( + components=gen_components_spec('statusline', 'tabline').optional(), + local_themes=Spec( + __tabline__=ext_theme_spec(), + ).unknown_spec( + Spec().re(function_name_re).func(partial(check_matcher_func, 'vim')), + ext_theme_spec() + ), + ).optional(), + ipython=ext_spec().update( + local_themes=Spec( + in2=ext_theme_spec(), + out=ext_theme_spec(), + rewrite=ext_theme_spec(), + ), + ).optional(), + shell=ext_spec().update( + components=gen_components_spec('tmux', 'prompt').optional(), + local_themes=Spec( + continuation=ext_theme_spec(), + select=ext_theme_spec(), + ), + ).optional(), + wm=ext_spec().update( + local_themes=Spec().unknown_spec( + Spec().re('^[0-9A-Za-z-]+$'), + ext_theme_spec() + ).optional(), + update_interval=Spec().cmp('gt', 0.0).optional(), + ).optional(), + ).unknown_spec( + check_ext, + ext_spec(), + ).context_message('Error while loading extensions configuration (key {key})'), +).context_message('Error while loading main configuration')) + +term_color_spec = Spec().unsigned().cmp('le', 255).copy +true_color_spec = Spec().re( + '^[0-9a-fA-F]{6}$', + (lambda value: '"{0}" is not a six-digit hexadecimal unsigned integer written as a string'.format(value)) +).copy +colors_spec = (Spec( + colors=Spec().unknown_spec( + Spec().ident(), + Spec().either( + Spec().tuple(term_color_spec(), true_color_spec()), + term_color_spec() + ) + ).context_message('Error while checking colors (key {key})'), + gradients=Spec().unknown_spec( + Spec().ident(), + Spec().tuple( + Spec().len('gt', 1).list(term_color_spec()), + Spec().len('gt', 1).list(true_color_spec()).optional(), + ) + ).context_message('Error while checking gradients (key {key})'), +).context_message('Error while loading colors configuration')) + + +color_spec = Spec().type(unicode).func(check_color).copy +name_spec = Spec().type(unicode).len('gt', 0).optional().copy +group_name_spec = Spec().ident().copy +group_spec = Spec().either(Spec( + fg=color_spec(), + bg=color_spec(), + attrs=Spec().list(Spec().type(unicode).oneof(set(('bold', 'italic', 'underline')))), +), group_name_spec().func(check_group)).copy +groups_spec = Spec().unknown_spec( + group_name_spec(), + group_spec(), +).context_message('Error while loading groups (key {key})').copy +colorscheme_spec = (Spec( + name=name_spec(), + groups=groups_spec(), +).context_message('Error while loading coloscheme')) +mode_translations_value_spec = Spec( + colors=Spec().unknown_spec( + color_spec(), + color_spec(), + ).optional(), + groups=Spec().unknown_spec( + group_name_spec().func(check_translated_group_name), + group_spec(), + ).optional(), +).copy +top_colorscheme_spec = (Spec( + name=name_spec(), + groups=groups_spec(), + mode_translations=Spec().unknown_spec( + Spec().type(unicode), + mode_translations_value_spec(), + ).optional().context_message('Error while loading mode translations (key {key})').optional(), +).context_message('Error while loading top-level coloscheme')) +vim_mode_spec = Spec().oneof(set(list(vim_modes) + ['nc', 'tab_nc', 'buf_nc'])).copy +vim_colorscheme_spec = (Spec( + name=name_spec(), + groups=groups_spec(), + mode_translations=Spec().unknown_spec( + vim_mode_spec(), + mode_translations_value_spec(), + ).optional().context_message('Error while loading mode translations (key {key})'), +).context_message('Error while loading vim colorscheme')) +shell_mode_spec = Spec().re('^(?:[\w\-]+|\.safe)$').copy +shell_colorscheme_spec = (Spec( + name=name_spec(), + groups=groups_spec(), + mode_translations=Spec().unknown_spec( + shell_mode_spec(), + mode_translations_value_spec(), + ).optional().context_message('Error while loading mode translations (key {key})'), +).context_message('Error while loading shell colorscheme')) + + +args_spec = Spec( + pl=Spec().error('pl object must be set by powerline').optional(), + segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(), +).unknown_spec(Spec(), Spec()).optional().copy +segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy +exinclude_spec = Spec().re(function_name_re).func(check_exinclude_function).copy +segment_spec_base = Spec( + name=Spec().re('^[a-zA-Z_]\w*$').optional(), + function=Spec().re(function_name_re).func(check_segment_function).optional(), + exclude_modes=Spec().list(vim_mode_spec()).optional(), + include_modes=Spec().list(vim_mode_spec()).optional(), + exclude_function=exinclude_spec().optional(), + include_function=exinclude_spec().optional(), + draw_hard_divider=Spec().type(bool).optional(), + draw_soft_divider=Spec().type(bool).optional(), + draw_inner_divider=Spec().type(bool).optional(), + display=Spec().type(bool).optional(), + module=segment_module_spec(), + priority=Spec().type(int, float, type(None)).optional(), + after=Spec().printable().optional(), + before=Spec().printable().optional(), + width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(), + align=Spec().oneof(set('lr')).optional(), + args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_function, *args, **kwargs)), + contents=Spec().printable().optional(), + highlight_groups=Spec().list( + highlight_group_spec().re( + '^(?:(?!:divider$).)+$', + (lambda value: 'it is recommended that only divider highlight group names end with ":divider"') + ) + ).func(check_highlight_groups).optional(), + divider_highlight_group=highlight_group_spec().func(check_highlight_group).re( + ':divider$', + (lambda value: 'it is recommended that divider highlight group names end with ":divider"') + ).optional(), +).func(check_full_segment_data).copy +subsegment_spec = segment_spec_base().update( + type=Spec().oneof(set((key for key in type_keys if key != 'segment_list'))).optional(), +) +segment_spec = segment_spec_base().update( + type=Spec().oneof(type_keys).optional(), + segments=Spec().optional().list(subsegment_spec), +) +segments_spec = Spec().optional().list(segment_spec).copy +segdict_spec = Spec( + left=segments_spec().context_message('Error while loading segments from left side (key {key})'), + right=segments_spec().context_message('Error while loading segments from right side (key {key})'), +).func( + (lambda value, *args: (True, True, not (('left' in value) or ('right' in value)))), + (lambda value: 'segments dictionary must contain either left, right or both keys') +).context_message('Error while loading segments (key {key})').copy +divside_spec = Spec( + hard=divider_spec(), + soft=divider_spec(), +).copy +segment_data_value_spec = Spec( + after=Spec().printable().optional(), + before=Spec().printable().optional(), + display=Spec().type(bool).optional(), + args=args_spec().func(lambda *args, **kwargs: check_args(get_all_possible_functions, *args, **kwargs)), + contents=Spec().printable().optional(), +).copy +dividers_spec = Spec( + left=divside_spec(), + right=divside_spec(), +).copy +spaces_spec = Spec().unsigned().cmp( + 'le', 2, (lambda value: 'Are you sure you need such a big ({0}) number of spaces?'.format(value)) +).copy +common_theme_spec = Spec( + default_module=segment_module_spec().optional(), + cursor_space=Spec().type(int, float).cmp('le', 100).cmp('gt', 0).optional(), + cursor_columns=Spec().type(int).cmp('gt', 0).optional(), +).context_message('Error while loading theme').copy +top_theme_spec = common_theme_spec().update( + dividers=dividers_spec(), + spaces=spaces_spec(), + use_non_breaking_spaces=Spec().type(bool).optional(), + segment_data=Spec().unknown_spec( + Spec().func(check_segment_data_key), + segment_data_value_spec(), + ).optional().context_message('Error while loading segment data (key {key})'), +) +main_theme_spec = common_theme_spec().update( + dividers=dividers_spec().optional(), + spaces=spaces_spec().optional(), + segment_data=Spec().unknown_spec( + Spec().func(check_segment_data_key), + segment_data_value_spec(), + ).optional().context_message('Error while loading segment data (key {key})'), +) +theme_spec = common_theme_spec().update( + dividers=dividers_spec().optional(), + spaces=spaces_spec().optional(), + segment_data=Spec().unknown_spec( + Spec().func(check_segment_data_key), + segment_data_value_spec(), + ).optional().context_message('Error while loading segment data (key {key})'), + segments=segdict_spec().update(above=Spec().list(segdict_spec()).optional()), +) + + +def register_common_names(): + register_common_name('player', 'powerline.segments.common.players', '_player') + + +def load_json_file(path): + with open_file(path) as F: + try: + config, hadproblem = load(F) + except MarkedError as e: + return True, None, str(e) + else: + return hadproblem, config, None + + +def updated_with_config(d): + hadproblem, config, error = load_json_file(d['path']) + d.update( + hadproblem=hadproblem, + config=config, + error=error, + ) + return d + + +def find_all_ext_config_files(search_paths, subdir): + for config_root in search_paths: + top_config_subpath = join(config_root, subdir) + if not os.path.isdir(top_config_subpath): + if os.path.exists(top_config_subpath): + yield { + 'error': 'Path {0} is not a directory'.format(top_config_subpath), + 'path': top_config_subpath, + } + continue + for ext_name in os.listdir(top_config_subpath): + ext_path = os.path.join(top_config_subpath, ext_name) + if not os.path.isdir(ext_path): + if ext_name.endswith('.json') and os.path.isfile(ext_path): + yield updated_with_config({ + 'error': False, + 'path': ext_path, + 'name': ext_name[:-5], + 'ext': None, + 'type': 'top_' + subdir, + }) + else: + yield { + 'error': 'Path {0} is not a directory or configuration file'.format(ext_path), + 'path': ext_path, + } + continue + for config_file_name in os.listdir(ext_path): + config_file_path = os.path.join(ext_path, config_file_name) + if config_file_name.endswith('.json') and os.path.isfile(config_file_path): + yield updated_with_config({ + 'error': False, + 'path': config_file_path, + 'name': config_file_name[:-5], + 'ext': ext_name, + 'type': subdir, + }) + else: + yield { + 'error': 'Path {0} is not a configuration file'.format(config_file_path), + 'path': config_file_path, + } + + +def dict2(d): + return defaultdict(dict, ((k, dict(v)) for k, v in d.items())) + + +def check(paths=None, debug=False, echoerr=echoerr, require_ext=None): + '''Check configuration sanity + + :param list paths: + Paths from which configuration should be loaded. + :param bool debug: + Determines whether some information useful for debugging linter should + be output. + :param function echoerr: + Function that will be used to echo the error(s). Should accept four + optional keyword parameters: ``problem`` and ``problem_mark``, and + ``context`` and ``context_mark``. + :param str require_ext: + Require configuration for some extension to be present. + + :return: + ``False`` if user configuration seems to be completely sane and ``True`` + if some problems were found. + ''' + hadproblem = False + + register_common_names() + search_paths = paths or get_config_paths() + find_config_files = generate_config_finder(lambda: search_paths) + + logger = logging.getLogger('powerline-lint') + logger.setLevel(logging.DEBUG if debug else logging.ERROR) + logger.addHandler(logging.StreamHandler()) + + ee = EchoErr(echoerr, logger) + + if require_ext: + used_main_spec = main_spec.copy() + try: + used_main_spec['ext'][require_ext].required() + except KeyError: + used_main_spec['ext'][require_ext] = ext_spec() + else: + used_main_spec = main_spec + + lhadproblem = [False] + load_json_config = generate_json_config_loader(lhadproblem) + + config_loader = ConfigLoader(run_once=True, load=load_json_config) + + lists = { + 'colorschemes': set(), + 'themes': set(), + 'exts': set(), + } + found_dir = { + 'themes': False, + 'colorschemes': False, + } + config_paths = defaultdict(lambda: defaultdict(dict)) + loaded_configs = defaultdict(lambda: defaultdict(dict)) + for d in chain( + find_all_ext_config_files(search_paths, 'colorschemes'), + find_all_ext_config_files(search_paths, 'themes'), + ): + if d['error']: + hadproblem = True + ee(problem=d['error']) + continue + if d['hadproblem']: + hadproblem = True + if d['ext']: + found_dir[d['type']] = True + lists['exts'].add(d['ext']) + if d['name'] == '__main__': + pass + elif d['name'].startswith('__') or d['name'].endswith('__'): + hadproblem = True + ee(problem='File name is not supposed to start or end with “__”: {0}'.format( + d['path'])) + else: + lists[d['type']].add(d['name']) + config_paths[d['type']][d['ext']][d['name']] = d['path'] + loaded_configs[d['type']][d['ext']][d['name']] = d['config'] + else: + config_paths[d['type']][d['name']] = d['path'] + loaded_configs[d['type']][d['name']] = d['config'] + + for typ in ('themes', 'colorschemes'): + if not found_dir[typ]: + hadproblem = True + ee(problem='Subdirectory {0} was not found in paths {1}'.format(typ, ', '.join(search_paths))) + + diff = set(config_paths['colorschemes']) - set(config_paths['themes']) + if diff: + hadproblem = True + for ext in diff: + typ = 'colorschemes' if ext in config_paths['themes'] else 'themes' + if not config_paths['top_' + typ] or typ == 'themes': + ee(problem='{0} extension {1} not present in {2}'.format( + ext, + 'configuration' if ( + ext in loaded_configs['themes'] and ext in loaded_configs['colorschemes'] + ) else 'directory', + typ, + )) + + try: + main_config = load_config('config', find_config_files, config_loader) + except IOError: + main_config = {} + ee(problem='Configuration file not found: config.json') + hadproblem = True + except MarkedError as e: + main_config = {} + ee(problem=str(e)) + hadproblem = True + else: + if used_main_spec.match( + main_config, + data={'configs': config_paths, 'lists': lists}, + context=Context(main_config), + echoerr=ee + )[1]: + hadproblem = True + + import_paths = [os.path.expanduser(path) for path in main_config.get('common', {}).get('paths', [])] + + try: + colors_config = load_config('colors', find_config_files, config_loader) + except IOError: + colors_config = {} + ee(problem='Configuration file not found: colors.json') + hadproblem = True + except MarkedError as e: + colors_config = {} + ee(problem=str(e)) + hadproblem = True + else: + if colors_spec.match(colors_config, context=Context(colors_config), echoerr=ee)[1]: + hadproblem = True + + if lhadproblem[0]: + hadproblem = True + + top_colorscheme_configs = dict(loaded_configs['top_colorschemes']) + data = { + 'ext': None, + 'top_colorscheme_configs': top_colorscheme_configs, + 'ext_colorscheme_configs': {}, + 'colors_config': colors_config + } + for colorscheme, config in loaded_configs['top_colorschemes'].items(): + data['colorscheme'] = colorscheme + if top_colorscheme_spec.match(config, context=Context(config), data=data, echoerr=ee)[1]: + hadproblem = True + + ext_colorscheme_configs = dict2(loaded_configs['colorschemes']) + for ext, econfigs in ext_colorscheme_configs.items(): + data = { + 'ext': ext, + 'top_colorscheme_configs': top_colorscheme_configs, + 'ext_colorscheme_configs': ext_colorscheme_configs, + 'colors_config': colors_config, + } + for colorscheme, config in econfigs.items(): + data['colorscheme'] = colorscheme + if ext == 'vim': + spec = vim_colorscheme_spec + elif ext == 'shell': + spec = shell_colorscheme_spec + else: + spec = colorscheme_spec + if spec.match(config, context=Context(config), data=data, echoerr=ee)[1]: + hadproblem = True + + colorscheme_configs = {} + for ext in lists['exts']: + colorscheme_configs[ext] = {} + for colorscheme in lists['colorschemes']: + econfigs = ext_colorscheme_configs[ext] + ecconfigs = econfigs.get(colorscheme) + mconfigs = ( + top_colorscheme_configs.get(colorscheme), + econfigs.get('__main__'), + ecconfigs, + ) + if not (mconfigs[0] or mconfigs[2]): + continue + config = None + for mconfig in mconfigs: + if not mconfig: + continue + if config: + config = mergedicts_copy(config, mconfig) + else: + config = mconfig + colorscheme_configs[ext][colorscheme] = config + + theme_configs = dict2(loaded_configs['themes']) + top_theme_configs = dict(loaded_configs['top_themes']) + for ext, configs in theme_configs.items(): + data = { + 'ext': ext, + 'colorscheme_configs': colorscheme_configs, + 'import_paths': import_paths, + 'main_config': main_config, + 'top_themes': top_theme_configs, + 'ext_theme_configs': configs, + 'colors_config': colors_config + } + for theme, config in configs.items(): + data['theme'] = theme + if theme == '__main__': + data['theme_type'] = 'main' + spec = main_theme_spec + else: + data['theme_type'] = 'regular' + spec = theme_spec + if spec.match(config, context=Context(config), data=data, echoerr=ee)[1]: + hadproblem = True + + for top_theme, config in top_theme_configs.items(): + data = { + 'ext': None, + 'colorscheme_configs': colorscheme_configs, + 'import_paths': import_paths, + 'main_config': main_config, + 'theme_configs': theme_configs, + 'ext_theme_configs': None, + 'colors_config': colors_config + } + data['theme_type'] = 'top' + data['theme'] = top_theme + if top_theme_spec.match(config, context=Context(config), data=data, echoerr=ee)[1]: + hadproblem = True + + return hadproblem diff --git a/powerline/lint/checks.py b/powerline/lint/checks.py new file mode 100644 index 0000000..c2fcfc4 --- /dev/null +++ b/powerline/lint/checks.py @@ -0,0 +1,866 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re +import logging + +from collections import defaultdict + +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_groups', '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_groups', '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 = None + if ext: + def listed_key(d, k): + try: + return [d[k]] + except KeyError: + return [] + + if colorscheme == '__main__': + colorscheme_names = set(data['ext_colorscheme_configs'][ext]) + colorscheme_names.update(data['top_colorscheme_configs']) + colorscheme_names.discard('__main__') + configs = [ + ( + name, + listed_key(data['ext_colorscheme_configs'][ext], name) + + listed_key(data['ext_colorscheme_configs'][ext], '__main__') + + listed_key(data['top_colorscheme_configs'], name) + ) + for name in colorscheme_names + ] + else: + configs = [ + ( + colorscheme, + listed_key(data['ext_colorscheme_configs'][ext], colorscheme) + + listed_key(data['ext_colorscheme_configs'][ext], '__main__') + + listed_key(data['top_colorscheme_configs'], colorscheme) + ) + ] + else: + try: + configs = [(colorscheme, [data['top_colorscheme_configs'][colorscheme]])] + except KeyError: + pass + hadproblem = False + for new_colorscheme, config_lst in configs: + not_found = [] + new_data = data.copy() + new_data['colorscheme'] = new_colorscheme + for config in config_lst: + 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 + if not proceed: + break + if not_found and len(not_found) == len(config_lst): + echoerr( + context='Error while checking group definition in colorscheme (key {key})'.format( + key=context.key), + problem='name {0} is not present anywhere in {1} {2} {3} colorschemes: {4}'.format( + group, len(not_found), ext, new_colorscheme, ', '.join(not_found)), + problem_mark=group.mark + ) + hadproblem = True + 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_groups 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) + + +highlight_group_spec = Spec().ident().copy +_highlight_group_spec = highlight_group_spec().context_message( + 'Error while checking function documentation while checking theme (key {key})') + + +def check_hl_group_name(hl_group, context_mark, context, echoerr): + '''Check highlight group name: it should match naming conventions + + :param str hl_group: + Checked group. + :param Mark context_mark: + Context mark. May be ``None``. + :param Context context: + Current context. + :param func echoerr: + Function used for error reporting. + + :return: ``False`` if check succeeded and ``True`` if it failed. + ''' + return _highlight_group_spec.match(hl_group, context_mark=context_mark, context=context, echoerr=echoerr)[1] + + +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 + + hadproblem = False + + if func.__doc__: + NO_H_G_USED_STR = 'No highlight groups are used (literal segment).' + 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 + if hl_groups is None: + idx -= LHGUS + mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx) + echoerr( + context='Error while checking theme (key {key})'.format(key=context.key), + context_mark=function_name.mark, + problem=( + 'found highlight group definition in addition to sentense stating that ' + 'no highlight groups are used' + ), + problem_mark=mark, + ) + hadproblem = True + continue + 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) + elif NO_H_G_USED_STR in line: + idx = line.index(NO_H_G_USED_STR) + if hl_groups: + mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx) + echoerr( + context='Error while checking theme (key {key})'.format(key=context.key), + context_mark=function_name.mark, + problem=( + 'found sentense stating that no highlight groups are used ' + 'in addition to highlight group definition' + ), + problem_mark=mark, + ) + hadproblem = True + continue + hl_groups = None + pointer += len(line) + len('\n') + + 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), + context_mark=function_name.mark, + 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=divider_hl_group.mark, + ) + hadproblem = True + if check_hl_group_name(divider_hl_group, function_name.mark, context, echoerr): + 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)) + ) + if check_hl_group_name(hl_group, function_name.mark, context, echoerr): + hadproblem = True + 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 + elif hl_groups is not None: + 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_group_in_colorscheme(hl_group, cconfig, allow_gradients, data, context, echoerr): + havemarks(hl_group, cconfig) + if hl_group not in cconfig.get('groups', {}): + return False + elif not allow_gradients or allow_gradients == 'force': + group_config = cconfig['groups'][hl_group] + while isinstance(group_config, unicode): + try: + group_config = cconfig['groups'][group_config] + except KeyError: + # No such group. Error was already reported when checking + # colorschemes. + return True + havemarks(group_config) + hadgradient = False + for ckey in ('fg', 'bg'): + color = group_config.get(ckey) + if not color: + # No color. Error was already reported when checking + # colorschemes. + return True + 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 + ) + return False + 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 + ) + return False + return True + + +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 = [] + found = False + for colorscheme, cconfig in data['colorscheme_configs'][ext].items(): + if hl_group_in_colorscheme(hl_group, cconfig, allow_gradients, data, context, echoerr): + found = True + else: + r.append(colorscheme) + if not found: + pass + 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 + + +common_names = defaultdict(set) + + +def register_common_name(name, cmodule, cname): + s = cmodule + '.' + cname + cmodule_mark = Mark('<common name definition>', 1, 1, s, 1) + cname_mark = Mark('<common name definition>', 1, len(cmodule) + 1, s, len(cmodule) + 1) + common_names[name].add((MarkedUnicode(cmodule, cmodule_mark), MarkedUnicode(cname, cname_mark))) + + +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: + if name in common_names: + for cmodule, cname in common_names[name]: + cfunc = import_segment(cname, data, context, echoerr, module=MarkedUnicode(cmodule, None)) + if cfunc: + yield cfunc + 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 + + +def check_log_file_level(this_level, data, context, echoerr): + '''Check handler level specified in :ref:`log_file key <config-common-log>` + + This level must be greater or equal to the level in :ref:`log_level key + <config-common-log_level>`. + ''' + havemarks(this_level) + hadproblem = False + top_level = context[0][1].get('common', {}).get('log_level', 'WARNING') + top_level_str = top_level + top_level_mark = getattr(top_level, 'mark', None) + if ( + not isinstance(top_level, unicode) or not hasattr(logging, top_level) + or not isinstance(this_level, unicode) or not hasattr(logging, this_level) + ): + return True, False, hadproblem + top_level = getattr(logging, top_level) + this_level_str = this_level + this_level_mark = this_level.mark + this_level = getattr(logging, this_level) + if this_level < top_level: + echoerr( + context='Error while checking log level index (key {key})'.format( + key=context.key), + context_mark=this_level_mark, + problem='found level that is less critical then top level ({0} < {0})'.format( + this_level_str, top_level_str), + problem_mark=top_level_mark, + ) + hadproblem = True + return True, False, hadproblem + + +def check_logging_handler(handler_name, data, context, echoerr): + havemarks(handler_name) + import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])] + + handler_module, separator, handler_class = handler_name.rpartition('.') + if not separator: + handler_module = 'logging.handlers' + handler_class = handler_name + with WithPath(import_paths): + try: + handler = getattr(__import__(str(handler_module), fromlist=[str(handler_class)]), str(handler_class)) + except ImportError: + echoerr(context='Error while loading logger class (key {key})'.format(key=context.key), + problem='failed to load module {0}'.format(handler_module), + problem_mark=handler_name.mark) + return True, False, True + except AttributeError: + echoerr(context='Error while loading logger class (key {key})'.format(key=context.key), + problem='failed to load handler class {0}'.format(handler_class), + problem_mark=handler_name.mark) + return True, False, True + + if not issubclass(handler, logging.Handler): + echoerr(context='Error while loading logger class (key {key})'.format(key=context.key), + problem='loaded class {0} is not a logging.Handler subclass'.format(handler_class), + problem_mark=handler_name.mark) + return True, False, True + + return True, False, False diff --git a/powerline/lint/context.py b/powerline/lint/context.py new file mode 100644 index 0000000..a48a283 --- /dev/null +++ b/powerline/lint/context.py @@ -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) diff --git a/powerline/lint/imp.py b/powerline/lint/imp.py new file mode 100644 index 0000000..399654e --- /dev/null +++ b/powerline/lint/imp.py @@ -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.i3wm' and name == 'workspaces': + echoerr(context='Warning while checking segments (key {key})'.format(key=context.key), + context_mark=name.mark, + problem='segment {0} from {1} is deprecated'.format(name, module), + problem_mark=module.mark) + + with WithPath(data['import_paths']): + 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) diff --git a/powerline/lint/inspect.py b/powerline/lint/inspect.py new file mode 100644 index 0000000..15bb610 --- /dev/null +++ b/powerline/lint/inspect.py @@ -0,0 +1,63 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from inspect import ArgSpec, getargspec + +from powerline.segments import Segment + + +def getconfigargspec(obj): + if hasattr(obj, 'powerline_origin'): + obj = obj.powerline_origin + else: + obj = obj + + args = [] + defaults = [] + + if isinstance(obj, Segment): + additional_args = obj.additional_args() + argspecobjs = obj.argspecobjs() + get_omitted_args = obj.omitted_args + else: + additional_args = () + argspecobjs = ((None, obj),) + get_omitted_args = lambda *args: () + + for arg in additional_args: + args.append(arg[0]) + if len(arg) > 1: + defaults.append(arg[1]) + + requires_segment_info = hasattr(obj, 'powerline_requires_segment_info') + requires_filesystem_watcher = hasattr(obj, 'powerline_requires_filesystem_watcher') + + for name, method in argspecobjs: + argspec = getargspec(method) + omitted_args = get_omitted_args(name, method) + largs = len(argspec.args) + for i, arg in enumerate(reversed(argspec.args)): + if ( + largs - (i + 1) in omitted_args + or arg in omitted_args + or arg == 'pl' + or arg == 'self' + or (arg == 'create_watcher' and requires_filesystem_watcher) + or (arg == 'segment_info' and requires_segment_info) + ): + continue + if argspec.defaults and len(argspec.defaults) > i: + if arg in args: + idx = args.index(arg) + if len(args) - idx > len(defaults): + args.pop(idx) + else: + continue + default = argspec.defaults[-(i + 1)] + defaults.append(default) + args.append(arg) + else: + if arg not in args: + args.insert(0, arg) + + return ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults)) diff --git a/powerline/lint/markedjson/__init__.py b/powerline/lint/markedjson/__init__.py new file mode 100644 index 0000000..dea5faf --- /dev/null +++ b/powerline/lint/markedjson/__init__.py @@ -0,0 +1,19 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.lint.markedjson.loader import Loader + + +def load(stream, Loader=Loader): + '''Parse JSON value 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) + try: + r = loader.get_single_data() + return r, loader.haserrors + finally: + loader.dispose() diff --git a/powerline/lint/markedjson/composer.py b/powerline/lint/markedjson/composer.py new file mode 100644 index 0000000..bd5620d --- /dev/null +++ b/powerline/lint/markedjson/composer.py @@ -0,0 +1,119 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.lint.markedjson import nodes +from powerline.lint.markedjson import events +from powerline.lint.markedjson.error import MarkedError + + +__all__ = ['Composer', 'ComposerError'] + + +class ComposerError(MarkedError): + pass + + +class Composer: + def __init__(self): + pass + + def check_node(self): + # Drop the STREAM-START event. + if self.check_event(events.StreamStartEvent): + self.get_event() + + # If there are more documents available? + return not self.check_event(events.StreamEndEvent) + + def get_node(self): + # Get the root node of the next document. + if not self.check_event(events.StreamEndEvent): + return self.compose_document() + + def get_single_node(self): + # Drop the STREAM-START event. + self.get_event() + + # Compose a document if the stream is not empty. + document = None + if not self.check_event(events.StreamEndEvent): + document = self.compose_document() + + # Ensure that the stream contains no more documents. + if not self.check_event(events.StreamEndEvent): + event = self.get_event() + raise ComposerError( + 'expected a single document in the stream', + document.start_mark, + 'but found another document', + event.start_mark + ) + + # Drop the STREAM-END event. + self.get_event() + + return document + + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + + # Compose the root node. + node = self.compose_node(None, None) + + # Drop the DOCUMENT-END event. + self.get_event() + + return node + + def compose_node(self, parent, index): + self.descend_resolver(parent, index) + if self.check_event(events.ScalarEvent): + node = self.compose_scalar_node() + elif self.check_event(events.SequenceStartEvent): + node = self.compose_sequence_node() + elif self.check_event(events.MappingStartEvent): + node = self.compose_mapping_node() + self.ascend_resolver() + return node + + def compose_scalar_node(self): + event = self.get_event() + tag = event.tag + if tag is None or tag == '!': + tag = self.resolve(nodes.ScalarNode, event.value, event.implicit, event.start_mark) + node = nodes.ScalarNode(tag, event.value, event.start_mark, event.end_mark, style=event.style) + return node + + def compose_sequence_node(self): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(nodes.SequenceNode, None, start_event.implicit) + node = nodes.SequenceNode(tag, [], start_event.start_mark, None, flow_style=start_event.flow_style) + index = 0 + while not self.check_event(events.SequenceEndEvent): + node.value.append(self.compose_node(node, index)) + index += 1 + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + + def compose_mapping_node(self): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(nodes.MappingNode, None, start_event.implicit) + node = nodes.MappingNode(tag, [], start_event.start_mark, None, flow_style=start_event.flow_style) + while not self.check_event(events.MappingEndEvent): + # key_event = self.peek_event() + item_key = self.compose_node(node, None) + # if item_key in node.value: + # raise ComposerError('while composing a mapping', start_event.start_mark, + # 'found duplicate key', key_event.start_mark) + item_value = self.compose_node(node, item_key) + # node.value[item_key] = item_value + node.value.append((item_key, item_value)) + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node diff --git a/powerline/lint/markedjson/constructor.py b/powerline/lint/markedjson/constructor.py new file mode 100644 index 0000000..2a95d84 --- /dev/null +++ b/powerline/lint/markedjson/constructor.py @@ -0,0 +1,285 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import collections +import types + +from functools import wraps + +from powerline.lint.markedjson.error import MarkedError + +from powerline.lint.markedjson import nodes +from powerline.lint.markedjson.markedvalue import gen_marked_value +from powerline.lib.unicode import unicode + + +def marked(func): + @wraps(func) + def f(self, node, *args, **kwargs): + return gen_marked_value(func(self, node, *args, **kwargs), node.start_mark) + return f + + +class ConstructorError(MarkedError): + pass + + +class BaseConstructor: + yaml_constructors = {} + + def __init__(self): + self.constructed_objects = {} + self.state_generators = [] + self.deep_construct = False + + def check_data(self): + # If there are more documents available? + return self.check_node() + + def get_data(self): + # Construct and return the next document. + if self.check_node(): + return self.construct_document(self.get_node()) + + def get_single_data(self): + # Ensure that the stream contains a single document and construct it. + node = self.get_single_node() + if node is not None: + return self.construct_document(node) + return None + + def construct_document(self, node): + data = self.construct_object(node) + while self.state_generators: + state_generators = self.state_generators + self.state_generators = [] + for generator in state_generators: + for dummy in generator: + pass + self.constructed_objects = {} + self.deep_construct = False + return data + + def construct_object(self, node, deep=False): + if node in self.constructed_objects: + return self.constructed_objects[node] + if deep: + old_deep = self.deep_construct + self.deep_construct = True + constructor = None + tag_suffix = None + if node.tag in self.yaml_constructors: + constructor = self.yaml_constructors[node.tag] + else: + raise ConstructorError(None, None, 'no constructor for tag %s' % node.tag) + if tag_suffix is None: + data = constructor(self, node) + else: + data = constructor(self, tag_suffix, node) + if isinstance(data, types.GeneratorType): + generator = data + data = next(generator) + if self.deep_construct: + for dummy in generator: + pass + else: + self.state_generators.append(generator) + self.constructed_objects[node] = data + if deep: + self.deep_construct = old_deep + return data + + @marked + def construct_scalar(self, node): + if not isinstance(node, nodes.ScalarNode): + raise ConstructorError( + None, None, + 'expected a scalar node, but found %s' % node.id, + node.start_mark + ) + return node.value + + def construct_sequence(self, node, deep=False): + if not isinstance(node, nodes.SequenceNode): + raise ConstructorError( + None, None, + 'expected a sequence node, but found %s' % node.id, + node.start_mark + ) + return [ + self.construct_object(child, deep=deep) + for child in node.value + ] + + @marked + def construct_mapping(self, node, deep=False): + if not isinstance(node, nodes.MappingNode): + raise ConstructorError( + None, None, + 'expected a mapping node, but found %s' % node.id, + node.start_mark + ) + mapping = {} + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, collections.Hashable): + self.echoerr( + 'While constructing a mapping', node.start_mark, + 'found unhashable key', key_node.start_mark + ) + continue + elif type(key.value) != unicode: + self.echoerr( + 'Error while constructing a mapping', node.start_mark, + 'found key that is not a string', key_node.start_mark + ) + continue + elif key in mapping: + self.echoerr( + 'Error while constructing a mapping', node.start_mark, + 'found duplicate key', key_node.start_mark + ) + continue + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + + @classmethod + def add_constructor(cls, tag, constructor): + if 'yaml_constructors' not in cls.__dict__: + cls.yaml_constructors = cls.yaml_constructors.copy() + cls.yaml_constructors[tag] = constructor + + +class Constructor(BaseConstructor): + def construct_scalar(self, node): + if isinstance(node, nodes.MappingNode): + for key_node, value_node in node.value: + if key_node.tag == 'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return BaseConstructor.construct_scalar(self, node) + + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == 'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, nodes.MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, nodes.SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, nodes.MappingNode): + raise ConstructorError( + 'while constructing a mapping', + node.start_mark, + 'expected a mapping for merging, but found %s' % subnode.id, + subnode.start_mark + ) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError( + 'while constructing a mapping', + node.start_mark, + ('expected a mapping or list of mappings for merging, but found %s' % value_node.id), + value_node.start_mark + ) + elif key_node.tag == 'tag:yaml.org,2002:value': + key_node.tag = 'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value + + def construct_mapping(self, node, deep=False): + if isinstance(node, nodes.MappingNode): + self.flatten_mapping(node) + return BaseConstructor.construct_mapping(self, node, deep=deep) + + @marked + def construct_yaml_null(self, node): + self.construct_scalar(node) + return None + + @marked + def construct_yaml_bool(self, node): + value = self.construct_scalar(node).value + return bool(value) + + @marked + def construct_yaml_int(self, node): + value = self.construct_scalar(node).value + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + else: + return sign * int(value) + + @marked + def construct_yaml_float(self, node): + value = self.construct_scalar(node).value + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + else: + return sign * float(value) + + def construct_yaml_str(self, node): + return self.construct_scalar(node) + + def construct_yaml_seq(self, node): + data = gen_marked_value([], node.start_mark) + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = gen_marked_value({}, node.start_mark) + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_undefined(self, node): + raise ConstructorError( + None, None, + 'could not determine a constructor for the tag %r' % node.tag, + node.start_mark + ) + + +Constructor.add_constructor( + 'tag:yaml.org,2002:null', Constructor.construct_yaml_null) + +Constructor.add_constructor( + 'tag:yaml.org,2002:bool', Constructor.construct_yaml_bool) + +Constructor.add_constructor( + 'tag:yaml.org,2002:int', Constructor.construct_yaml_int) + +Constructor.add_constructor( + 'tag:yaml.org,2002:float', Constructor.construct_yaml_float) + +Constructor.add_constructor( + 'tag:yaml.org,2002:str', Constructor.construct_yaml_str) + +Constructor.add_constructor( + 'tag:yaml.org,2002:seq', Constructor.construct_yaml_seq) + +Constructor.add_constructor( + 'tag:yaml.org,2002:map', Constructor.construct_yaml_map) + +Constructor.add_constructor( + None, Constructor.construct_undefined) diff --git a/powerline/lint/markedjson/error.py b/powerline/lint/markedjson/error.py new file mode 100644 index 0000000..732120b --- /dev/null +++ b/powerline/lint/markedjson/error.py @@ -0,0 +1,241 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import re + +from powerline.lib.encoding import get_preferred_output_encoding + + +NON_PRINTABLE_STR = ( + '[^' + # ASCII control characters: 0x00-0x19 + + '\t\n' # Tab, newline: allowed ASCII control characters + + '\x20-\x7E' # ASCII printable characters + # Unicode control characters: 0x7F-0x9F + + '\u0085' # Allowed unicode control character: next line character + + '\u00A0-\uD7FF' + # Surrogate escapes: 0xD800-0xDFFF + + '\uE000-\uFFFD' + + (( + '\uD800-\uDFFF' + ) if sys.maxunicode < 0x10FFFF else ( + '\U00010000-\U0010FFFF' + )) + + ']' + + (( + # Paired surrogate escapes: allowed in UCS-2 builds as the only way to + # represent characters above 0xFFFF. Only paired variant is allowed. + '|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]' + + '|[\uD800-\uDBFF](?![\uDC00-\uDFFF])' + ) if sys.maxunicode < 0x10FFFF else ( + '' + )) +) +NON_PRINTABLE_RE = re.compile(NON_PRINTABLE_STR) + + +def repl(s): + return '<x%04x>' % ord(s.group()) + + +def strtrans(s): + return NON_PRINTABLE_RE.sub(repl, s.replace('\t', '>---')) + + +class Mark: + def __init__(self, name, line, column, buffer, pointer, old_mark=None, merged_marks=None): + self.name = name + self.line = line + self.column = column + self.buffer = buffer + self.pointer = pointer + self.old_mark = old_mark + self.merged_marks = merged_marks or [] + + def copy(self): + return Mark(self.name, self.line, self.column, self.buffer, self.pointer, self.old_mark, self.merged_marks[:]) + + def get_snippet(self, indent=4, max_length=75): + if self.buffer is None: + return None + head = '' + start = self.pointer + while start > 0 and self.buffer[start - 1] not in '\0\n': + start -= 1 + if self.pointer - start > max_length / 2 - 1: + head = ' ... ' + start += 5 + break + tail = '' + end = self.pointer + while end < len(self.buffer) and self.buffer[end] not in '\0\n': + end += 1 + if end - self.pointer > max_length / 2 - 1: + tail = ' ... ' + end -= 5 + break + snippet = [self.buffer[start:self.pointer], self.buffer[self.pointer], self.buffer[self.pointer + 1:end]] + snippet = [strtrans(s) for s in snippet] + return ( + ' ' * indent + head + ''.join(snippet) + tail + '\n' + + ' ' * (indent + len(head) + len(snippet[0])) + '^' + ) + + def advance_string(self, diff): + ret = self.copy() + # FIXME Currently does not work properly with escaped strings. + ret.column += diff + ret.pointer += diff + return ret + + def set_old_mark(self, old_mark): + if self is old_mark: + return + checked_marks = set([id(self)]) + older_mark = old_mark + while True: + if id(older_mark) in checked_marks: + raise ValueError('Trying to set recursive marks') + checked_marks.add(id(older_mark)) + older_mark = older_mark.old_mark + if not older_mark: + break + self.old_mark = old_mark + + def set_merged_mark(self, merged_mark): + self.merged_marks.append(merged_mark) + + def to_string(self, indent=0, head_text='in ', add_snippet=True): + mark = self + where = '' + processed_marks = set() + while mark: + indentstr = ' ' * indent + where += ('%s %s"%s", line %d, column %d' % ( + indentstr, head_text, mark.name, mark.line + 1, mark.column + 1)) + if add_snippet: + snippet = mark.get_snippet(indent=(indent + 4)) + if snippet: + where += ':\n' + snippet + if mark.merged_marks: + where += '\n' + indentstr + ' with additionally merged\n' + where += mark.merged_marks[0].to_string(indent + 4, head_text='', add_snippet=False) + for mmark in mark.merged_marks[1:]: + where += '\n' + indentstr + ' and\n' + where += mmark.to_string(indent + 4, head_text='', add_snippet=False) + if add_snippet: + processed_marks.add(id(mark)) + if mark.old_mark: + where += '\n' + indentstr + ' which replaced value\n' + indent += 4 + mark = mark.old_mark + if id(mark) in processed_marks: + raise ValueError('Trying to dump recursive mark') + return where + + if sys.version_info < (3,): + def __str__(self): + return self.to_string().encode('utf-8') + + def __unicode__(self): + return self.to_string() + else: + def __str__(self): + return self.to_string() + + def __eq__(self, other): + return self is other or ( + self.name == other.name + and self.line == other.line + and self.column == other.column + ) + + +if sys.version_info < (3,): + def echoerr(**kwargs): + stream = kwargs.pop('stream', sys.stderr) + stream.write('\n') + stream.write((format_error(**kwargs) + '\n').encode(get_preferred_output_encoding())) +else: + def echoerr(**kwargs): + stream = kwargs.pop('stream', sys.stderr) + stream.write('\n') + stream.write(format_error(**kwargs) + '\n') + + +def format_error(context=None, context_mark=None, problem=None, problem_mark=None, note=None, indent=0): + lines = [] + indentstr = ' ' * indent + if context is not None: + lines.append(indentstr + context) + if ( + context_mark is not None + and ( + problem is None or problem_mark is None + or context_mark != problem_mark + ) + ): + lines.append(context_mark.to_string(indent=indent)) + if problem is not None: + lines.append(indentstr + problem) + if problem_mark is not None: + lines.append(problem_mark.to_string(indent=indent)) + if note is not None: + lines.append(indentstr + note) + return '\n'.join(lines) + + +class MarkedError(Exception): + 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)) + + +class EchoErr(object): + __slots__ = ('echoerr', 'logger', 'indent') + + def __init__(self, echoerr, logger, indent=0): + self.echoerr = echoerr + self.logger = logger + self.indent = indent + + def __call__(self, **kwargs): + kwargs = kwargs.copy() + kwargs.setdefault('indent', self.indent) + self.echoerr(**kwargs) + + +class DelayedEchoErr(EchoErr): + __slots__ = ('echoerr', 'logger', 'errs', 'message', 'separator_message', 'indent', 'indent_shift') + + def __init__(self, echoerr, message='', separator_message=''): + super(DelayedEchoErr, self).__init__(echoerr, echoerr.logger) + self.errs = [[]] + self.message = message + self.separator_message = separator_message + self.indent_shift = (4 if message or separator_message else 0) + self.indent = echoerr.indent + self.indent_shift + + def __call__(self, **kwargs): + kwargs = kwargs.copy() + kwargs['indent'] = kwargs.get('indent', 0) + self.indent + self.errs[-1].append(kwargs) + + def next_variant(self): + self.errs.append([]) + + def echo_all(self): + if self.message: + self.echoerr(problem=self.message, indent=(self.indent - self.indent_shift)) + for variant in self.errs: + if not variant: + continue + if self.separator_message and variant is not self.errs[0]: + self.echoerr(problem=self.separator_message, indent=(self.indent - self.indent_shift)) + for kwargs in variant: + self.echoerr(**kwargs) + + def __nonzero__(self): + return not not self.errs + + __bool__ = __nonzero__ diff --git a/powerline/lint/markedjson/events.py b/powerline/lint/markedjson/events.py new file mode 100644 index 0000000..ef8a70e --- /dev/null +++ b/powerline/lint/markedjson/events.py @@ -0,0 +1,97 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + + +# Abstract classes. +class Event(object): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + + def __repr__(self): + attributes = [ + key for key in ['implicit', 'value'] + if hasattr(self, key) + ] + arguments = ', '.join([ + '%s=%r' % (key, getattr(self, key)) + for key in attributes + ]) + return '%s(%s)' % (self.__class__.__name__, arguments) + + +class NodeEvent(Event): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + + +class CollectionStartEvent(NodeEvent): + def __init__(self, implicit, start_mark=None, end_mark=None, flow_style=None): + self.tag = None + self.implicit = implicit + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + + +class CollectionEndEvent(Event): + pass + + +# Implementations. +class StreamStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + + +class StreamEndEvent(Event): + pass + + +class DocumentStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, explicit=None, version=None, tags=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + self.version = version + self.tags = tags + + +class DocumentEndEvent(Event): + def __init__(self, start_mark=None, end_mark=None, explicit=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + + +class AliasEvent(NodeEvent): + pass + + +class ScalarEvent(NodeEvent): + def __init__(self, implicit, value, start_mark=None, end_mark=None, style=None): + self.tag = None + self.implicit = implicit + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + + +class SequenceStartEvent(CollectionStartEvent): + pass + + +class SequenceEndEvent(CollectionEndEvent): + pass + + +class MappingStartEvent(CollectionStartEvent): + pass + + +class MappingEndEvent(CollectionEndEvent): + pass diff --git a/powerline/lint/markedjson/loader.py b/powerline/lint/markedjson/loader.py new file mode 100644 index 0000000..3ee5686 --- /dev/null +++ b/powerline/lint/markedjson/loader.py @@ -0,0 +1,25 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.lint.markedjson.reader import Reader +from powerline.lint.markedjson.scanner import Scanner +from powerline.lint.markedjson.parser import Parser +from powerline.lint.markedjson.composer import Composer +from powerline.lint.markedjson.constructor import Constructor +from powerline.lint.markedjson.resolver import Resolver +from powerline.lint.markedjson.error import echoerr + + +class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) + self.haserrors = False + + def echoerr(self, *args, **kwargs): + echoerr(*args, **kwargs) + self.haserrors = True diff --git a/powerline/lint/markedjson/markedvalue.py b/powerline/lint/markedjson/markedvalue.py new file mode 100644 index 0000000..3b8db3e --- /dev/null +++ b/powerline/lint/markedjson/markedvalue.py @@ -0,0 +1,151 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.lib.unicode import unicode + + +def gen_new(cls): + def __new__(arg_cls, value, mark): + r = super(arg_cls, arg_cls).__new__(arg_cls, value) + r.mark = mark + r.value = value + return r + return __new__ + + +def gen_init(cls): + def __init__(self, value, mark): + return cls.__init__(self, value) + return __init__ + + +def gen_getnewargs(cls): + def __getnewargs__(self): + return (self.value, self.mark) + return __getnewargs__ + + +class MarkedUnicode(unicode): + __new__ = gen_new(unicode) + __getnewargs__ = gen_getnewargs(unicode) + + def _proc_partition(self, part_result): + pointdiff = 1 + r = [] + for s in part_result: + r.append(MarkedUnicode(s, self.mark.advance_string(pointdiff))) + pointdiff += len(s) + return tuple(r) + + def rpartition(self, sep): + return self._proc_partition(super(MarkedUnicode, self).rpartition(sep)) + + def partition(self, sep): + return self._proc_partition(super(MarkedUnicode, self).partition(sep)) + + +class MarkedInt(int): + __new__ = gen_new(int) + __getnewargs__ = gen_getnewargs(int) + + +class MarkedFloat(float): + __new__ = gen_new(float) + __getnewargs__ = gen_getnewargs(float) + + +class MarkedDict(dict): + __init__ = gen_init(dict) + __getnewargs__ = gen_getnewargs(dict) + + def __new__(arg_cls, value, mark): + r = super(arg_cls, arg_cls).__new__(arg_cls, value) + r.mark = mark + r.value = value + r.keydict = dict(((key, key) for key in r)) + return r + + def setmerged(self, d): + try: + self.mark.set_merged_mark(d.mark) + except AttributeError: + pass + + def __setitem__(self, key, value): + try: + old_value = self[key] + except KeyError: + pass + else: + try: + key.mark.set_old_mark(self.keydict[key].mark) + except AttributeError: + pass + except KeyError: + pass + try: + value.mark.set_old_mark(old_value.mark) + except AttributeError: + pass + dict.__setitem__(self, key, value) + self.keydict[key] = key + + def update(self, *args, **kwargs): + dict.update(self, *args, **kwargs) + self.keydict = dict(((key, key) for key in self)) + + def copy(self): + return MarkedDict(super(MarkedDict, self).copy(), self.mark) + + +class MarkedList(list): + __new__ = gen_new(list) + __init__ = gen_init(list) + __getnewargs__ = gen_getnewargs(list) + + +class MarkedValue: + def __init__(self, value, mark): + self.mark = mark + self.value = value + + __getinitargs__ = gen_getnewargs(None) + + +specialclasses = { + unicode: MarkedUnicode, + int: MarkedInt, + float: MarkedFloat, + dict: MarkedDict, + list: MarkedList, +} + +classcache = {} + + +def gen_marked_value(value, mark, use_special_classes=True): + if use_special_classes and value.__class__ in specialclasses: + Marked = specialclasses[value.__class__] + elif value.__class__ in classcache: + Marked = classcache[value.__class__] + else: + class Marked(MarkedValue): + for func in value.__class__.__dict__: + if func == 'copy': + def copy(self): + return self.__class__(self.value.copy(), self.mark) + elif func not in set(('__init__', '__new__', '__getattribute__')): + if func in set(('__eq__',)): + # HACK to make marked dictionaries always work + exec (( + 'def {0}(self, *args):\n' + ' return self.value.{0}(*[arg.value if isinstance(arg, MarkedValue) else arg for arg in args])' + ).format(func)) + else: + exec (( + 'def {0}(self, *args, **kwargs):\n' + ' return self.value.{0}(*args, **kwargs)\n' + ).format(func)) + classcache[value.__class__] = Marked + + return Marked(value, mark) diff --git a/powerline/lint/markedjson/nodes.py b/powerline/lint/markedjson/nodes.py new file mode 100644 index 0000000..66ad843 --- /dev/null +++ b/powerline/lint/markedjson/nodes.py @@ -0,0 +1,55 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + + +class Node(object): + def __init__(self, tag, value, start_mark, end_mark): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + + def __repr__(self): + value = self.value + # if isinstance(value, list): + # if len(value) == 0: + # value = '<empty>' + # elif len(value) == 1: + # value = '<1 item>' + # else: + # value = '<%d items>' % len(value) + # else: + # if len(value) > 75: + # value = repr(value[:70]+u' ... ') + # else: + # value = repr(value) + value = repr(value) + return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) + + +class ScalarNode(Node): + id = 'scalar' + + def __init__(self, tag, value, start_mark=None, end_mark=None, style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + + +class CollectionNode(Node): + def __init__(self, tag, value, start_mark=None, end_mark=None, flow_style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + + +class SequenceNode(CollectionNode): + id = 'sequence' + + +class MappingNode(CollectionNode): + id = 'mapping' diff --git a/powerline/lint/markedjson/parser.py b/powerline/lint/markedjson/parser.py new file mode 100644 index 0000000..336a2a2 --- /dev/null +++ b/powerline/lint/markedjson/parser.py @@ -0,0 +1,255 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.lint.markedjson.error import MarkedError +from powerline.lint.markedjson import tokens +from powerline.lint.markedjson import events + + +class ParserError(MarkedError): + pass + + +class Parser: + def __init__(self): + self.current_event = None + self.yaml_version = None + self.states = [] + self.marks = [] + self.state = self.parse_stream_start + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def check_event(self, *choices): + # Check the type of the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + if self.current_event is not None: + if not choices: + return True + for choice in choices: + if isinstance(self.current_event, choice): + return True + return False + + def peek_event(self): + # Get the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + return self.current_event + + def get_event(self): + # Get the next event and proceed further. + if self.current_event is None: + if self.state: + self.current_event = self.state() + value = self.current_event + self.current_event = None + return value + + # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + # implicit_document ::= block_node DOCUMENT-END* + # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + + def parse_stream_start(self): + # Parse the stream start. + token = self.get_token() + event = events.StreamStartEvent(token.start_mark, token.end_mark, encoding=token.encoding) + + # Prepare the next state. + self.state = self.parse_implicit_document_start + + return event + + def parse_implicit_document_start(self): + # Parse an implicit document. + if not self.check_token(tokens.StreamEndToken): + token = self.peek_token() + start_mark = end_mark = token.start_mark + event = events.DocumentStartEvent(start_mark, end_mark, explicit=False) + + # Prepare the next state. + self.states.append(self.parse_document_end) + self.state = self.parse_node + + return event + + else: + return self.parse_document_start() + + def parse_document_start(self): + # Parse an explicit document. + if not self.check_token(tokens.StreamEndToken): + token = self.peek_token() + self.echoerr( + None, None, + ('expected \'<stream end>\', but found %r' % token.id), token.start_mark + ) + return events.StreamEndEvent(token.start_mark, token.end_mark) + else: + # Parse the end of the stream. + token = self.get_token() + event = events.StreamEndEvent(token.start_mark, token.end_mark) + assert not self.states + assert not self.marks + self.state = None + return event + + def parse_document_end(self): + # Parse the document end. + token = self.peek_token() + start_mark = end_mark = token.start_mark + explicit = False + event = events.DocumentEndEvent(start_mark, end_mark, explicit=explicit) + + # Prepare the next state. + self.state = self.parse_document_start + + return event + + def parse_document_content(self): + return self.parse_node() + + def parse_node(self, indentless_sequence=False): + start_mark = end_mark = None + if start_mark is None: + start_mark = end_mark = self.peek_token().start_mark + event = None + implicit = True + if self.check_token(tokens.ScalarToken): + token = self.get_token() + end_mark = token.end_mark + if token.plain: + implicit = (True, False) + else: + implicit = (False, True) + event = events.ScalarEvent(implicit, token.value, start_mark, end_mark, style=token.style) + self.state = self.states.pop() + elif self.check_token(tokens.FlowSequenceStartToken): + end_mark = self.peek_token().end_mark + event = events.SequenceStartEvent(implicit, start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_sequence_first_entry + elif self.check_token(tokens.FlowMappingStartToken): + end_mark = self.peek_token().end_mark + event = events.MappingStartEvent(implicit, start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_mapping_first_key + else: + token = self.peek_token() + raise ParserError( + 'while parsing a flow node', start_mark, + 'expected the node content, but found %r' % token.id, + token.start_mark + ) + return event + + def parse_flow_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_sequence_entry(first=True) + + def parse_flow_sequence_entry(self, first=False): + if not self.check_token(tokens.FlowSequenceEndToken): + if not first: + if self.check_token(tokens.FlowEntryToken): + self.get_token() + if self.check_token(tokens.FlowSequenceEndToken): + token = self.peek_token() + self.echoerr( + 'While parsing a flow sequence', self.marks[-1], + ('expected sequence value, but got %r' % token.id), token.start_mark + ) + else: + token = self.peek_token() + raise ParserError( + 'while parsing a flow sequence', self.marks[-1], + ('expected \',\' or \']\', but got %r' % token.id), token.start_mark + ) + + if not self.check_token(tokens.FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry) + return self.parse_node() + token = self.get_token() + event = events.SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_sequence_entry_mapping_end(self): + self.state = self.parse_flow_sequence_entry + token = self.peek_token() + return events.MappingEndEvent(token.start_mark, token.start_mark) + + def parse_flow_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_mapping_key(first=True) + + def parse_flow_mapping_key(self, first=False): + if not self.check_token(tokens.FlowMappingEndToken): + if not first: + if self.check_token(tokens.FlowEntryToken): + self.get_token() + if self.check_token(tokens.FlowMappingEndToken): + token = self.peek_token() + self.echoerr( + 'While parsing a flow mapping', self.marks[-1], + ('expected mapping key, but got %r' % token.id), token.start_mark + ) + else: + token = self.peek_token() + raise ParserError( + 'while parsing a flow mapping', self.marks[-1], + ('expected \',\' or \'}\', but got %r' % token.id), token.start_mark + ) + if self.check_token(tokens.KeyToken): + token = self.get_token() + if not self.check_token(tokens.ValueToken, tokens.FlowEntryToken, tokens.FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_value) + return self.parse_node() + else: + token = self.peek_token() + raise ParserError( + 'while parsing a flow mapping', self.marks[-1], + ('expected value, but got %r' % token.id), token.start_mark + ) + elif not self.check_token(tokens.FlowMappingEndToken): + token = self.peek_token() + expect_key = self.check_token(tokens.ValueToken, tokens.FlowEntryToken) + if not expect_key: + self.get_token() + expect_key = self.check_token(tokens.ValueToken) + + if expect_key: + raise ParserError( + 'while parsing a flow mapping', self.marks[-1], + ('expected string key, but got %r' % token.id), token.start_mark + ) + else: + token = self.peek_token() + raise ParserError( + 'while parsing a flow mapping', self.marks[-1], + ('expected \':\', but got %r' % token.id), token.start_mark + ) + token = self.get_token() + event = events.MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_mapping_value(self): + if self.check_token(tokens.ValueToken): + token = self.get_token() + if not self.check_token(tokens.FlowEntryToken, tokens.FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_key) + return self.parse_node() + + token = self.peek_token() + raise ParserError( + 'while parsing a flow mapping', self.marks[-1], + ('expected mapping value, but got %r' % token.id), token.start_mark + ) diff --git a/powerline/lint/markedjson/reader.py b/powerline/lint/markedjson/reader.py new file mode 100644 index 0000000..0ca4516 --- /dev/null +++ b/powerline/lint/markedjson/reader.py @@ -0,0 +1,141 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import codecs + +from powerline.lint.markedjson.error import MarkedError, Mark, NON_PRINTABLE_RE +from powerline.lib.unicode import unicode + + +# This module contains abstractions for the input stream. You don’t have to +# looks further, there are no pretty code. + + +class ReaderError(MarkedError): + pass + + +class Reader(object): + # Reader: + # - determines the data encoding and converts it to a unicode string, + # - checks if characters are in allowed range, + # - adds '\0' to the end. + + # Reader accepts + # - a file-like object with its `read` method returning `str`, + + # Yeah, it’s ugly and slow. + def __init__(self, stream): + self.name = None + self.stream = None + self.stream_pointer = 0 + self.eof = True + self.buffer = '' + self.pointer = 0 + self.full_buffer = unicode('') + self.full_pointer = 0 + self.raw_buffer = None + self.raw_decode = codecs.utf_8_decode + self.encoding = 'utf-8' + self.index = 0 + self.line = 0 + self.column = 0 + + self.stream = stream + self.name = getattr(stream, 'name', '<file>') + self.eof = False + self.raw_buffer = None + + while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): + self.update_raw() + self.update(1) + + def peek(self, index=0): + try: + return self.buffer[self.pointer + index] + except IndexError: + self.update(index + 1) + return self.buffer[self.pointer + index] + + def prefix(self, length=1): + if self.pointer + length >= len(self.buffer): + self.update(length) + return self.buffer[self.pointer:self.pointer + length] + + def update_pointer(self, length): + while length: + ch = self.buffer[self.pointer] + self.pointer += 1 + self.full_pointer += 1 + self.index += 1 + if ch == '\n': + self.line += 1 + self.column = 0 + else: + self.column += 1 + length -= 1 + + def forward(self, length=1): + if self.pointer + length + 1 >= len(self.buffer): + self.update(length + 1) + self.update_pointer(length) + + def get_mark(self): + return Mark(self.name, self.line, self.column, self.full_buffer, self.full_pointer) + + def check_printable(self, data): + match = NON_PRINTABLE_RE.search(data) + if match: + self.update_pointer(match.start()) + raise ReaderError( + 'while reading from stream', None, + 'found special characters which are not allowed', + Mark(self.name, self.line, self.column, self.full_buffer, self.full_pointer) + ) + + def update(self, length): + if self.raw_buffer is None: + return + self.buffer = self.buffer[self.pointer:] + self.pointer = 0 + while len(self.buffer) < length: + if not self.eof: + self.update_raw() + try: + data, converted = self.raw_decode(self.raw_buffer, 'strict', self.eof) + except UnicodeDecodeError as exc: + character = self.raw_buffer[exc.start] + position = self.stream_pointer - len(self.raw_buffer) + exc.start + data, converted = self.raw_decode(self.raw_buffer[:exc.start], 'strict', self.eof) + self.buffer += data + self.full_buffer += data + '<' + str(ord(character)) + '>' + self.raw_buffer = self.raw_buffer[converted:] + self.update_pointer(exc.start - 1) + raise ReaderError( + 'while reading from stream', None, + 'found character #x%04x that cannot be decoded by UTF-8 codec' % ord(character), + Mark(self.name, self.line, self.column, self.full_buffer, position) + ) + self.buffer += data + self.full_buffer += data + self.raw_buffer = self.raw_buffer[converted:] + self.check_printable(data) + if self.eof: + self.buffer += '\0' + self.raw_buffer = None + break + + def update_raw(self, size=-1): + # Was size=4096 + assert(size < 0) + # WARNING: reading the whole stream at once. To change this behaviour to + # former reading N characters at once one must make sure that reading + # never ends at partial unicode character. + data = self.stream.read(size) + if self.raw_buffer is None: + self.raw_buffer = data + else: + self.raw_buffer += data + self.stream_pointer += len(data) + if not data: + self.eof = True diff --git a/powerline/lint/markedjson/resolver.py b/powerline/lint/markedjson/resolver.py new file mode 100644 index 0000000..fa8ceaa --- /dev/null +++ b/powerline/lint/markedjson/resolver.py @@ -0,0 +1,131 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re + +from powerline.lint.markedjson.error import MarkedError +from powerline.lint.markedjson import nodes + + +class ResolverError(MarkedError): + pass + + +class BaseResolver: + DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str' + DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq' + DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' + + yaml_implicit_resolvers = {} + yaml_path_resolvers = {} + + def __init__(self): + self.resolver_exact_paths = [] + self.resolver_prefix_paths = [] + + @classmethod + def add_implicit_resolver(cls, tag, regexp, first): + if 'yaml_implicit_resolvers' not in cls.__dict__: + cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy() + if first is None: + first = [None] + for ch in first: + cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) + + def descend_resolver(self, current_node, current_index): + if not self.yaml_path_resolvers: + return + exact_paths = {} + prefix_paths = [] + if current_node: + depth = len(self.resolver_prefix_paths) + for path, kind in self.resolver_prefix_paths[-1]: + if self.check_resolver_prefix(depth, path, kind, current_node, current_index): + if len(path) > depth: + prefix_paths.append((path, kind)) + else: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + for path, kind in self.yaml_path_resolvers: + if not path: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + prefix_paths.append((path, kind)) + self.resolver_exact_paths.append(exact_paths) + self.resolver_prefix_paths.append(prefix_paths) + + def ascend_resolver(self): + if not self.yaml_path_resolvers: + return + self.resolver_exact_paths.pop() + self.resolver_prefix_paths.pop() + + def check_resolver_prefix(self, depth, path, kind, current_node, current_index): + node_check, index_check = path[depth - 1] + if isinstance(node_check, str): + if current_node.tag != node_check: + return + elif node_check is not None: + if not isinstance(current_node, node_check): + return + if index_check is True and current_index is not None: + return + if ((index_check is False or index_check is None) + and current_index is None): + return + if isinstance(index_check, str): + if not (isinstance(current_index, nodes.ScalarNode) and index_check == current_index.value): + return + elif isinstance(index_check, int) and not isinstance(index_check, bool): + if index_check != current_index: + return + return True + + def resolve(self, kind, value, implicit, mark=None): + if kind is nodes.ScalarNode and implicit[0]: + if value == '': + resolvers = self.yaml_implicit_resolvers.get('', []) + else: + resolvers = self.yaml_implicit_resolvers.get(value[0], []) + resolvers += self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers: + if regexp.match(value): + return tag + else: + self.echoerr( + 'While resolving plain scalar', None, + 'expected floating-point value, integer, null or boolean, but got %r' % value, + mark + ) + return self.DEFAULT_SCALAR_TAG + if kind is nodes.ScalarNode: + return self.DEFAULT_SCALAR_TAG + elif kind is nodes.SequenceNode: + return self.DEFAULT_SEQUENCE_TAG + elif kind is nodes.MappingNode: + return self.DEFAULT_MAPPING_TAG + + +class Resolver(BaseResolver): + pass + + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:bool', + re.compile(r'''^(?:true|false)$''', re.X), + list('yYnNtTfFoO')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:float', + re.compile(r'^-?(?:0|[1-9]\d*)(?=[.eE])(?:\.\d+)?(?:[eE][-+]?\d+)?$', re.X), + list('-0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:int', + re.compile(r'^(?:0|-?[1-9]\d*)$', re.X), + list('-0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:null', + re.compile(r'^null$', re.X), + ['n']) diff --git a/powerline/lint/markedjson/scanner.py b/powerline/lint/markedjson/scanner.py new file mode 100644 index 0000000..b0bddf3 --- /dev/null +++ b/powerline/lint/markedjson/scanner.py @@ -0,0 +1,499 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from string import hexdigits + +from powerline.lint.markedjson.error import MarkedError +from powerline.lint.markedjson import tokens +from powerline.lib.unicode import unicode, unichr, surrogate_pair_to_character + + +hexdigits_set = set(hexdigits) + + +# Scanner produces tokens of the following types: +# STREAM-START +# STREAM-END +# DOCUMENT-START +# DOCUMENT-END +# FLOW-SEQUENCE-START +# FLOW-MAPPING-START +# FLOW-SEQUENCE-END +# FLOW-MAPPING-END +# FLOW-ENTRY +# KEY +# VALUE +# SCALAR(value, plain, style) +# +# Read comments in the Scanner code for more details. + + +class ScannerError(MarkedError): + pass + + +class SimpleKey: + # See below simple keys treatment. + def __init__(self, token_number, index, line, column, mark): + self.token_number = token_number + self.index = index + self.line = line + self.column = column + self.mark = mark + + +class Scanner: + def __init__(self): + '''Initialize the scanner.''' + # It is assumed that Scanner and Reader will have a common descendant. + # Reader do the dirty work of checking for BOM and converting the + # input data to Unicode. It also adds NUL to the end. + # + # Reader supports the following methods + # self.peek(i=0) # peek the next i-th character + # self.prefix(l=1) # peek the next l characters + # self.forward(l=1) # read the next l characters and move the pointer. + + # Had we reached the end of the stream? + self.done = False + + # The number of unclosed '{' and '['. `flow_level == 0` means block + # context. + self.flow_level = 0 + + # List of processed tokens that are not yet emitted. + self.tokens = [] + + # Add the STREAM-START token. + self.fetch_stream_start() + + # Number of tokens that were emitted through the `get_token` method. + self.tokens_taken = 0 + + # Variables related to simple keys treatment. + + # A simple key is a key that is not denoted by the '?' indicator. + # We emit the KEY token before all keys, so when we find a potential + # simple key, we try to locate the corresponding ':' indicator. + # Simple keys should be limited to a single line. + + # Can a simple key start at the current position? A simple key may + # start: + # - after '{', '[', ',' (in the flow context), + self.allow_simple_key = False + + # Keep track of possible simple keys. This is a dictionary. The key + # is `flow_level`; there can be no more that one possible simple key + # for each level. The value is a SimpleKey record: + # (token_number, index, line, column, mark) + # A simple key may start with SCALAR(flow), '[', or '{' tokens. + self.possible_simple_keys = {} + + # Public methods. + + def check_token(self, *choices): + # Check if the next token is one of the given types. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + if not choices: + return True + for choice in choices: + if isinstance(self.tokens[0], choice): + return True + return False + + def peek_token(self): + # Return the next token, but do not delete if from the queue. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + return self.tokens[0] + + def get_token(self): + # Return the next token. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + self.tokens_taken += 1 + return self.tokens.pop(0) + + # Private methods. + + def need_more_tokens(self): + if self.done: + return False + if not self.tokens: + return True + # The current token may be a potential simple key, so we + # need to look further. + self.stale_possible_simple_keys() + if self.next_possible_simple_key() == self.tokens_taken: + return True + + def fetch_more_tokens(self): + + # Eat whitespaces and comments until we reach the next token. + self.scan_to_next_token() + + # Remove obsolete possible simple keys. + self.stale_possible_simple_keys() + + # Peek the next character. + ch = self.peek() + + # Is it the end of stream? + if ch == '\0': + return self.fetch_stream_end() + + # Note: the order of the following checks is NOT significant. + + # Is it the flow sequence start indicator? + if ch == '[': + return self.fetch_flow_sequence_start() + + # Is it the flow mapping start indicator? + if ch == '{': + return self.fetch_flow_mapping_start() + + # Is it the flow sequence end indicator? + if ch == ']': + return self.fetch_flow_sequence_end() + + # Is it the flow mapping end indicator? + if ch == '}': + return self.fetch_flow_mapping_end() + + # Is it the flow entry indicator? + if ch == ',': + return self.fetch_flow_entry() + + # Is it the value indicator? + if ch == ':' and self.flow_level: + return self.fetch_value() + + # Is it a double quoted scalar? + if ch == '"': + return self.fetch_double() + + # It must be a plain scalar then. + if self.check_plain(): + return self.fetch_plain() + + # No? It’s an error. Let’s produce a nice error message. + raise ScannerError( + 'while scanning for the next token', None, + 'found character %r that cannot start any token' % ch, + self.get_mark() + ) + + # Simple keys treatment. + + def next_possible_simple_key(self): + # Return the number of the nearest possible simple key. Actually we + # don’t need to loop through the whole dictionary. We may replace it + # with the following code: + # if not self.possible_simple_keys: + # return None + # return self.possible_simple_keys[ + # min(self.possible_simple_keys.keys())].token_number + min_token_number = None + for level in self.possible_simple_keys: + key = self.possible_simple_keys[level] + if min_token_number is None or key.token_number < min_token_number: + min_token_number = key.token_number + return min_token_number + + def stale_possible_simple_keys(self): + # Remove entries that are no longer possible simple keys. According to + # the YAML specification, simple keys + # - should be limited to a single line, + # Disabling this procedure will allow simple keys of any length and + # height (may cause problems if indentation is broken though). + for level in list(self.possible_simple_keys): + key = self.possible_simple_keys[level] + if key.line != self.line: + del self.possible_simple_keys[level] + + def save_possible_simple_key(self): + # The next token may start a simple key. We check if it’s possible + # and save its position. This function is called for + # SCALAR(flow), '[', and '{'. + + # The next token might be a simple key. Let’s save it’s number and + # position. + if self.allow_simple_key: + self.remove_possible_simple_key() + token_number = self.tokens_taken + len(self.tokens) + key = SimpleKey(token_number, self.index, self.line, self.column, self.get_mark()) + self.possible_simple_keys[self.flow_level] = key + + def remove_possible_simple_key(self): + # Remove the saved possible key position at the current flow level. + if self.flow_level in self.possible_simple_keys: + del self.possible_simple_keys[self.flow_level] + + # Fetchers. + + def fetch_stream_start(self): + # We always add STREAM-START as the first token and STREAM-END as the + # last token. + + # Read the token. + mark = self.get_mark() + + # Add STREAM-START. + self.tokens.append(tokens.StreamStartToken(mark, mark, encoding=self.encoding)) + + def fetch_stream_end(self): + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + self.possible_simple_keys = {} + + # Read the token. + mark = self.get_mark() + + # Add STREAM-END. + self.tokens.append(tokens.StreamEndToken(mark, mark)) + + # The steam is finished. + self.done = True + + def fetch_flow_sequence_start(self): + self.fetch_flow_collection_start(tokens.FlowSequenceStartToken) + + def fetch_flow_mapping_start(self): + self.fetch_flow_collection_start(tokens.FlowMappingStartToken) + + def fetch_flow_collection_start(self, TokenClass): + # '[' and '{' may start a simple key. + self.save_possible_simple_key() + + # Increase the flow level. + self.flow_level += 1 + + # Simple keys are allowed after '[' and '{'. + self.allow_simple_key = True + + # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_end(self): + self.fetch_flow_collection_end(tokens.FlowSequenceEndToken) + + def fetch_flow_mapping_end(self): + self.fetch_flow_collection_end(tokens.FlowMappingEndToken) + + def fetch_flow_collection_end(self, TokenClass): + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Decrease the flow level. + self.flow_level -= 1 + + # No simple keys after ']' or '}'. + self.allow_simple_key = False + + # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_value(self): + # Do we determine a simple key? + if self.flow_level in self.possible_simple_keys: + + # Add KEY. + key = self.possible_simple_keys[self.flow_level] + del self.possible_simple_keys[self.flow_level] + self.tokens.insert(key.token_number - self.tokens_taken, tokens.KeyToken(key.mark, key.mark)) + + # There cannot be two simple keys one after another. + self.allow_simple_key = False + + # Add VALUE. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(tokens.ValueToken(start_mark, end_mark)) + + def fetch_flow_entry(self): + # Simple keys are allowed after ','. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add FLOW-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(tokens.FlowEntryToken(start_mark, end_mark)) + + def fetch_double(self): + # A flow scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after flow scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. + self.tokens.append(self.scan_flow_scalar()) + + def fetch_plain(self): + + self.save_possible_simple_key() + + # No simple keys after plain scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. May change `allow_simple_key`. + self.tokens.append(self.scan_plain()) + + # Checkers. + + def check_plain(self): + return self.peek() in '0123456789-ntf' + + # Scanners. + + def scan_to_next_token(self): + while self.peek() in ' \t\n': + self.forward() + + def scan_flow_scalar(self): + # See the specification for details. + # Note that we loose indentation rules for quoted scalars. Quoted + # scalars don’t need to adhere indentation because " and ' clearly + # mark the beginning and the end of them. Therefore we are less + # restrictive then the specification requires. We only need to check + # that document separators are not included in scalars. + chunks = [] + start_mark = self.get_mark() + quote = self.peek() + self.forward() + chunks.extend(self.scan_flow_scalar_non_spaces(start_mark)) + while self.peek() != quote: + chunks.extend(self.scan_flow_scalar_spaces(start_mark)) + chunks.extend(self.scan_flow_scalar_non_spaces(start_mark)) + self.forward() + end_mark = self.get_mark() + return tokens.ScalarToken(unicode().join(chunks), False, start_mark, end_mark, '"') + + ESCAPE_REPLACEMENTS = { + 'b': '\x08', + 't': '\x09', + 'n': '\x0A', + 'f': '\x0C', + 'r': '\x0D', + '"': '\"', + '\\': '\\', + } + + ESCAPE_CODES = { + 'u': 4, + } + + def scan_flow_scalar_non_spaces(self, start_mark): + # See the specification for details. + chunks = [] + while True: + length = 0 + while self.peek(length) not in '\"\\\0 \t\n': + length += 1 + if length: + chunks.append(self.prefix(length)) + self.forward(length) + ch = self.peek() + if ch == '\\': + self.forward() + ch = self.peek() + if ch in self.ESCAPE_REPLACEMENTS: + chunks.append(self.ESCAPE_REPLACEMENTS[ch]) + self.forward() + elif ch in self.ESCAPE_CODES: + length = self.ESCAPE_CODES[ch] + self.forward() + for k in range(length): + if self.peek(k) not in hexdigits: + raise ScannerError( + 'while scanning a double-quoted scalar', start_mark, + 'expected escape sequence of %d hexdecimal numbers, but found %r' % ( + length, self.peek(k)), + self.get_mark() + ) + code = int(self.prefix(length), 16) + self.forward(length) + if 0xD800 <= code <= 0xDC00: + # Start of the surrogate pair + next_char = self.prefix(6) + if ( + next_char[0] != '\\' + or next_char[1] != 'u' + or not (set(next_char[2:]) < hexdigits_set) + or not (0xDC00 <= int(next_char[2:], 16) <= 0xDFFF) + ): + raise ScannerError( + 'while scanning a double-quoted scalar', start_mark, + 'expected escape sequence with the next character in surrogate pair, but found %r' % ( + next_char + ), + self.get_mark() + ) + code = surrogate_pair_to_character(code, int(next_char[2:], 16)) + self.forward(6) + chunks.append(unichr(code)) + else: + raise ScannerError( + 'while scanning a double-quoted scalar', start_mark, + ('found unknown escape character %r' % ch), self.get_mark() + ) + else: + return chunks + + def scan_flow_scalar_spaces(self, start_mark): + # See the specification for details. + chunks = [] + length = 0 + while self.peek(length) in ' \t': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch == '\0': + raise ScannerError( + 'while scanning a quoted scalar', start_mark, + 'found unexpected end of stream', self.get_mark() + ) + elif ch == '\n': + raise ScannerError( + 'while scanning a quoted scalar', start_mark, + 'found unexpected line end', self.get_mark() + ) + else: + chunks.append(whitespaces) + return chunks + + def scan_plain(self): + chunks = [] + start_mark = self.get_mark() + spaces = [] + while True: + length = 0 + while True: + if self.peek(length) not in 'eE.0123456789nul-tr+fas': + break + length += 1 + if length == 0: + break + self.allow_simple_key = False + chunks.extend(spaces) + chunks.append(self.prefix(length)) + self.forward(length) + end_mark = self.get_mark() + return tokens.ScalarToken(''.join(chunks), True, start_mark, end_mark) diff --git a/powerline/lint/markedjson/tokens.py b/powerline/lint/markedjson/tokens.py new file mode 100644 index 0000000..6fa8bf1 --- /dev/null +++ b/powerline/lint/markedjson/tokens.py @@ -0,0 +1,72 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + + +class Token(object): + def __init__(self, start_mark, end_mark): + self.start_mark = start_mark + self.end_mark = end_mark + + def __repr__(self): + attributes = [ + key for key in self.__dict__ + if not key.endswith('_mark') + ] + attributes.sort() + arguments = ', '.join([ + '%s=%r' % (key, getattr(self, key)) + for key in attributes + ]) + return '%s(%s)' % (self.__class__.__name__, arguments) + + +class StreamStartToken(Token): + id = '<stream start>' + + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + + +class StreamEndToken(Token): + id = '<stream end>' + + +class FlowSequenceStartToken(Token): + id = '[' + + +class FlowMappingStartToken(Token): + id = '{' + + +class FlowSequenceEndToken(Token): + id = ']' + + +class FlowMappingEndToken(Token): + id = '}' + + +class KeyToken(Token): + id = '?' + + +class ValueToken(Token): + id = ':' + + +class FlowEntryToken(Token): + id = ',' + + +class ScalarToken(Token): + id = '<scalar>' + + def __init__(self, value, plain, start_mark, end_mark, style=None): + self.value = value + self.plain = plain + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style diff --git a/powerline/lint/selfcheck.py b/powerline/lint/selfcheck.py new file mode 100644 index 0000000..06d1fbe --- /dev/null +++ b/powerline/lint/selfcheck.py @@ -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) + ']')) diff --git a/powerline/lint/spec.py b/powerline/lint/spec.py new file mode 100644 index 0000000..f7b9155 --- /dev/null +++ b/powerline/lint/spec.py @@ -0,0 +1,759 @@ +# 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, NON_PRINTABLE_STR +from powerline.lint.selfcheck import havemarks + + +NON_PRINTABLE_RE = re.compile( + NON_PRINTABLE_STR.translate({ + ord('\t'): None, + ord('\n'): None, + 0x0085: None, + }) +) + + +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, + 'One of the either variants failed. Messages from the first variant:', + 'messages from the next variant:' + ) + + hadproblem = False + for spec in self.specs[start:end]: + proceed, hadproblem = spec.match(value, value.mark, data, context, new_echoerr) + new_echoerr.next_variant() + 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 check_printable(self, value, context_mark, data, context, echoerr, _): + '''Check that given unicode string contains only printable characters + ''' + hadproblem = False + for match in NON_PRINTABLE_RE.finditer(value): + hadproblem = True + echoerr( + context=self.cmsg.format(key=context.key), + context_mark=value.mark, + problem='found not printable character U+{0:04x} in a configuration string'.format( + ord(match.group(0))), + problem_mark=value.mark.advance_string(match.start() + 1) + ) + return True, hadproblem + + def printable(self, *args): + self.type(unicode) + self.checks.append(('check_printable', args)) + return self + + 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: + if min_len > 0: + 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}) diff --git a/powerline/listers/__init__.py b/powerline/listers/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/listers/__init__.py diff --git a/powerline/listers/i3wm.py b/powerline/listers/i3wm.py new file mode 100644 index 0000000..0bbcfdc --- /dev/null +++ b/powerline/listers/i3wm.py @@ -0,0 +1,68 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.theme import requires_segment_info +from powerline.lib.dict import updated +from powerline.bindings.wm import get_i3_connection, get_connected_xrandr_outputs + + +@requires_segment_info +def output_lister(pl, segment_info): + '''List all outputs in segment_info format + ''' + + return ( + ( + updated(segment_info, output=output['name']), + { + 'draw_inner_divider': None + } + ) + for output in get_connected_xrandr_outputs(pl) + ) + + +@requires_segment_info +def workspace_lister(pl, segment_info, only_show=None, output=None): + '''List all workspaces in segment_info format + + Sets the segment info values of ``workspace`` and ``output`` to the name of + the i3 workspace and the ``xrandr`` output respectively and the keys + ``"visible"``, ``"urgent"`` and ``"focused"`` to a boolean indicating these + states. + + :param list only_show: + Specifies which workspaces to list. Valid entries are ``"visible"``, + ``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces + are listed. + + :param str output: + May be set to the name of an X output. If specified, only workspaces + on that output are listed. Overrides automatic output detection by + the lemonbar renderer and bindings. Set to ``false`` to force + all workspaces to be shown. + ''' + + if output == None: + output = output or segment_info.get('output') + + return ( + ( + updated( + segment_info, + output=w['output'], + workspace={ + 'name': w['name'], + 'visible': w['visible'], + 'urgent': w['urgent'], + 'focused': w['focused'], + }, + ), + { + 'draw_inner_divider': None + } + ) + for w in get_i3_connection().get_workspaces() + if (((not only_show or any(w[typ] for typ in only_show)) + and (not output or w['output'] == output))) + ) diff --git a/powerline/listers/pdb.py b/powerline/listers/pdb.py new file mode 100644 index 0000000..24e11ea --- /dev/null +++ b/powerline/listers/pdb.py @@ -0,0 +1,37 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.theme import requires_segment_info + + +@requires_segment_info +def frame_lister(pl, segment_info, full_stack=False, maxframes=3): + '''List all frames in segment_info format + + :param bool full_stack: + If true, then all frames in the stack are listed. Normally N first + frames are discarded where N is a number of frames present at the first + invocation of the prompt minus one. + :param int maxframes: + Maximum number of frames to display. + ''' + if full_stack: + initial_stack_length = 0 + frames = segment_info['pdb'].stack + else: + initial_stack_length = segment_info['initial_stack_length'] + frames = segment_info['pdb'].stack[initial_stack_length:] + + if len(frames) > maxframes: + frames = frames[-maxframes:] + + return ( + ( + { + 'curframe': frame[0], + 'initial_stack_length': initial_stack_length, + }, + {} + ) + for frame in frames + ) diff --git a/powerline/listers/vim.py b/powerline/listers/vim.py new file mode 100644 index 0000000..583e0d3 --- /dev/null +++ b/powerline/listers/vim.py @@ -0,0 +1,123 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.theme import requires_segment_info +from powerline.bindings.vim import (current_tabpage, list_tabpages) + +try: + import vim +except ImportError: + vim = object() + + +def tabpage_updated_segment_info(segment_info, tabpage): + segment_info = segment_info.copy() + window = tabpage.window + buffer = window.buffer + segment_info.update( + tabpage=tabpage, + tabnr=tabpage.number, + window=window, + winnr=window.number, + window_id=int(window.vars.get('powerline_window_id', -1)), + buffer=buffer, + bufnr=buffer.number, + ) + return segment_info + + +@requires_segment_info +def tablister(pl, segment_info, **kwargs): + '''List all tab pages in segment_info format + + Specifically generates a list of segment info dictionaries with ``window``, + ``winnr``, ``window_id``, ``buffer`` and ``bufnr`` keys set to tab-local + ones and additional ``tabpage`` and ``tabnr`` keys. + + Adds either ``tab:`` or ``tab_nc:`` prefix to all segment highlight groups. + + Works best with vim-7.4 or later: earlier versions miss tabpage object and + thus window objects are not available as well. + ''' + cur_tabpage = current_tabpage() + cur_tabnr = cur_tabpage.number + + def add_multiplier(tabpage, dct): + dct['priority_multiplier'] = 1 + (0.001 * abs(tabpage.number - cur_tabnr)) + return dct + + return ( + (lambda tabpage, prefix: ( + tabpage_updated_segment_info(segment_info, tabpage), + add_multiplier(tabpage, { + 'highlight_group_prefix': prefix, + 'divider_highlight_group': 'tab:divider' + }) + ))(tabpage, 'tab' if tabpage == cur_tabpage else 'tab_nc') + for tabpage in list_tabpages() + ) + + +def buffer_updated_segment_info(segment_info, buffer): + segment_info = segment_info.copy() + segment_info.update( + window=None, + winnr=None, + window_id=None, + buffer=buffer, + bufnr=buffer.number, + ) + return segment_info + + +@requires_segment_info +def bufferlister(pl, segment_info, show_unlisted=False, **kwargs): + '''List all buffers in segment_info format + + Specifically generates a list of segment info dictionaries with ``buffer`` + and ``bufnr`` keys set to buffer-specific ones, ``window``, ``winnr`` and + ``window_id`` keys set to None. + + Adds one of ``buf:``, ``buf_nc:``, ``buf_mod:``, or ``buf_nc_mod`` + prefix to all segment highlight groups. + + :param bool show_unlisted: + True if unlisted buffers should be shown as well. Current buffer is + always shown. + ''' + cur_buffer = vim.current.buffer + cur_bufnr = cur_buffer.number + + def add_multiplier(buffer, dct): + dct['priority_multiplier'] = 1 + (0.001 * abs(buffer.number - cur_bufnr)) + return dct + + return ( + (lambda buffer, current, modified: ( + buffer_updated_segment_info(segment_info, buffer), + add_multiplier(buffer, { + 'highlight_group_prefix': '{0}{1}'.format(current, modified), + 'divider_highlight_group': 'tab:divider' + }) + ))( + buffer, + 'buf' if buffer is cur_buffer else 'buf_nc', + '_mod' if int(vim.eval('getbufvar({0}, \'&modified\')'.format(buffer.number))) > 0 else '' + ) + for buffer in vim.buffers if ( + buffer is cur_buffer + or show_unlisted + # We can't use vim_getbufoption(segment_info, 'buflisted') + # here for performance reasons. Querying the buffer options + # through the vim python module's option attribute caused + # vim to think it needed to update the tabline for every + # keystroke after any event that changed the buffer's + # options. + # + # Using the vim module's eval method to directly use the + # buflisted(nr) vim method instead does not cause vim to + # update the tabline after every keystroke, but rather after + # events that would change that status. Fixes #1281 + or int(vim.eval('buflisted(%s)' % buffer.number)) > 0 + ) + ) diff --git a/powerline/matchers/__init__.py b/powerline/matchers/__init__.py new file mode 100644 index 0000000..b2b9f10 --- /dev/null +++ b/powerline/matchers/__init__.py @@ -0,0 +1,6 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) +from pkgutil import extend_path + + +__path__ = extend_path(__path__, __name__) diff --git a/powerline/matchers/vim/__init__.py b/powerline/matchers/vim/__init__.py new file mode 100644 index 0000000..f6de45e --- /dev/null +++ b/powerline/matchers/vim/__init__.py @@ -0,0 +1,19 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from powerline.bindings.vim import vim_getbufoption, buffer_name + + +def help(matcher_info): + return vim_getbufoption(matcher_info, 'buftype') == 'help' + + +def cmdwin(matcher_info): + name = buffer_name(matcher_info) + return name and os.path.basename(name) == b'[Command Line]' + + +def quickfix(matcher_info): + return vim_getbufoption(matcher_info, 'buftype') == 'quickfix' diff --git a/powerline/matchers/vim/plugin/__init__.py b/powerline/matchers/vim/plugin/__init__.py new file mode 100644 index 0000000..b2b9f10 --- /dev/null +++ b/powerline/matchers/vim/plugin/__init__.py @@ -0,0 +1,6 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) +from pkgutil import extend_path + + +__path__ = extend_path(__path__, __name__) diff --git a/powerline/matchers/vim/plugin/commandt.py b/powerline/matchers/vim/plugin/commandt.py new file mode 100644 index 0000000..7eefe9b --- /dev/null +++ b/powerline/matchers/vim/plugin/commandt.py @@ -0,0 +1,14 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from powerline.bindings.vim import vim_getbufoption, buffer_name + + +def commandt(matcher_info): + name = buffer_name(matcher_info) + return ( + vim_getbufoption(matcher_info, 'filetype') == 'command-t' + or (name and os.path.basename(name) == b'GoToFile') + ) diff --git a/powerline/matchers/vim/plugin/gundo.py b/powerline/matchers/vim/plugin/gundo.py new file mode 100644 index 0000000..e0fe377 --- /dev/null +++ b/powerline/matchers/vim/plugin/gundo.py @@ -0,0 +1,16 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from powerline.bindings.vim import buffer_name + + +def gundo(matcher_info): + name = buffer_name(matcher_info) + return name and os.path.basename(name) == b'__Gundo__' + + +def gundo_preview(matcher_info): + name = buffer_name(matcher_info) + return name and os.path.basename(name) == b'__Gundo_Preview__' diff --git a/powerline/matchers/vim/plugin/nerdtree.py b/powerline/matchers/vim/plugin/nerdtree.py new file mode 100644 index 0000000..d6e9f69 --- /dev/null +++ b/powerline/matchers/vim/plugin/nerdtree.py @@ -0,0 +1,15 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re + +from powerline.bindings.vim import buffer_name + + +NERD_TREE_RE = re.compile(b'NERD_tree_\\d+') + + +def nerdtree(matcher_info): + name = buffer_name(matcher_info) + return name and NERD_TREE_RE.match(os.path.basename(name)) diff --git a/powerline/pdb.py b/powerline/pdb.py new file mode 100644 index 0000000..b1e13ce --- /dev/null +++ b/powerline/pdb.py @@ -0,0 +1,48 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import platform +import os + +from powerline import Powerline +from powerline.lib.overrides import parse_override_var +from powerline.lib.dict import mergeargs, mergedicts + + +class PDBPowerline(Powerline): + '''PDB-specific powerline bindings + ''' + def init(self, **kwargs): + return super(PDBPowerline, self).init( + ext='pdb', + renderer_module='pdb', + **kwargs + ) + + def do_setup(self, pdb): + self.update_renderer() + self.renderer.set_pdb(pdb) + + def load_main_config(self): + r = super(PDBPowerline, self).load_main_config() + config_overrides = os.environ.get('POWERLINE_CONFIG_OVERRIDES') + if config_overrides: + mergedicts(r, mergeargs(parse_override_var(config_overrides))) + return r + + def load_theme_config(self, name): + r = super(PDBPowerline, self).load_theme_config(name) + theme_overrides = os.environ.get('POWERLINE_THEME_OVERRIDES') + if theme_overrides: + theme_overrides_dict = mergeargs(parse_override_var(theme_overrides)) + if name in theme_overrides_dict: + mergedicts(r, theme_overrides_dict[name]) + return r + + def get_config_paths(self): + paths = [path for path in os.environ.get('POWERLINE_CONFIG_PATHS', '').split(':') if path] + return paths or super(PDBPowerline, self).get_config_paths() + + if sys.version_info < (3,) and platform.python_implementation() == 'PyPy': + get_encoding = staticmethod(lambda: 'ascii') diff --git a/powerline/renderer.py b/powerline/renderer.py new file mode 100644 index 0000000..040b406 --- /dev/null +++ b/powerline/renderer.py @@ -0,0 +1,594 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os +import re +import operator + +from itertools import chain + +from powerline.theme import Theme +from powerline.lib.unicode import unichr, strwidth_ucs_2, strwidth_ucs_4 + + +NBSP = ' ' + + +np_control_character_translations = dict(( + # Control characters: ^@ … ^Y + (i1, '^' + unichr(i1 + 0x40)) for i1 in range(0x20) +)) +'''Control character translations + +Dictionary that maps characters in range 0x00–0x1F (inclusive) to strings +``'^@'``, ``'^A'`` and so on. + +.. note: maps tab to ``^I`` and newline to ``^J``. +''' + +np_invalid_character_translations = dict(( + # Invalid unicode characters obtained using 'surrogateescape' error + # handler. + (i2, '<{0:02x}>'.format(i2 - 0xDC00)) for i2 in range(0xDC80, 0xDD00) +)) +'''Invalid unicode character translations + +When using ``surrogateescape`` encoding error handling method characters in +range 0x80–0xFF (inclusive) are transformed into unpaired surrogate escape +unicode codepoints 0xDC80–0xDD00. This dictionary maps such characters to +``<80>``, ``<81>``, and so on: in Python-3 they cannot be printed or +converted to UTF-8 because UTF-8 standard does not allow surrogate escape +characters, not even paired ones. Python-2 contains a bug that allows such +action, but printing them in any case makes no sense. +''' + +# XXX: not using `r` because it makes no sense. +np_invalid_character_re = re.compile('(?<![\uD800-\uDBFF])[\uDC80-\uDD00]') +'''Regex that finds unpaired surrogate escape characters + +Search is only limited to the ones obtained from ``surrogateescape`` error +handling method. This regex is only used for UCS-2 Python variants because +in this case characters above 0xFFFF are represented as surrogate escapes +characters and are thus subject to partial transformation if +``np_invalid_character_translations`` translation table is used. +''' + +np_character_translations = np_control_character_translations.copy() +'''Dictionary that contains non-printable character translations + +In UCS-4 versions of Python this is a union of +``np_invalid_character_translations`` and ``np_control_character_translations`` +dictionaries. In UCS-2 for technical reasons ``np_invalid_character_re`` is used +instead and this dictionary only contains items from +``np_control_character_translations``. +''' + +translate_np = ( + ( + lambda s: ( + np_invalid_character_re.subn( + lambda match: ( + np_invalid_character_translations[ord(match.group(0))] + ), s + )[0].translate(np_character_translations) + ) + ) if sys.maxunicode < 0x10FFFF else ( + lambda s: ( + s.translate(np_character_translations) + ) + ) +) +'''Function that translates non-printable characters into printable strings + +Is used to translate control characters and surrogate escape characters +obtained from ``surrogateescape`` encoding errors handling method into some +printable sequences. See documentation for +``np_invalid_character_translations`` and +``np_control_character_translations`` for more details. +''' + + +def construct_returned_value(rendered_highlighted, segments, width, output_raw, output_width): + if not (output_raw or output_width): + return rendered_highlighted + else: + return ( + (rendered_highlighted,) + + ((''.join((segment['_rendered_raw'] for segment in segments)),) if output_raw else ()) + + ((width,) if output_width else ()) + ) + + +class Renderer(object): + '''Object that is responsible for generating the highlighted string. + + :param dict theme_config: + Main theme configuration. + :param local_themes: + Local themes. Is to be used by subclasses from ``.get_theme()`` method, + base class only records this parameter to a ``.local_themes`` attribute. + :param dict theme_kwargs: + Keyword arguments for ``Theme`` class constructor. + :param PowerlineLogger pl: + Object used for logging. + :param int ambiwidth: + Width of the characters with east asian width unicode attribute equal to + ``A`` (Ambigious). + :param dict options: + Various options. Are normally not used by base renderer, but all options + are recorded as attributes. + ''' + + segment_info = { + 'environ': os.environ, + 'getcwd': getattr(os, 'getcwdu', os.getcwd), + 'home': os.environ.get('HOME'), + } + '''Basic segment info + + Is merged with local segment information by :py:meth:`get_segment_info` + method. Keys: + + ``environ`` + Object containing environment variables. Must define at least the + following methods: ``.__getitem__(var)`` that raises ``KeyError`` in + case requested environment variable is not present, ``.get(var, + default=None)`` that works like ``dict.get`` and be able to be passed to + ``Popen``. + + ``getcwd`` + Function that returns current working directory. Will be called without + any arguments, should return ``unicode`` or (in python-2) regular + string. + + ``home`` + String containing path to home directory. Should be ``unicode`` or (in + python-2) regular string or ``None``. + ''' + + character_translations = {} + '''Character translations for use in escape() function. + + See documentation of ``unicode.translate`` for details. + ''' + + def __init__(self, + theme_config, + local_themes, + theme_kwargs, + pl, + ambiwidth=1, + **options): + self.__dict__.update(options) + self.theme_config = theme_config + theme_kwargs['pl'] = pl + self.pl = pl + if theme_config.get('use_non_breaking_spaces', True): + self.character_translations = self.character_translations.copy() + self.character_translations[ord(' ')] = NBSP + self.theme = Theme(theme_config=theme_config, **theme_kwargs) + self.local_themes = local_themes + self.theme_kwargs = theme_kwargs + self.width_data = { + 'N': 1, # Neutral + 'Na': 1, # Narrow + 'A': ambiwidth, # Ambigious + 'H': 1, # Half-width + 'W': 2, # Wide + 'F': 2, # Fullwidth + } + + strwidth = lambda self, s: ( + (strwidth_ucs_2 if sys.maxunicode < 0x10FFFF else strwidth_ucs_4)( + self.width_data, s) + ) + '''Function that returns string width. + + Is used to calculate the place given string occupies when handling + ``width`` argument to ``.render()`` method. Must take east asian width + into account. + + :param unicode string: + String whose width will be calculated. + + :return: unsigned integer. + ''' + + def get_theme(self, matcher_info): + '''Get Theme object. + + Is to be overridden by subclasses to support local themes, this variant + only returns ``.theme`` attribute. + + :param matcher_info: + Parameter ``matcher_info`` that ``.render()`` method received. + Unused. + ''' + return self.theme + + def shutdown(self): + '''Prepare for interpreter shutdown. The only job it is supposed to do + is calling ``.shutdown()`` method for all theme objects. Should be + overridden by subclasses in case they support local themes. + ''' + self.theme.shutdown() + + def get_segment_info(self, segment_info, mode): + '''Get segment information. + + Must return a dictionary containing at least ``home``, ``environ`` and + ``getcwd`` keys (see documentation for ``segment_info`` attribute). This + implementation merges ``segment_info`` dictionary passed to + ``.render()`` method with ``.segment_info`` attribute, preferring keys + from the former. It also replaces ``getcwd`` key with function returning + ``segment_info['environ']['PWD']`` in case ``PWD`` variable is + available. + + :param dict segment_info: + Segment information that was passed to ``.render()`` method. + + :return: dict with segment information. + ''' + r = self.segment_info.copy() + r['mode'] = mode + if segment_info: + r.update(segment_info) + if 'PWD' in r['environ']: + r['getcwd'] = lambda: r['environ']['PWD'] + return r + + def render_above_lines(self, **kwargs): + '''Render all segments in the {theme}/segments/above list + + Rendering happens in the reversed order. Parameters are the same as in + .render() method. + + :yield: rendered line. + ''' + + theme = self.get_theme(kwargs.get('matcher_info', None)) + for line in range(theme.get_line_number() - 1, 0, -1): + yield self.render(side=None, line=line, **kwargs) + + def render(self, mode=None, width=None, side=None, line=0, output_raw=False, output_width=False, segment_info=None, matcher_info=None): + '''Render all segments. + + When a width is provided, low-priority segments are dropped one at + a time until the line is shorter than the width, or only segments + with a negative priority are left. If one or more segments with + ``"width": "auto"`` are provided they will fill the remaining space + until the desired width is reached. + + :param str mode: + Mode string. Affects contents (colors and the set of segments) of + rendered string. + :param int width: + Maximum width text can occupy. May be exceeded if there are too much + non-removable segments. + :param str side: + One of ``left``, ``right``. Determines which side will be rendered. + If not present all sides are rendered. + :param int line: + Line number for which segments should be obtained. Is counted from + zero (botmost line). + :param bool output_raw: + Changes the output: if this parameter is ``True`` then in place of + one string this method outputs a pair ``(colored_string, + colorless_string)``. + :param bool output_width: + Changes the output: if this parameter is ``True`` then in place of + one string this method outputs a pair ``(colored_string, + string_width)``. Returns a three-tuple if ``output_raw`` is also + ``True``: ``(colored_string, colorless_string, string_width)``. + :param dict segment_info: + Segment information. See also :py:meth:`get_segment_info` method. + :param matcher_info: + Matcher information. Is processed in :py:meth:`get_segment_info` + method. + ''' + theme = self.get_theme(matcher_info) + return self.do_render( + mode=mode, + width=width, + side=side, + line=line, + output_raw=output_raw, + output_width=output_width, + segment_info=self.get_segment_info(segment_info, mode), + theme=theme, + ) + + def compute_divider_widths(self, theme): + return { + 'left': { + 'hard': self.strwidth(theme.get_divider('left', 'hard')), + 'soft': self.strwidth(theme.get_divider('left', 'soft')), + }, + 'right': { + 'hard': self.strwidth(theme.get_divider('right', 'hard')), + 'soft': self.strwidth(theme.get_divider('right', 'soft')), + }, + } + + hl_join = staticmethod(''.join) + '''Join a list of rendered segments into a resulting string + + This method exists to deal with non-string render outputs, so `segments` + may actually be not an iterable with strings. + + :param list segments: + Iterable containing rendered segments. By “rendered segments” + :py:meth:`Renderer.hl` output is meant. + + :return: Results of joining these segments. + ''' + + def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme): + '''Like Renderer.render(), but accept theme in place of matcher_info + ''' + segments = list(theme.get_segments(side, line, segment_info, mode)) + + current_width = 0 + + self._prepare_segments(segments, output_width or width) + + if not width: + # No width specified, so we don’t need to crop or pad anything + if output_width: + current_width = self._render_length(theme, segments, self.compute_divider_widths(theme)) + return construct_returned_value(self.hl_join([ + segment['_rendered_hl'] + for segment in self._render_segments(theme, segments) + ]) + self.hlstyle(), segments, current_width, output_raw, output_width) + + divider_widths = self.compute_divider_widths(theme) + + # Create an ordered list of segments that can be dropped + segments_priority = sorted((segment for segment in segments if segment['priority'] is not None), key=lambda segment: segment['priority'], reverse=True) + no_priority_segments = filter(lambda segment: segment['priority'] is None, segments) + current_width = self._render_length(theme, segments, divider_widths) + if current_width > width: + for segment in chain(segments_priority, no_priority_segments): + if segment['truncate'] is not None: + segment['contents'] = segment['truncate'](self.pl, current_width - width, segment) + + segments_priority = iter(segments_priority) + if current_width > width and len(segments) > 100: + # When there are too many segments use faster, but less correct + # algorythm for width computation + diff = current_width - width + for segment in segments_priority: + segments.remove(segment) + diff -= segment['_len'] + if diff <= 0: + break + current_width = self._render_length(theme, segments, divider_widths) + if current_width > width: + # When there are not too much use more precise, but much slower + # width computation. It also finishes computations in case + # previous variant did not free enough space. + for segment in segments_priority: + segments.remove(segment) + current_width = self._render_length(theme, segments, divider_widths) + if current_width <= width: + break + del segments_priority + + # Distribute the remaining space on spacer segments + segments_spacers = [segment for segment in segments if segment['expand'] is not None] + if segments_spacers: + distribute_len, distribute_len_remainder = divmod(width - current_width, len(segments_spacers)) + for segment in segments_spacers: + segment['contents'] = ( + segment['expand']( + self.pl, + distribute_len + (1 if distribute_len_remainder > 0 else 0), + segment)) + distribute_len_remainder -= 1 + # `_len` key is not needed anymore, but current_width should have an + # actual value for various bindings. + current_width = width + elif output_width: + current_width = self._render_length(theme, segments, divider_widths) + + rendered_highlighted = self.hl_join([ + segment['_rendered_hl'] + for segment in self._render_segments(theme, segments) + ]) + if rendered_highlighted: + rendered_highlighted += self.hlstyle() + + return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width) + + def _prepare_segments(self, segments, calculate_contents_len): + '''Translate non-printable characters and calculate segment width + ''' + for segment in segments: + segment['contents'] = translate_np(segment['contents']) + if calculate_contents_len: + for segment in segments: + if segment['literal_contents'][1]: + segment['_contents_len'] = segment['literal_contents'][0] + else: + segment['_contents_len'] = self.strwidth(segment['contents']) + + def _render_length(self, theme, segments, divider_widths): + '''Update segments lengths and return them + ''' + segments_len = len(segments) + ret = 0 + divider_spaces = theme.get_spaces() + prev_segment = theme.EMPTY_SEGMENT + try: + first_segment = next(iter(( + segment + for segment in segments + if not segment['literal_contents'][1] + ))) + except StopIteration: + first_segment = None + try: + last_segment = next(iter(( + segment + for segment in reversed(segments) + if not segment['literal_contents'][1] + ))) + except StopIteration: + last_segment = None + for index, segment in enumerate(segments): + side = segment['side'] + segment_len = segment['_contents_len'] + if not segment['literal_contents'][1]: + if side == 'left': + if segment is not last_segment: + compare_segment = next(iter(( + segment + for segment in segments[index + 1:] + if not segment['literal_contents'][1] + ))) + else: + compare_segment = theme.EMPTY_SEGMENT + else: + compare_segment = prev_segment + + divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' + + outer_padding = int(bool( + segment is first_segment + if side == 'left' else + segment is last_segment + )) * theme.outer_padding + + draw_divider = segment['draw_' + divider_type + '_divider'] + segment_len += outer_padding + if draw_divider: + segment_len += divider_widths[side][divider_type] + divider_spaces + prev_segment = segment + + segment['_len'] = segment_len + ret += segment_len + return ret + + def _render_segments(self, theme, segments, render_highlighted=True): + '''Internal segment rendering method. + + This method loops through the segment array and compares the + foreground/background colors and divider properties and returns the + rendered statusline as a string. + + The method always renders the raw segment contents (i.e. without + highlighting strings added), and only renders the highlighted + statusline if render_highlighted is True. + ''' + segments_len = len(segments) + divider_spaces = theme.get_spaces() + prev_segment = theme.EMPTY_SEGMENT + try: + first_segment = next(iter(( + segment + for segment in segments + if not segment['literal_contents'][1] + ))) + except StopIteration: + first_segment = None + try: + last_segment = next(iter(( + segment + for segment in reversed(segments) + if not segment['literal_contents'][1] + ))) + except StopIteration: + last_segment = None + + for index, segment in enumerate(segments): + side = segment['side'] + if not segment['literal_contents'][1]: + if side == 'left': + if segment is not last_segment: + compare_segment = next(iter(( + segment + for segment in segments[index + 1:] + if not segment['literal_contents'][1] + ))) + else: + compare_segment = theme.EMPTY_SEGMENT + else: + compare_segment = prev_segment + outer_padding = int(bool( + segment is first_segment + if side == 'left' else + segment is last_segment + )) * theme.outer_padding * ' ' + divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' + + divider_highlighted = '' + contents_raw = segment['contents'] + contents_highlighted = '' + draw_divider = segment['draw_' + divider_type + '_divider'] + + # XXX Make sure self.hl() calls are called in the same order + # segments are displayed. This is needed for Vim renderer to work. + if draw_divider: + divider_raw = self.escape(theme.get_divider(side, divider_type)) + if side == 'left': + contents_raw = outer_padding + contents_raw + (divider_spaces * ' ') + else: + contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding + + if divider_type == 'soft': + divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight' + divider_fg = segment[divider_highlight_group_key]['fg'] + divider_bg = segment[divider_highlight_group_key]['bg'] + else: + divider_fg = segment['highlight']['bg'] + divider_bg = compare_segment['highlight']['bg'] + + if side == 'left': + if render_highlighted: + contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) + divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) + segment['_rendered_raw'] = contents_raw + divider_raw + segment['_rendered_hl'] = contents_highlighted + divider_highlighted + else: + if render_highlighted: + divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) + contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) + segment['_rendered_raw'] = divider_raw + contents_raw + segment['_rendered_hl'] = divider_highlighted + contents_highlighted + else: + if side == 'left': + contents_raw = outer_padding + contents_raw + else: + contents_raw = contents_raw + outer_padding + + contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) + segment['_rendered_raw'] = contents_raw + segment['_rendered_hl'] = contents_highlighted + prev_segment = segment + else: + segment['_rendered_raw'] = ' ' * segment['literal_contents'][0] + segment['_rendered_hl'] = segment['literal_contents'][1] + yield segment + + def escape(self, string): + '''Method that escapes segment contents. + ''' + return string.translate(self.character_translations) + + def hlstyle(fg=None, bg=None, attrs=None): + '''Output highlight style string. + + Assuming highlighted string looks like ``{style}{contents}`` this method + should output ``{style}``. If it is called without arguments this method + is supposed to reset style to its default. + ''' + raise NotImplementedError + + def hl(self, contents, fg=None, bg=None, attrs=None): + '''Output highlighted chunk. + + This implementation just outputs :py:meth:`hlstyle` joined with + ``contents``. + ''' + return self.hlstyle(fg, bg, attrs) + (contents or '') diff --git a/powerline/renderers/__init__.py b/powerline/renderers/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/renderers/__init__.py diff --git a/powerline/renderers/i3bar.py b/powerline/renderers/i3bar.py new file mode 100644 index 0000000..4124a0f --- /dev/null +++ b/powerline/renderers/i3bar.py @@ -0,0 +1,36 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import json + +from powerline.renderer import Renderer + + +class I3barRenderer(Renderer): + '''I3bar Segment Renderer. + + Currently works only for i3bgbar (i3 bar with custom patches). + ''' + + @staticmethod + def hlstyle(*args, **kwargs): + # We don’t need to explicitly reset attributes, so skip those calls + return '' + + def hl(self, contents, fg=None, bg=None, attrs=None): + segment = { + 'full_text': contents, + 'separator': False, + 'separator_block_width': 0, # no seperators + } + + if fg is not None: + if fg is not False and fg[1] is not False: + segment['color'] = '#{0:06x}'.format(fg[1]) + if bg is not None: + if bg is not False and bg[1] is not False: + segment['background'] = '#{0:06x}'.format(bg[1]) + return json.dumps(segment) + ',' + + +renderer = I3barRenderer diff --git a/powerline/renderers/ipython/__init__.py b/powerline/renderers/ipython/__init__.py new file mode 100644 index 0000000..8f463b5 --- /dev/null +++ b/powerline/renderers/ipython/__init__.py @@ -0,0 +1,34 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.theme import Theme +from powerline.renderers.shell import PromptRenderer + + +class IPythonRenderer(PromptRenderer): + '''Powerline ipython segment renderer.''' + def get_segment_info(self, segment_info, mode): + r = self.segment_info.copy() + r['ipython'] = segment_info + return r + + def get_theme(self, matcher_info): + if matcher_info == 'in': + return self.theme + else: + match = self.local_themes[matcher_info] + try: + return match['theme'] + except KeyError: + match['theme'] = Theme( + theme_config=match['config'], + main_theme_config=self.theme_config, + **self.theme_kwargs + ) + return match['theme'] + + def shutdown(self): + self.theme.shutdown() + for match in self.local_themes.values(): + if 'theme' in match: + match['theme'].shutdown() diff --git a/powerline/renderers/ipython/pre_5.py b/powerline/renderers/ipython/pre_5.py new file mode 100644 index 0000000..9fc8c21 --- /dev/null +++ b/powerline/renderers/ipython/pre_5.py @@ -0,0 +1,56 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell import ShellRenderer +from powerline.renderers.shell.readline import ReadlineRenderer +from powerline.renderers.ipython import IPythonRenderer + + +class IPythonPre50Renderer(IPythonRenderer, ShellRenderer): + '''Powerline ipython segment renderer for pre-5.0 IPython versions.''' + def render(self, **kwargs): + # XXX super(ShellRenderer), *not* super(IPythonPre50Renderer) + return super(ShellRenderer, self).render(**kwargs) + + def do_render(self, segment_info, **kwargs): + segment_info.update(client_id='ipython') + return super(IPythonPre50Renderer, self).do_render( + segment_info=segment_info, + **kwargs + ) + + +class IPythonPromptRenderer(IPythonPre50Renderer, ReadlineRenderer): + '''Powerline ipython prompt (in and in2) renderer''' + pass + + +class IPythonNonPromptRenderer(IPythonPre50Renderer): + '''Powerline ipython non-prompt (out and rewrite) renderer''' + pass + + +class RendererProxy(object): + '''Powerline IPython renderer proxy which chooses appropriate renderer + + Instantiates two renderer objects: one will be used for prompts and the + other for non-prompts. + ''' + def __init__(self, **kwargs): + old_widths = {} + self.non_prompt_renderer = IPythonNonPromptRenderer(old_widths=old_widths, **kwargs) + self.prompt_renderer = IPythonPromptRenderer(old_widths=old_widths, **kwargs) + + def render_above_lines(self, *args, **kwargs): + return self.non_prompt_renderer.render_above_lines(*args, **kwargs) + + def render(self, is_prompt, *args, **kwargs): + return (self.prompt_renderer if is_prompt else self.non_prompt_renderer).render( + *args, **kwargs) + + def shutdown(self, *args, **kwargs): + self.prompt_renderer.shutdown(*args, **kwargs) + self.non_prompt_renderer.shutdown(*args, **kwargs) + + +renderer = RendererProxy diff --git a/powerline/renderers/ipython/since_5.py b/powerline/renderers/ipython/since_5.py new file mode 100644 index 0000000..8a26da7 --- /dev/null +++ b/powerline/renderers/ipython/since_5.py @@ -0,0 +1,130 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import operator + +from collections import defaultdict + +try: + from __builtin__ import reduce +except ImportError: + from functools import reduce + +from pygments.token import Token +from prompt_toolkit.styles import DynamicStyle, Attrs + +from powerline.renderers.ipython import IPythonRenderer +from powerline.ipython import IPythonInfo +from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE + + +PowerlinePromptToken = Token.Generic.Prompt.Powerline + + +# Note: since 2.7 there is dict.__missing__ with same purpose. But in 2.6 one +# must use defaultdict to get __missing__ working. +class PowerlineStyleDict(defaultdict): + '''Dictionary used for getting pygments style for Powerline groups + ''' + def __new__(cls, missing_func): + return defaultdict.__new__(cls) + + def __init__(self, missing_func): + super(PowerlineStyleDict, self).__init__() + self.missing_func = missing_func + + def __missing__(self, key): + return self.missing_func(key) + + +class PowerlinePromptStyle(DynamicStyle): + def get_attrs_for_token(self, token): + if ( + token not in PowerlinePromptToken + or len(token) != len(PowerlinePromptToken) + 1 + or not token[-1].startswith('Pl') + or token[-1] == 'Pl' + ): + return super(PowerlinePromptStyle, self).get_attrs_for_token(token) + ret = { + 'color': None, + 'bgcolor': None, + 'bold': None, + 'underline': None, + 'italic': None, + 'reverse': False, + 'blink': False, + } + for prop in token[-1][3:].split('_'): + if prop[0] == 'a': + ret[prop[1:]] = True + elif prop[0] == 'f': + ret['color'] = prop[1:] + elif prop[0] == 'b': + ret['bgcolor'] = prop[1:] + return Attrs(**ret) + + def get_token_to_attributes_dict(self): + dct = super(PowerlinePromptStyle, self).get_token_to_attributes_dict() + + def fallback(key): + try: + return dct[key] + except KeyError: + return self.get_attrs_for_token(key) + + return PowerlineStyleDict(fallback) + + def invalidation_hash(self): + return super(PowerlinePromptStyle, self).invalidation_hash() + 1 + + +class IPythonPygmentsRenderer(IPythonRenderer): + reduce_initial = [] + + def get_segment_info(self, segment_info, mode): + return super(IPythonPygmentsRenderer, self).get_segment_info( + IPythonInfo(segment_info), mode) + + @staticmethod + def hl_join(segments): + return reduce(operator.iadd, segments, []) + + def hl(self, contents, fg=None, bg=None, attrs=None): + '''Output highlighted chunk. + + This implementation outputs a list containing a single pair + (:py:class:`pygments.token.Token`, + :py:class:`powerline.lib.unicode.unicode`). + ''' + guifg = None + guibg = None + attrs = [] + if fg is not None and fg is not False: + guifg = fg[1] + if bg is not None and bg is not False: + guibg = bg[1] + if attrs: + attrs = [] + if attrs & ATTR_BOLD: + attrs.append('bold') + if attrs & ATTR_ITALIC: + attrs.append('italic') + if attrs & ATTR_UNDERLINE: + attrs.append('underline') + name = ( + 'Pl' + + ''.join(('_a' + attr for attr in attrs)) + + (('_f%6x' % guifg) if guifg is not None else '') + + (('_b%6x' % guibg) if guibg is not None else '') + ) + return [(getattr(Token.Generic.Prompt.Powerline, name), contents)] + + def hlstyle(self, **kwargs): + return [] + + def get_client_id(self, segment_info): + return id(self) + + +renderer = IPythonPygmentsRenderer diff --git a/powerline/renderers/lemonbar.py b/powerline/renderers/lemonbar.py new file mode 100644 index 0000000..f378f23 --- /dev/null +++ b/powerline/renderers/lemonbar.py @@ -0,0 +1,61 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderer import Renderer +from powerline.theme import Theme +from powerline.colorscheme import ATTR_UNDERLINE + + +class LemonbarRenderer(Renderer): + '''lemonbar (formerly bar/bar ain't recursive) renderer + + + See documentation of `lemonbar <https://github.com/LemonBoy/bar>`_ and :ref:`the usage instructions <lemonbar-usage>` + ''' + + character_translations = Renderer.character_translations.copy() + character_translations[ord('%')] = '%%{}' + + @staticmethod + def hlstyle(*args, **kwargs): + # We don’t need to explicitly reset attributes, so skip those calls + return '' + + def hl(self, contents, fg=None, bg=None, attrs=None): + text = '' + + if fg is not None: + if fg is not False and fg[1] is not False: + text += '%{{F#ff{0:06x}}}'.format(fg[1]) + if bg is not None: + if bg is not False and bg[1] is not False: + text += '%{{B#ff{0:06x}}}'.format(bg[1]) + + if attrs & ATTR_UNDERLINE: + text += '%{+u}' + + return text + contents + '%{F-B--u}' + + def render(self, *args, **kwargs): + return '%{{l}}{0}%{{r}}{1}'.format( + super(LemonbarRenderer, self).render(side='left', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs), + super(LemonbarRenderer, self).render(side='right', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs), + ) + + def get_theme(self, matcher_info): + if not matcher_info or matcher_info not in self.local_themes: + return self.theme + match = self.local_themes[matcher_info] + + try: + return match['theme'] + except KeyError: + match['theme'] = Theme( + theme_config=match['config'], + main_theme_config=self.theme_config, + **self.theme_kwargs + ) + return match['theme'] + + +renderer = LemonbarRenderer diff --git a/powerline/renderers/pango_markup.py b/powerline/renderers/pango_markup.py new file mode 100644 index 0000000..1b7d624 --- /dev/null +++ b/powerline/renderers/pango_markup.py @@ -0,0 +1,39 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from xml.sax.saxutils import escape as _escape + +from powerline.renderer import Renderer +from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE + + +class PangoMarkupRenderer(Renderer): + '''Powerline Pango markup segment renderer.''' + + @staticmethod + def hlstyle(*args, **kwargs): + # We don’t need to explicitly reset attributes, so skip those calls + return '' + + def hl(self, contents, fg=None, bg=None, attrs=None): + '''Highlight a segment.''' + awesome_attr = [] + if fg is not None: + if fg is not False and fg[1] is not False: + awesome_attr += ['foreground="#{0:06x}"'.format(fg[1])] + if bg is not None: + if bg is not False and bg[1] is not False: + awesome_attr += ['background="#{0:06x}"'.format(bg[1])] + if attrs is not None and attrs is not False: + if attrs & ATTR_BOLD: + awesome_attr += ['font_weight="bold"'] + if attrs & ATTR_ITALIC: + awesome_attr += ['font_style="italic"'] + if attrs & ATTR_UNDERLINE: + awesome_attr += ['underline="single"'] + return '<span ' + ' '.join(awesome_attr) + '>' + contents + '</span>' + + escape = staticmethod(_escape) + + +renderer = PangoMarkupRenderer diff --git a/powerline/renderers/pdb.py b/powerline/renderers/pdb.py new file mode 100644 index 0000000..040f0e1 --- /dev/null +++ b/powerline/renderers/pdb.py @@ -0,0 +1,50 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import platform + +from powerline.renderers.shell.readline import ReadlineRenderer +from powerline.renderer import Renderer + + +class PDBRenderer(ReadlineRenderer): + '''PDB-specific powerline renderer + ''' + pdb = None + initial_stack_length = None + + def get_segment_info(self, segment_info, mode): + r = self.segment_info.copy() + r['pdb'] = self.pdb + r['initial_stack_length'] = self.initial_stack_length + r['curframe'] = self.pdb.curframe + return r + + def set_pdb(self, pdb): + '''Record currently used :py:class:`pdb.Pdb` instance + + Must be called before first calling :py:meth:`render` method. + + :param pdb.Pdb pdb: + Used :py:class:`pdb.Pdb` instance. This instance will later be used + by :py:meth:`get_segment_info` for patching :ref:`segment_info + <dev-segments-info>` dictionary. + ''' + self.pdb = pdb + + def render(self, **kwargs): + if self.initial_stack_length is None: + self.initial_stack_length = len(self.pdb.stack) - 1 + return Renderer.render(self, **kwargs) + + if sys.version_info < (3,) and platform.python_implementation() == 'PyPy': + def do_render(self, **kwargs): + # Make sure that only ASCII characters survive + ret = super(PDBRenderer, self).do_render(**kwargs) + ret = ret.encode('ascii', 'replace') + ret = ret.decode('ascii') + return ret + + +renderer = PDBRenderer diff --git a/powerline/renderers/shell/__init__.py b/powerline/renderers/shell/__init__.py new file mode 100644 index 0000000..ebb0501 --- /dev/null +++ b/powerline/renderers/shell/__init__.py @@ -0,0 +1,182 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderer import Renderer +from powerline.theme import Theme +from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE + + +def int_to_rgb(num): + r = (num >> 16) & 0xff + g = (num >> 8) & 0xff + b = num & 0xff + return r, g, b + + +class PromptRenderer(Renderer): + '''Powerline generic prompt segment renderer''' + + def __init__(self, old_widths=None, **kwargs): + super(PromptRenderer, self).__init__(**kwargs) + self.old_widths = old_widths if old_widths is not None else {} + + def get_client_id(self, segment_info): + '''Get client ID given segment info + + This is used by daemon to correctly cache widths for different clients + using a single renderer instance. + + :param dict segment_info: + :ref:`Segment info dictionary <dev-segments-info>`. Out of it only + ``client_id`` key is used. It is OK for this dictionary to not + contain this key. + + :return: Any hashable value or ``None``. + ''' + return segment_info.get('client_id') if isinstance(segment_info, dict) else None + + def do_render(self, output_width, segment_info, side, theme, width=None, **kwargs): + client_id = self.get_client_id(segment_info) + if client_id is not None: + local_key = (client_id, side, None if theme is self.theme else id(theme)) + key = (client_id, side, None) + did_width = False + if local_key[-1] != key[-1] and side == 'left': + try: + width = self.old_widths[key] + except KeyError: + pass + else: + did_width = True + if not did_width and width is not None: + if theme.cursor_space_multiplier is not None: + width = int(width * theme.cursor_space_multiplier) + elif theme.cursor_columns: + width -= theme.cursor_columns + + if side == 'right': + try: + width -= self.old_widths[(client_id, 'left', local_key[-1])] + except KeyError: + pass + res = super(PromptRenderer, self).do_render( + output_width=True, + width=width, + theme=theme, + segment_info=segment_info, + side=side, + **kwargs + ) + if client_id is not None: + self.old_widths[local_key] = res[-1] + ret = res if output_width else res[:-1] + if len(ret) == 1: + return ret[0] + else: + return ret + + +class ShellRenderer(PromptRenderer): + '''Powerline shell segment renderer.''' + escape_hl_start = '' + escape_hl_end = '' + term_truecolor = False + term_escape_style = 'auto' + tmux_escape = False + screen_escape = False + + character_translations = Renderer.character_translations.copy() + + def render(self, segment_info, **kwargs): + local_theme = segment_info.get('local_theme') + return super(ShellRenderer, self).render( + matcher_info=local_theme, + segment_info=segment_info, + **kwargs + ) + + def do_render(self, segment_info, **kwargs): + if self.term_escape_style == 'auto': + if segment_info['environ'].get('TERM') == 'fbterm': + self.used_term_escape_style = 'fbterm' + else: + self.used_term_escape_style = 'xterm' + else: + self.used_term_escape_style = self.term_escape_style + return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs) + + def hlstyle(self, fg=None, bg=None, attrs=None): + '''Highlight a segment. + + 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 + is a valid color or attribute, it’s added to the ANSI escape code. + ''' + ansi = [0] + is_fbterm = self.used_term_escape_style == 'fbterm' + term_truecolor = not is_fbterm and self.term_truecolor + if fg is not None: + if fg is False or fg[0] is False: + ansi += [39] + else: + if term_truecolor: + ansi += [38, 2] + list(int_to_rgb(fg[1])) + else: + ansi += [38, 5, fg[0]] + if bg is not None: + if bg is False or bg[0] is False: + ansi += [49] + else: + if term_truecolor: + ansi += [48, 2] + list(int_to_rgb(bg[1])) + else: + ansi += [48, 5, bg[0]] + if attrs is not None: + if attrs is False: + ansi += [22] + else: + if attrs & ATTR_BOLD: + ansi += [1] + elif attrs & ATTR_ITALIC: + # Note: is likely not to work or even be inverse in place of + # italic. Omit using this in colorschemes. + ansi += [3] + elif attrs & ATTR_UNDERLINE: + ansi += [4] + if is_fbterm: + r = [] + while ansi: + cur_ansi = ansi.pop(0) + if cur_ansi == 38: + ansi.pop(0) + r.append('\033[1;{0}}}'.format(ansi.pop(0))) + elif cur_ansi == 48: + ansi.pop(0) + r.append('\033[2;{0}}}'.format(ansi.pop(0))) + else: + r.append('\033[{0}m'.format(cur_ansi)) + r = ''.join(r) + else: + r = '\033[{0}m'.format(';'.join(str(attr) for attr in ansi)) + if self.tmux_escape: + r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\' + elif self.screen_escape: + r = '\033P' + r.replace('\033', '\033\033') + '\033\\' + return self.escape_hl_start + r + self.escape_hl_end + + def get_theme(self, matcher_info): + if not matcher_info: + return self.theme + match = self.local_themes[matcher_info] + try: + return match['theme'] + except KeyError: + match['theme'] = Theme( + theme_config=match['config'], + main_theme_config=self.theme_config, + **self.theme_kwargs + ) + return match['theme'] + + +renderer = ShellRenderer diff --git a/powerline/renderers/shell/bash.py b/powerline/renderers/shell/bash.py new file mode 100644 index 0000000..783bd50 --- /dev/null +++ b/powerline/renderers/shell/bash.py @@ -0,0 +1,18 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell import ShellRenderer + + +class BashPromptRenderer(ShellRenderer): + '''Powerline bash prompt segment renderer.''' + escape_hl_start = '\[' + escape_hl_end = '\]' + + character_translations = ShellRenderer.character_translations.copy() + character_translations[ord('$')] = '\\$' + character_translations[ord('`')] = '\\`' + character_translations[ord('\\')] = '\\\\' + + +renderer = BashPromptRenderer diff --git a/powerline/renderers/shell/ksh.py b/powerline/renderers/shell/ksh.py new file mode 100644 index 0000000..0828e57 --- /dev/null +++ b/powerline/renderers/shell/ksh.py @@ -0,0 +1,19 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell import ShellRenderer + + +ESCAPE_CHAR = '\001' + + +class KshPromptRenderer(ShellRenderer): + '''Powerline bash prompt segment renderer.''' + escape_hl_start = '\001' + escape_hl_end = '\001' + + def render(self, *args, **kwargs): + return '\001\r' + super(KshPromptRenderer, self).render(*args, **kwargs) + + +renderer = KshPromptRenderer diff --git a/powerline/renderers/shell/rcsh.py b/powerline/renderers/shell/rcsh.py new file mode 100644 index 0000000..75ccb22 --- /dev/null +++ b/powerline/renderers/shell/rcsh.py @@ -0,0 +1,7 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell.readline import ReadlineRenderer + + +renderer = ReadlineRenderer diff --git a/powerline/renderers/shell/readline.py b/powerline/renderers/shell/readline.py new file mode 100644 index 0000000..a72dff0 --- /dev/null +++ b/powerline/renderers/shell/readline.py @@ -0,0 +1,14 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell import ShellRenderer + + +class ReadlineRenderer(ShellRenderer): + '''Renderer useful for some applications that use readline + ''' + escape_hl_start = '\x01' + escape_hl_end = '\x02' + + +renderer = ReadlineRenderer diff --git a/powerline/renderers/shell/tcsh.py b/powerline/renderers/shell/tcsh.py new file mode 100644 index 0000000..bf0697d --- /dev/null +++ b/powerline/renderers/shell/tcsh.py @@ -0,0 +1,31 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell.zsh import ZshPromptRenderer + + +class TcshPromptRenderer(ZshPromptRenderer): + '''Powerline tcsh prompt segment renderer.''' + character_translations = ZshPromptRenderer.character_translations.copy() + character_translations[ord('%')] = '%%' + character_translations[ord('\\')] = '\\\\' + character_translations[ord('^')] = '\\^' + character_translations[ord('!')] = '\\!' + + def do_render(self, **kwargs): + ret = super(TcshPromptRenderer, self).do_render(**kwargs) + nbsp = self.character_translations.get(ord(' '), ' ') + end = self.hlstyle() + assert not ret or ret.endswith(end) + if ret.endswith(nbsp + end): + # Exchange nbsp and highlight end because tcsh removes trailing + # %{%} part of the prompt for whatever reason + ret = ret[:-(len(nbsp) + len(end))] + end + nbsp + else: + # We *must* end prompt with non-%{%} sequence for the reasons + # explained above. So add nbsp if it is not already there. + ret += nbsp + return ret + + +renderer = TcshPromptRenderer diff --git a/powerline/renderers/shell/zsh.py b/powerline/renderers/shell/zsh.py new file mode 100644 index 0000000..a231512 --- /dev/null +++ b/powerline/renderers/shell/zsh.py @@ -0,0 +1,16 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell import ShellRenderer + + +class ZshPromptRenderer(ShellRenderer): + '''Powerline zsh prompt segment renderer.''' + escape_hl_start = '%{' + escape_hl_end = '%}' + + character_translations = ShellRenderer.character_translations.copy() + character_translations[ord('%')] = '%%' + + +renderer = ZshPromptRenderer diff --git a/powerline/renderers/tmux.py b/powerline/renderers/tmux.py new file mode 100644 index 0000000..ee6c09c --- /dev/null +++ b/powerline/renderers/tmux.py @@ -0,0 +1,79 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderer import Renderer +from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE + + +def attrs_to_tmux_attrs(attrs): + if attrs is False: + return ['nobold', 'noitalics', 'nounderscore'] + else: + ret = [] + if attrs & ATTR_BOLD: + ret += ['bold'] + else: + ret += ['nobold'] + if attrs & ATTR_ITALIC: + ret += ['italics'] + else: + ret += ['noitalics'] + if attrs & ATTR_UNDERLINE: + ret += ['underscore'] + else: + ret += ['nounderscore'] + return ret + + +class TmuxRenderer(Renderer): + '''Powerline tmux segment renderer.''' + + character_translations = Renderer.character_translations.copy() + character_translations[ord('#')] = '##[]' + + def render(self, width=None, segment_info={}, **kwargs): + if width and segment_info: + width -= segment_info.get('width_adjust', 0) + if width < 10: + width = 10 + return super(TmuxRenderer, self).render(width=width, segment_info=segment_info, **kwargs) + + def hlstyle(self, fg=None, bg=None, attrs=None): + '''Highlight a segment.''' + # We don’t need to explicitly reset attributes, so skip those calls + if not attrs and not bg and not fg: + return '' + tmux_attrs = [] + if fg is not None: + if fg is False or fg[0] is False: + tmux_attrs += ['fg=default'] + else: + if self.term_truecolor and fg[1]: + tmux_attrs += ['fg=#{0:06x}'.format(int(fg[1]))] + else: + tmux_attrs += ['fg=colour' + str(fg[0])] + if bg is not None: + if bg is False or bg[0] is False: + tmux_attrs += ['bg=default'] + else: + if self.term_truecolor and bg[1]: + tmux_attrs += ['bg=#{0:06x}'.format(int(bg[1]))] + else: + tmux_attrs += ['bg=colour' + str(bg[0])] + if attrs is not None: + tmux_attrs += attrs_to_tmux_attrs(attrs) + return '#[' + ','.join(tmux_attrs) + ']' + + def get_segment_info(self, segment_info, mode): + r = self.segment_info.copy() + if segment_info: + r.update(segment_info) + if 'pane_id' in r: + varname = 'TMUX_PWD_' + str(r['pane_id']) + if varname in r['environ']: + r['getcwd'] = lambda: r['environ'][varname] + r['mode'] = mode + return r + + +renderer = TmuxRenderer diff --git a/powerline/renderers/vim.py b/powerline/renderers/vim.py new file mode 100644 index 0000000..281177c --- /dev/null +++ b/powerline/renderers/vim.py @@ -0,0 +1,188 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +import vim + +from powerline.bindings.vim import vim_get_func, vim_getoption, environ, current_tabpage, get_vim_encoding +from powerline.renderer import Renderer +from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE +from powerline.theme import Theme +from powerline.lib.unicode import unichr, register_strwidth_error + + +vim_mode = vim_get_func('mode', rettype='unicode') +if int(vim.eval('v:version')) >= 702: + _vim_mode = vim_mode + vim_mode = lambda: _vim_mode(1) + +mode_translations = { + unichr(ord('V') - 0x40): '^V', + unichr(ord('S') - 0x40): '^S', +} + + +class VimRenderer(Renderer): + '''Powerline vim segment renderer.''' + + character_translations = Renderer.character_translations.copy() + character_translations[ord('%')] = '%%' + + segment_info = Renderer.segment_info.copy() + segment_info.update(environ=environ) + + def __init__(self, *args, **kwargs): + if not hasattr(vim, 'strwidth'): + # Hope nobody want to change this at runtime + if vim.eval('&ambiwidth') == 'double': + kwargs = dict(**kwargs) + kwargs['ambigious'] = 2 + super(VimRenderer, self).__init__(*args, **kwargs) + self.hl_groups = {} + self.prev_highlight = None + self.strwidth_error_name = register_strwidth_error(self.strwidth) + self.encoding = get_vim_encoding() + + def shutdown(self): + self.theme.shutdown() + for match in self.local_themes.values(): + if 'theme' in match: + match['theme'].shutdown() + + def add_local_theme(self, matcher, theme): + if matcher in self.local_themes: + raise KeyError('There is already a local theme with given matcher') + self.local_themes[matcher] = theme + + def get_matched_theme(self, match): + try: + return match['theme'] + except KeyError: + match['theme'] = Theme(theme_config=match['config'], main_theme_config=self.theme_config, **self.theme_kwargs) + return match['theme'] + + def get_theme(self, matcher_info): + if matcher_info is None: + return self.get_matched_theme(self.local_themes[None]) + for matcher in self.local_themes.keys(): + if matcher and matcher(matcher_info): + return self.get_matched_theme(self.local_themes[matcher]) + else: + return self.theme + + if hasattr(vim, 'strwidth'): + if sys.version_info < (3,): + def strwidth(self, string): + # Does not work with tabs, but neither is strwidth from default + # renderer + return vim.strwidth(string.encode(self.encoding, 'replace')) + else: + @staticmethod + def strwidth(string): + return vim.strwidth(string) + + def get_segment_info(self, segment_info, mode): + return segment_info or self.segment_info + + def render(self, window=None, window_id=None, winnr=None, is_tabline=False): + '''Render all segments.''' + segment_info = self.segment_info.copy() + + if window is vim.current.window: + mode = vim_mode() + mode = mode_translations.get(mode, mode) + else: + mode = 'nc' + + segment_info.update( + window=window, + mode=mode, + window_id=window_id, + winnr=winnr, + buffer=window.buffer, + tabpage=current_tabpage(), + encoding=self.encoding, + ) + segment_info['tabnr'] = segment_info['tabpage'].number + segment_info['bufnr'] = segment_info['buffer'].number + if is_tabline: + winwidth = int(vim_getoption('columns')) + else: + winwidth = segment_info['window'].width + + statusline = super(VimRenderer, self).render( + mode=mode, + width=winwidth, + segment_info=segment_info, + matcher_info=(None if is_tabline else segment_info), + ) + statusline = statusline.encode(self.encoding, self.strwidth_error_name) + return statusline + + def reset_highlight(self): + self.hl_groups.clear() + + def hlstyle(self, fg=None, bg=None, attrs=None): + '''Highlight a segment. + + 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 + is a valid color or attribute, it’s added to the vim highlight group. + ''' + # In order not to hit E541 two consequent identical highlighting + # specifiers may be squashed into one. + attrs = attrs or 0 # Normalize `attrs` + if (fg, bg, attrs) == self.prev_highlight: + return '' + self.prev_highlight = (fg, bg, attrs) + + # We don’t need to explicitly reset attributes in vim, so skip those + # calls + if not attrs and not bg and not fg: + return '' + + if not (fg, bg, attrs) in self.hl_groups: + hl_group = { + 'ctermfg': 'NONE', + 'guifg': None, + 'ctermbg': 'NONE', + 'guibg': None, + 'attrs': ['NONE'], + 'name': '', + } + if fg is not None and fg is not False: + hl_group['ctermfg'] = fg[0] + hl_group['guifg'] = fg[1] + if bg is not None and bg is not False: + hl_group['ctermbg'] = bg[0] + hl_group['guibg'] = bg[1] + if attrs: + hl_group['attrs'] = [] + if attrs & ATTR_BOLD: + hl_group['attrs'].append('bold') + if attrs & ATTR_ITALIC: + hl_group['attrs'].append('italic') + if attrs & ATTR_UNDERLINE: + hl_group['attrs'].append('underline') + hl_group['name'] = ( + 'Pl_' + + str(hl_group['ctermfg']) + '_' + + str(hl_group['guifg']) + '_' + + str(hl_group['ctermbg']) + '_' + + str(hl_group['guibg']) + '_' + + ''.join(hl_group['attrs']) + ) + self.hl_groups[(fg, bg, attrs)] = hl_group + vim.command('hi {group} ctermfg={ctermfg} guifg={guifg} guibg={guibg} ctermbg={ctermbg} cterm={attrs} gui={attrs}'.format( + group=hl_group['name'], + ctermfg=hl_group['ctermfg'], + guifg='#{0:06x}'.format(hl_group['guifg']) if hl_group['guifg'] is not None else 'NONE', + ctermbg=hl_group['ctermbg'], + guibg='#{0:06x}'.format(hl_group['guibg']) if hl_group['guibg'] is not None else 'NONE', + attrs=','.join(hl_group['attrs']), + )) + return '%#' + self.hl_groups[(fg, bg, attrs)]['name'] + '#' + + +renderer = VimRenderer diff --git a/powerline/segment.py b/powerline/segment.py new file mode 100644 index 0000000..c83bf6f --- /dev/null +++ b/powerline/segment.py @@ -0,0 +1,450 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.lib.watcher import create_file_watcher + + +def list_segment_key_values(segment, theme_configs, segment_data, key, function_name=None, name=None, module=None, default=None): + try: + yield segment[key] + except KeyError: + pass + found_module_key = False + for theme_config in theme_configs: + try: + segment_data = theme_config['segment_data'] + except KeyError: + pass + else: + if function_name and not name: + if module: + try: + yield segment_data[module + '.' + function_name][key] + found_module_key = True + except KeyError: + pass + if not found_module_key: + try: + yield segment_data[function_name][key] + except KeyError: + pass + if name: + try: + yield segment_data[name][key] + except KeyError: + pass + if segment_data is not None: + try: + yield segment_data[key] + except KeyError: + pass + yield default + + +def get_segment_key(merge, *args, **kwargs): + if merge: + ret = None + for value in list_segment_key_values(*args, **kwargs): + if ret is None: + ret = value + elif isinstance(ret, dict) and isinstance(value, dict): + old_ret = ret + ret = value.copy() + ret.update(old_ret) + else: + return ret + return ret + else: + return next(list_segment_key_values(*args, **kwargs)) + + +def get_function(data, segment): + function_name = segment['function'] + if '.' in function_name: + module, function_name = function_name.rpartition('.')[::2] + else: + module = data['default_module'] + function = data['get_module_attr'](module, function_name, prefix='segment_generator') + if not function: + raise ImportError('Failed to obtain segment function') + return None, function, module, function_name, segment.get('name') + + +def get_string(data, segment): + name = segment.get('name') + return data['get_key'](False, segment, None, None, name, 'contents'), None, None, None, name + + +segment_getters = { + 'function': get_function, + 'string': get_string, + 'segment_list': get_function, +} + + +def get_attr_func(contents_func, key, args, is_space_func=False): + try: + func = getattr(contents_func, key) + except AttributeError: + return None + else: + if is_space_func: + def expand_func(pl, amount, segment): + try: + return func(pl=pl, amount=amount, segment=segment, **args) + except Exception as e: + pl.exception('Exception while computing {0} function: {1}', key, str(e)) + return segment['contents'] + (' ' * amount) + return expand_func + else: + return lambda pl, shutdown_event: func(pl=pl, shutdown_event=shutdown_event, **args) + + +def process_segment_lister(pl, segment_info, parsed_segments, side, mode, colorscheme, + lister, subsegments, patcher_args): + subsegments = [ + subsegment + for subsegment in subsegments + if subsegment['display_condition'](pl, segment_info, mode) + ] + for subsegment_info, subsegment_update in lister(pl=pl, segment_info=segment_info, **patcher_args): + draw_inner_divider = subsegment_update.pop('draw_inner_divider', False) + old_pslen = len(parsed_segments) + for subsegment in subsegments: + if subsegment_update: + subsegment = subsegment.copy() + subsegment.update(subsegment_update) + if 'priority_multiplier' in subsegment_update and subsegment['priority']: + subsegment['priority'] *= subsegment_update['priority_multiplier'] + + process_segment( + pl, + side, + subsegment_info, + parsed_segments, + subsegment, + mode, + colorscheme, + ) + new_pslen = len(parsed_segments) + while parsed_segments[new_pslen - 1]['literal_contents'][1]: + new_pslen -= 1 + if new_pslen > old_pslen + 1 and draw_inner_divider is not None: + for i in range(old_pslen, new_pslen - 1) if side == 'left' else range(old_pslen + 1, new_pslen): + parsed_segments[i]['draw_soft_divider'] = draw_inner_divider + return None + + +def set_segment_highlighting(pl, colorscheme, segment, mode): + if segment['literal_contents'][1]: + return True + try: + highlight_group_prefix = segment['highlight_group_prefix'] + except KeyError: + hl_groups = lambda hlgs: hlgs + else: + hl_groups = lambda hlgs: [highlight_group_prefix + ':' + hlg for hlg in hlgs] + hlgs + try: + segment['highlight'] = colorscheme.get_highlighting( + hl_groups(segment['highlight_groups']), + mode, + segment.get('gradient_level') + ) + if segment['divider_highlight_group']: + segment['divider_highlight'] = colorscheme.get_highlighting( + hl_groups([segment['divider_highlight_group']]), + mode + ) + else: + segment['divider_highlight'] = None + except Exception as e: + pl.exception('Failed to set highlight group: {0}', str(e)) + return False + else: + return True + + +def process_segment(pl, side, segment_info, parsed_segments, segment, mode, colorscheme): + segment = segment.copy() + pl.prefix = segment['name'] + if segment['type'] in ('function', 'segment_list'): + try: + if segment['type'] == 'function': + contents = segment['contents_func'](pl, segment_info) + else: + contents = segment['contents_func'](pl, segment_info, parsed_segments, side, mode, colorscheme) + except Exception as e: + pl.exception('Exception while computing segment: {0}', str(e)) + return + + if contents is None: + return + + if isinstance(contents, list): + # Needs copying here, but it was performed at the very start of the + # function + segment_base = segment + if contents: + draw_divider_position = -1 if side == 'left' else 0 + for key, i, newval in ( + ('before', 0, ''), + ('after', -1, ''), + ('draw_soft_divider', draw_divider_position, True), + ('draw_hard_divider', draw_divider_position, True), + ): + try: + contents[i][key] = segment_base.pop(key) + segment_base[key] = newval + except KeyError: + pass + + draw_inner_divider = None + if side == 'right': + append = parsed_segments.append + else: + pslen = len(parsed_segments) + append = lambda item: parsed_segments.insert(pslen, item) + + for subsegment in (contents if side == 'right' else reversed(contents)): + segment_copy = segment_base.copy() + segment_copy.update(subsegment) + if draw_inner_divider is not None: + segment_copy['draw_soft_divider'] = draw_inner_divider + draw_inner_divider = segment_copy.pop('draw_inner_divider', None) + if set_segment_highlighting(pl, colorscheme, segment_copy, mode): + append(segment_copy) + else: + segment['contents'] = contents + if set_segment_highlighting(pl, colorscheme, segment, mode): + parsed_segments.append(segment) + elif segment['width'] == 'auto' or (segment['type'] == 'string' and segment['contents'] is not None): + if set_segment_highlighting(pl, colorscheme, segment, mode): + parsed_segments.append(segment) + + +always_true = lambda pl, segment_info, mode: True + +get_fallback_segment = { + 'name': 'fallback', + 'type': 'string', + 'highlight_groups': ['background'], + 'divider_highlight_group': None, + 'before': None, + 'after': None, + 'contents': '', + 'literal_contents': (0, ''), + 'priority': None, + 'draw_soft_divider': True, + 'draw_hard_divider': True, + 'draw_inner_divider': True, + 'display_condition': always_true, + 'width': None, + 'align': None, + 'expand': None, + 'truncate': None, + 'startup': None, + 'shutdown': None, + '_rendered_raw': '', + '_rendered_hl': '', + '_len': None, + '_contents_len': None, +}.copy + + +def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, get_module_attr, top_theme): + data = { + 'default_module': default_module or 'powerline.segments.' + ext, + 'get_module_attr': get_module_attr, + 'segment_data': None, + } + + def get_key(merge, segment, module, function_name, name, key, default=None): + return get_segment_key(merge, segment, theme_configs, data['segment_data'], key, function_name, name, module, default) + data['get_key'] = get_key + + def get_selector(function_name): + if '.' in function_name: + module, function_name = function_name.rpartition('.')[::2] + else: + module = 'powerline.selectors.' + ext + function = get_module_attr(module, function_name, prefix='segment_generator/selector_function') + if not function: + pl.error('Failed to get segment selector, ignoring it') + return function + + def get_segment_selector(segment, selector_type): + try: + function_name = segment[selector_type + '_function'] + except KeyError: + function = None + else: + function = get_selector(function_name) + try: + modes = segment[selector_type + '_modes'] + except KeyError: + modes = None + + if modes: + if function: + return lambda pl, segment_info, mode: ( + mode in modes + or function(pl=pl, segment_info=segment_info, mode=mode) + ) + else: + return lambda pl, segment_info, mode: mode in modes + else: + if function: + return lambda pl, segment_info, mode: ( + function(pl=pl, segment_info=segment_info, mode=mode) + ) + else: + return None + + def gen_display_condition(segment): + include_function = get_segment_selector(segment, 'include') + exclude_function = get_segment_selector(segment, 'exclude') + if include_function: + if exclude_function: + return lambda *args: ( + include_function(*args) + and not exclude_function(*args)) + else: + return include_function + else: + if exclude_function: + return lambda *args: not exclude_function(*args) + else: + return always_true + + def get(segment, side): + segment_type = segment.get('type', 'function') + try: + get_segment_info = segment_getters[segment_type] + except KeyError: + pl.error('Unknown segment type: {0}', segment_type) + return None + + try: + contents, _contents_func, module, function_name, name = get_segment_info(data, segment) + except Exception as e: + pl.exception('Failed to generate segment from {0!r}: {1}', segment, str(e), prefix='segment_generator') + return None + + if not get_key(False, segment, module, function_name, name, 'display', True): + return None + + segment_datas = getattr(_contents_func, 'powerline_segment_datas', None) + if segment_datas: + try: + data['segment_data'] = segment_datas[top_theme] + except KeyError: + pass + + if segment_type == 'function': + highlight_groups = [function_name] + else: + highlight_groups = segment.get('highlight_groups') or [name] + + if segment_type in ('function', 'segment_list'): + args = dict(( + (str(k), v) + for k, v in + get_key(True, segment, module, function_name, name, 'args', {}).items() + )) + + display_condition = gen_display_condition(segment) + + if segment_type == 'segment_list': + # Handle startup and shutdown of _contents_func? + subsegments = [ + subsegment + for subsegment in ( + get(subsegment, side) + for subsegment in segment['segments'] + ) if subsegment + ] + return { + 'name': name or function_name, + 'type': segment_type, + 'highlight_groups': None, + 'divider_highlight_group': None, + 'before': None, + 'after': None, + 'contents_func': lambda pl, segment_info, parsed_segments, side, mode, colorscheme: ( + process_segment_lister( + pl, segment_info, parsed_segments, side, mode, colorscheme, + patcher_args=args, + subsegments=subsegments, + lister=_contents_func, + ) + ), + 'contents': None, + 'literal_contents': None, + 'priority': None, + 'draw_soft_divider': None, + 'draw_hard_divider': None, + 'draw_inner_divider': None, + 'side': side, + 'display_condition': display_condition, + 'width': None, + 'align': None, + 'expand': None, + 'truncate': None, + 'startup': None, + 'shutdown': None, + '_rendered_raw': '', + '_rendered_hl': '', + '_len': None, + '_contents_len': None, + } + + if segment_type == 'function': + startup_func = get_attr_func(_contents_func, 'startup', args) + shutdown_func = getattr(_contents_func, 'shutdown', None) + expand_func = get_attr_func(_contents_func, 'expand', args, True) + truncate_func = get_attr_func(_contents_func, 'truncate', args, True) + + if hasattr(_contents_func, 'powerline_requires_filesystem_watcher'): + create_watcher = lambda: create_file_watcher(pl, common_config['watcher']) + args[str('create_watcher')] = create_watcher + + if hasattr(_contents_func, 'powerline_requires_segment_info'): + contents_func = lambda pl, segment_info: _contents_func(pl=pl, segment_info=segment_info, **args) + else: + contents_func = lambda pl, segment_info: _contents_func(pl=pl, **args) + else: + startup_func = None + shutdown_func = None + contents_func = None + expand_func = None + truncate_func = None + + return { + 'name': name or function_name, + 'type': segment_type, + 'highlight_groups': highlight_groups, + 'divider_highlight_group': None, + 'before': get_key(False, segment, module, function_name, name, 'before', ''), + 'after': get_key(False, segment, module, function_name, name, 'after', ''), + 'contents_func': contents_func, + 'contents': contents, + 'literal_contents': (0, ''), + 'priority': segment.get('priority', None), + 'draw_hard_divider': segment.get('draw_hard_divider', True), + 'draw_soft_divider': segment.get('draw_soft_divider', True), + 'draw_inner_divider': segment.get('draw_inner_divider', False), + 'side': side, + 'display_condition': display_condition, + 'width': segment.get('width'), + 'align': segment.get('align', 'l'), + 'expand': expand_func, + 'truncate': truncate_func, + 'startup': startup_func, + 'shutdown': shutdown_func, + '_rendered_raw': '', + '_rendered_hl': '', + '_len': None, + '_contents_len': None, + } + + return get diff --git a/powerline/segments/__init__.py b/powerline/segments/__init__.py new file mode 100644 index 0000000..fa09e58 --- /dev/null +++ b/powerline/segments/__init__.py @@ -0,0 +1,63 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +from pkgutil import extend_path +from types import MethodType + + +__path__ = extend_path(__path__, __name__) + + +class Segment(object): + '''Base class for any segment that is not a function + + Required for powerline.lint.inspect to work properly: it defines methods for + omitting existing or adding new arguments. + + .. note:: + Until python-3.4 ``inspect.getargspec`` does not support querying + callable classes for arguments of their ``__call__`` method, requiring + to use this method directly (i.e. before 3.4 you should write + ``getargspec(obj.__call__)`` in place of ``getargspec(obj)``). + ''' + if sys.version_info < (3, 4): + def argspecobjs(self): + yield '__call__', self.__call__ + else: + def argspecobjs(self): + yield '__call__', self + + argspecobjs.__doc__ = ( + '''Return a list of valid arguments for inspect.getargspec + + Used to determine function arguments. + ''' + ) + + def omitted_args(self, name, method): + '''List arguments which should be omitted + + Returns a tuple with indexes of omitted arguments. + + .. note::``segment_info``, ``create_watcher`` and ``pl`` will be omitted + regardless of the below return (for ``segment_info`` and + ``create_watcher``: only if object was marked to require segment + info or filesystem watcher). + ''' + if isinstance(self.__call__, MethodType): + return (0,) + else: + return () + + @staticmethod + def additional_args(): + '''Returns a list of (additional argument name[, default value]) tuples. + ''' + return () + + +def with_docstring(instance, doc): + instance.__doc__ = doc + return instance diff --git a/powerline/segments/common/__init__.py b/powerline/segments/common/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/segments/common/__init__.py diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py new file mode 100644 index 0000000..c892f62 --- /dev/null +++ b/powerline/segments/common/bat.py @@ -0,0 +1,302 @@ +# 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 + + +def _fetch_battery_info(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', '').endswith('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' + devices = [] + 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 + devices.append(devpath) + pl.debug('Using DBUS+UPower with {0}', devpath) + if devices: + def _flatten_battery(pl): + energy = 0.0 + energy_full = 0.0 + state = True + for devpath in devices: + dev = bus.get_object(interface, devpath) + energy_full += float( + dbus.Interface(dev, dbus_interface=devinterface).Get( + devtype_name, + 'EnergyFull' + ), + ) + energy += float( + dbus.Interface(dev, dbus_interface=devinterface).Get( + devtype_name, + 'Energy' + ), + ) + state &= dbus.Interface(dev, dbus_interface=devinterface).Get( + devtype_name, + 'State' + ) != 2 + if energy_full > 0: + return (energy * 100.0 / energy_full), state + else: + return 0.0, state + return _flatten_battery + pl.debug('Not using DBUS+UPower as no batteries were found') + + if os.path.isdir('/sys/class/power_supply'): + # ENERGY_* attributes represents capacity in µWh only. + # CHARGE_* attributes represents capacity in µAh only. + linux_capacity_units = ('energy', 'charge') + linux_energy_full_fmt = '/sys/class/power_supply/{0}/{1}_full' + linux_energy_fmt = '/sys/class/power_supply/{0}/{1}_now' + linux_status_fmt = '/sys/class/power_supply/{0}/status' + devices = [] + for linux_supplier in os.listdir('/sys/class/power_supply'): + for unit in linux_capacity_units: + energy_path = linux_energy_fmt.format(linux_supplier, unit) + if not os.path.exists(energy_path): + continue + pl.debug('Using /sys/class/power_supply with battery {0} and unit {1}', + linux_supplier, unit) + devices.append((linux_supplier, unit)) + break # energy or charge, not both + if devices: + def _get_battery_status(pl): + energy = 0.0 + energy_full = 0.0 + state = True + for device, unit in devices: + with open(linux_energy_full_fmt.format(device, unit), 'r') as f: + energy_full += int(float(f.readline().split()[0])) + with open(linux_energy_fmt.format(device, unit), 'r') as f: + energy += int(float(f.readline().split()[0])) + try: + with open(linux_status_fmt.format(device), 'r') as f: + state &= (f.readline().strip() != 'Discharging') + except IOError: + state = None + return (energy * 100.0 / energy_full), state + return _get_battery_status + pl.debug('Not using /sys/class/power_supply as no batteries were found') + else: + pl.debug("Checking for first capacity battery percentage") + for batt in os.listdir('/sys/class/power_supply'): + if os.path.exists('/sys/class/power_supply/{0}/capacity'.format(batt)): + def _get_battery_perc(pl): + state = True + with open('/sys/class/power_supply/{0}/capacity'.format(batt), 'r') as f: + perc = int(f.readline().split()[0]) + try: + with open(linux_status_fmt.format(batt), 'r') as f: + state &= (f.readline().strip() != 'Discharging') + except IOError: + state = None + return perc, state + return _get_battery_perc + 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_battery_status(pl): + battery_summary = run_cmd(pl, ['pmset', '-g', 'batt']) + battery_percent = BATTERY_PERCENT_RE.search(battery_summary).group(1) + ac_charging = 'AC' in battery_summary + return int(battery_percent), ac_charging + return _get_battery_status + else: + pl.debug('Not using pmset: executable not found') + + if sys.platform.startswith('win') or sys.platform == 'cygwin': + # 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_battery_status(pl): + # http://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx + return battery.EstimatedChargeRemaining, battery.BatteryStatus == 6 + + return _get_battery_status + pl.debug('Not using win32com.client as no batteries were found') + from ctypes import Structure, c_byte, c_ulong, byref + if sys.platform == 'cygwin': + pl.debug('Using cdll to communicate with kernel32 (Cygwin)') + from ctypes import cdll + library_loader = cdll + else: + pl.debug('Using windll to communicate with kernel32 (Windows)') + from ctypes import windll + library_loader = windll + + class PowerClass(Structure): + _fields_ = [ + ('ACLineStatus', c_byte), + ('BatteryFlag', c_byte), + ('BatteryLifePercent', c_byte), + ('Reserved1', c_byte), + ('BatteryLifeTime', c_ulong), + ('BatteryFullLifeTime', c_ulong) + ] + + def _get_battery_status(pl): + powerclass = PowerClass() + result = library_loader.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, powerclass.ACLineStatus == 1 + + if _get_battery_status() is None: + pl.debug('Not using GetSystemPowerStatus because it failed') + else: + pl.debug('Using GetSystemPowerStatus') + + return _get_battery_status + + raise NotImplementedError + + +def _get_battery_status(pl): + global _get_battery_status + + def _failing_get_status(pl): + raise NotImplementedError + + try: + _get_battery_status = _fetch_battery_info(pl) + except NotImplementedError: + _get_battery_status = _failing_get_status + except Exception as e: + pl.exception('Exception while obtaining battery status: {0}', str(e)) + _get_battery_status = _failing_get_status + return _get_battery_status(pl) + + +def battery(pl, format='{ac_state} {capacity:3.0%}', steps=5, gamify=False, full_heart='O', empty_heart='O', online='C', offline=' '): + '''Return battery charge status. + + :param str format: + Percent format in case gamify is False. Format arguments: ``ac_state`` + which is equal to either ``online`` or ``offline`` string arguments and + ``capacity`` which is equal to current battery capacity in interval [0, + 100]. + :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``. ``battery_online`` or ``battery_offline`` group + will be used for leading segment containing ``online`` or ``offline`` + argument contents. + :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. + :param str online: + Symbol used if computer is connected to a power supply. + :param str offline: + Symbol used if computer is not connected to a power supply. + + ``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``, ``battery_online`` or ``battery_ac_state`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_offline`` or ``battery_ac_state`` or ``battery_gradient`` (gradient) or ``battery``. + ''' + try: + capacity, ac_powered = _get_battery_status(pl) + except NotImplementedError: + pl.info('Unable to get battery status.') + return None + + ret = [] + if gamify: + denom = int(steps) + numer = int(denom * capacity / 100) + ret.append({ + 'contents': online if ac_powered else offline, + 'draw_inner_divider': False, + 'highlight_groups': ['battery_online' if ac_powered else 'battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'], + 'gradient_level': 0, + }) + ret.append({ + 'contents': full_heart * numer, + 'draw_inner_divider': False, + 'highlight_groups': ['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_groups': ['battery_empty', 'battery_gradient', 'battery'], + # Using a hundred as it is most alert color. + 'gradient_level': 100, + }) + else: + ret.append({ + 'contents': format.format(ac_state=(online if ac_powered else offline), capacity=(capacity / 100.0)), + 'highlight_groups': ['battery_gradient', 'battery'], + # Gradients are “least alert – most alert” by default, capacity has + # the opposite semantics. + 'gradient_level': 100 - capacity, + }) + return ret diff --git a/powerline/segments/common/env.py b/powerline/segments/common/env.py new file mode 100644 index 0000000..61f516e --- /dev/null +++ b/powerline/segments/common/env.py @@ -0,0 +1,197 @@ +# 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, ignore_venv=False, ignore_conda=False): + '''Return the name of the current Python or conda virtualenv. + + :param bool ignore_venv: + Whether to ignore virtual environments. Default is False. + :param bool ignore_conda: + Whether to ignore conda environments. Default is False. + ''' + return ( + (not ignore_venv and + os.path.basename(segment_info['environ'].get('VIRTUAL_ENV', ''))) or + (not ignore_conda and + segment_info['environ'].get('CONDA_DEFAULT_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 () + 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_groups'] = ['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: + try: + from os import geteuid as getuid + except ImportError: + from os import getuid + + def _get_user(): + return pwd.getpwuid(getuid()).pw_name + + +username = False +# os.geteuid is not available on windows +_geteuid = getattr(os, 'geteuid', lambda: 1) + + +@requires_segment_info +def user(pl, segment_info, hide_user=None, hide_domain=False): + '''Return the current user. + + :param str hide_user: + Omit showing segment for users with names equal to this string. + :param bool hide_domain: + Drop domain component if it exists in a username (delimited by '@'). + + 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 ( + segment_info['environ'].get('_POWERLINE_RUNNING_SHELL_TESTS') + == 'ee5bcdc6-b749-11e7-9456-50465d597777' + ): + return 'user' + 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 + if hide_domain: + try: + username = username[:username.index('@')] + except ValueError: + pass + euid = _geteuid() + return [{ + 'contents': username, + 'highlight_groups': ['user'] if euid != 0 else ['superuser', 'user'], + }] diff --git a/powerline/segments/common/mail.py b/powerline/segments/common/mail.py new file mode 100644 index 0000000..8202492 --- /dev/null +++ b/powerline/segments/common/mail.py @@ -0,0 +1,78 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re + +from imaplib import IMAP4_SSL_PORT, IMAP4_SSL, IMAP4 +from collections import namedtuple + +from powerline.lib.threaded import KwThreadedSegment +from powerline.segments import with_docstring + + +_IMAPKey = namedtuple('Key', 'username password server port folder use_ssl') + + +class EmailIMAPSegment(KwThreadedSegment): + interval = 60 + + @staticmethod + def key(username, password, server='imap.gmail.com', port=IMAP4_SSL_PORT, folder='INBOX', use_ssl=None, **kwargs): + if use_ssl is None: + use_ssl = (port == IMAP4_SSL_PORT) + return _IMAPKey(username, password, server, port, folder, use_ssl) + + def compute_state(self, key): + if not key.username or not key.password: + self.warn('Username and password are not configured') + return None + if key.use_ssl: + mail = IMAP4_SSL(key.server, key.port) + else: + mail = IMAP4(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_groups': ['email_alert'], + }] + else: + return [{ + 'contents': str(unread_count), + 'highlight_groups': ['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. +:param bool use_ssl: + If ``True`` then use SSL connection. If ``False`` then do not use it. + Default is ``True`` if port is equal to {ssl_port} and ``False`` otherwise. + +Highlight groups used: ``email_alert_gradient`` (gradient), ``email_alert``. +''').format(ssl_port=IMAP4_SSL_PORT)) diff --git a/powerline/segments/common/net.py b/powerline/segments/common/net.py new file mode 100644 index 0000000..b5d9062 --- /dev/null +++ b/powerline/segments/common/net.py @@ -0,0 +1,315 @@ +# 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 ( + segment_info['environ'].get('_POWERLINE_RUNNING_SHELL_TESTS') + == 'ee5bcdc6-b749-11e7-9456-50465d597777' + ): + return 'hostname' + 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='auto', ipv=4): + return None +else: + _interface_starts = { + 'eth': 10, # Regular ethernet adapters : eth1 + 'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0 + 'en': 10, # OS X : en0 + '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 + 'docker': -5, # Docker bridge interface : docker0 + 'vmnet': -5, # VMWare bridge interface : vmnet1 + 'vboxnet': -5, # VirtualBox bridge interface : vboxnet0 + } + + _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='auto', ipv=4): + family = netifaces.AF_INET6 if ipv == 6 else netifaces.AF_INET + if interface == 'auto': + try: + interface = next(iter(sorted(netifaces.interfaces(), key=_interface_key, reverse=True))) + except StopIteration: + pl.info('No network interfaces found') + return None + elif interface == 'default_gateway': + try: + interface = netifaces.gateways()['default'][family][1] + except KeyError: + pl.info('No default gateway found for IPv{0}', ipv) + return None + addrs = netifaces.ifaddresses(interface) + try: + return addrs[family][0]['addr'] + except (KeyError, IndexError): + pl.info("No IPv{0} address found for interface {1}", ipv, interface) + 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 ``auto`` 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. + + Use ``default_gateway`` to detect the interface based on the machine's + `default gateway <https://en.wikipedia.org/wiki/Default_gateway>`_ (i.e., + the router to which it is connected). + +: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(): + try: + io_counters = psutil.net_io_counters(pernic=True) + except AttributeError: + 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='auto', **kwargs): + return interface + + def compute_state(self, interface): + if interface == 'auto': + 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 == 'auto': + # 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': 'network_load:divider', + 'highlight_groups': 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 "auto" 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 that determines how download speed should look like. Receives + ``value`` as argument. +:param str sent_format: + Format string that determines how upload speed should look like. 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: ``network_load: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``. +''') diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py new file mode 100644 index 0000000..9298bc9 --- /dev/null +++ b/powerline/segments/common/players.py @@ -0,0 +1,607 @@ +# 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, with_docstring + + +STATE_SYMBOLS = { + 'fallback': '', + 'play': '>', + 'pause': '~', + 'stop': 'X', +} + + +def _convert_state(state): + '''Guess player state''' + state = state.lower() + if 'play' in state: + return 'play' + if 'pause' in state: + return 'pause' + if 'stop' in state: + return 'stop' + return 'fallback' + + +def _convert_seconds(seconds): + '''Convert seconds to minutes:seconds format''' + return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60)) + + +class PlayerSegment(Segment): + def __call__(self, format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs): + stats = { + 'state': 'fallback', + 'album': None, + 'artist': None, + 'title': None, + 'elapsed': None, + 'total': None, + } + func_stats = self.get_player_status(**kwargs) + if not func_stats: + return None + stats.update(func_stats) + stats['state_symbol'] = state_symbols.get(stats['state']) + return [{ + 'contents': format.format(**stats), + 'highlight_groups': ['player_' + (stats['state'] or 'fallback'), 'player'], + }] + + def get_player_status(self, pl): + pass + + def argspecobjs(self): + for ret in super(PlayerSegment, self).argspecobjs(): + yield ret + yield 'get_player_status', self.get_player_status + + def omitted_args(self, name, method): + return () + + +_common_args = ''' +This player segment should be added like this: + +.. code-block:: json + + {{ + "function": "powerline.segments.common.players.{0}", + "name": "player" + }} + +(with additional ``"args": {{…}}`` if needed). + +Highlight groups used: ``player_fallback`` or ``player``, ``player_play`` or ``player``, ``player_pause`` or ``player``, ``player_stop`` or ``player``. + +:param str format: + Format used for displaying data from player. Should be a str.format-like + string with the following keyword parameters: + + +------------+-------------------------------------------------------------+ + |Parameter |Description | + +============+=============================================================+ + |state_symbol|Symbol displayed for play/pause/stop states. There is also | + | |“fallback” state used in case function failed to get player | + | |state. For this state symbol is by default empty. All | + | |symbols are defined in ``state_symbols`` argument. | + +------------+-------------------------------------------------------------+ + |album |Album that is currently played. | + +------------+-------------------------------------------------------------+ + |artist |Artist whose song is currently played | + +------------+-------------------------------------------------------------+ + |title |Currently played composition. | + +------------+-------------------------------------------------------------+ + |elapsed |Composition duration in format M:SS (minutes:seconds). | + +------------+-------------------------------------------------------------+ + |total |Composition length in format M:SS. | + +------------+-------------------------------------------------------------+ +:param dict state_symbols: + Symbols used for displaying state. Must contain all of the following keys: + + ======== ======================================================== + Key Description + ======== ======================================================== + play Displayed when player is playing. + pause Displayed when player is paused. + stop Displayed when player is not playing anything. + fallback Displayed if state is not one of the above or not known. + ======== ======================================================== +''' + + +_player = with_docstring(PlayerSegment(), _common_args.format('_player')) + + +class CmusPlayerSegment(PlayerSegment): + def get_player_status(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 = _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': _convert_seconds(now_playing.get('position', 0)), + 'total': _convert_seconds(now_playing.get('duration', 0)), + } + + +cmus = with_docstring(CmusPlayerSegment(), +('''Return CMUS player information + +Requires cmus-remote command be acessible from $PATH. + +{0} +''').format(_common_args.format('cmus'))) + + +class MpdPlayerSegment(PlayerSegment): + def get_player_status(self, pl, host='localhost', password=None, port=6600): + try: + import mpd + except ImportError: + if password: + host = password + '@' + host + now_playing = run_cmd(pl, [ + 'mpc', 'current', + '-f', '%album%\n%artist%\n%title%\n%time%', + '-h', host, + '-p', str(port) + ], strip=False) + 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: + try: + client = mpd.MPDClient(use_unicode=True) + except TypeError: + # python-mpd 1.x does not support use_unicode + client = mpd.MPDClient() + client.connect(host, port) + if password: + client.password(password) + 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': _convert_seconds(status.get('elapsed', 0)), + 'total': _convert_seconds(now_playing.get('time', 0)), + } + + +mpd = with_docstring(MpdPlayerSegment(), +('''Return Music Player Daemon information + +Requires ``mpd`` Python module (e.g. |python-mpd2|_ or |python-mpd|_ Python +package) or alternatively the ``mpc`` command to be acessible from $PATH. + +.. |python-mpd| replace:: ``python-mpd`` +.. _python-mpd: https://pypi.python.org/pypi/python-mpd + +.. |python-mpd2| replace:: ``python-mpd2`` +.. _python-mpd2: https://pypi.python.org/pypi/python-mpd2 + +{0} +:param str host: + Host on which mpd runs. +:param str password: + Password used for connecting to daemon. +:param int port: + Port which should be connected to. +''').format(_common_args.format('mpd'))) + + +try: + import dbus +except ImportError: + def _get_dbus_player_status(pl, player_name, **kwargs): + pl.error('Could not add {0} segment: requires dbus module', player_name) + return +else: + def _get_dbus_player_status(pl, bus_name, player_path, iface_prop, + iface_player, player_name='player'): + 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 + + try: + elapsed = iface.Get(iface_player, 'Position') + except dbus.exceptions.DBusException: + pl.warning('Missing player elapsed time') + elapsed = None + else: + elapsed = _convert_seconds(elapsed / 1e6) + album = info.get('xesam:album') + title = info.get('xesam:title') + artist = info.get('xesam:artist') + state = _convert_state(status) + if album: + album = out_u(album) + if title: + title = out_u(title) + if artist: + artist = out_u(artist[0]) + return { + 'state': state, + 'album': album, + 'artist': artist, + 'title': title, + 'elapsed': elapsed, + 'total': _convert_seconds(info.get('mpris:length') / 1e6), + } + + +class DbusPlayerSegment(PlayerSegment): + get_player_status = staticmethod(_get_dbus_player_status) + + +dbus_player = with_docstring(DbusPlayerSegment(), +('''Return generic dbus player state + +Requires ``dbus`` python module. Only for players that support specific protocol + (e.g. like :py:func:`spotify` and :py:func:`clementine`). + +{0} +:param str player_name: + Player name. Used in error messages only. +:param str bus_name: + Dbus bus name. +:param str player_path: + Path to the player on the given bus. +:param str iface_prop: + Interface properties name for use with dbus.Interface. +:param str iface_player: + Player name. +''').format(_common_args.format('dbus_player'))) + + +class SpotifyDbusPlayerSegment(PlayerSegment): + def get_player_status(self, pl): + player_status = _get_dbus_player_status( + pl=pl, + player_name='Spotify', + bus_name='org.mpris.MediaPlayer2.spotify', + player_path='/org/mpris/MediaPlayer2', + iface_prop='org.freedesktop.DBus.Properties', + iface_player='org.mpris.MediaPlayer2.Player', + ) + if player_status is not None: + return player_status + # Fallback for legacy spotify client with different DBus protocol + return _get_dbus_player_status( + pl=pl, + player_name='Spotify', + bus_name='com.spotify.qt', + player_path='/', + iface_prop='org.freedesktop.DBus.Properties', + iface_player='org.freedesktop.MediaPlayer2', + ) + + +spotify_dbus = with_docstring(SpotifyDbusPlayerSegment(), +('''Return spotify player information + +Requires ``dbus`` python module. + +{0} +''').format(_common_args.format('spotify_dbus'))) + + +class SpotifyAppleScriptPlayerSegment(PlayerSegment): + def get_player_status(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 & "{0}" & player position + 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 = _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': _convert_seconds(int(spotify_status[4])/1000), + 'elapsed': _convert_seconds(spotify_status[5]), + } + + +spotify_apple_script = with_docstring(SpotifyAppleScriptPlayerSegment(), +('''Return spotify player information + +Requires ``osascript`` available in $PATH. + +{0} +''').format(_common_args.format('spotify_apple_script'))) + + +if not sys.platform.startswith('darwin'): + spotify = spotify_dbus + _old_name = 'spotify_dbus' +else: + spotify = spotify_apple_script + _old_name = 'spotify_apple_script' + + +spotify = with_docstring(spotify, spotify.__doc__.replace(_old_name, 'spotify')) + + +class ClementinePlayerSegment(PlayerSegment): + def get_player_status(self, pl): + return _get_dbus_player_status( + pl=pl, + 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', + ) + + +clementine = with_docstring(ClementinePlayerSegment(), +('''Return clementine player information + +Requires ``dbus`` python module. + +{0} +''').format(_common_args.format('clementine'))) + + +class RhythmboxPlayerSegment(PlayerSegment): + def get_player_status(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' + ], strip=False) + 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], + } + + +rhythmbox = with_docstring(RhythmboxPlayerSegment(), +('''Return rhythmbox player information + +Requires ``rhythmbox-client`` available in $PATH. + +{0} +''').format(_common_args.format('rhythmbox'))) + + +class RDIOPlayerSegment(PlayerSegment): + def get_player_status(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(status_delimiter) + if len(now_playing) != 6: + return + state = _convert_state(now_playing[5]) + total = _convert_seconds(now_playing[4]) + elapsed = _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, + } + + +rdio = with_docstring(RDIOPlayerSegment(), +('''Return rdio player information + +Requires ``osascript`` available in $PATH. + +{0} +''').format(_common_args.format('rdio'))) + + +class ITunesPlayerSegment(PlayerSegment): + def get_player_status(self, pl): + status_delimiter = '-~`/=' + ascript = ''' + tell application "System Events" + set process_list to (name of every process) + end tell + + if process_list contains "iTunes" then + tell application "iTunes" + if player state is playing then + set t_title to name of current track + set t_artist to artist of current track + set t_album to album of current track + set t_duration to duration of current track + set t_elapsed to player position + set t_state to player state + return t_title & "{0}" & t_artist & "{0}" & t_album & "{0}" & t_elapsed & "{0}" & t_duration & "{0}" & t_state + end if + end tell + end if + '''.format(status_delimiter) + now_playing = asrun(pl, ascript) + if not now_playing: + return + now_playing = now_playing.split(status_delimiter) + if len(now_playing) != 6: + return + title, artist, album = now_playing[0], now_playing[1], now_playing[2] + state = _convert_state(now_playing[5]) + total = _convert_seconds(now_playing[4]) + elapsed = _convert_seconds(now_playing[3]) + return { + 'title': title, + 'artist': artist, + 'album': album, + 'total': total, + 'elapsed': elapsed, + 'state': state + } + + +itunes = with_docstring(ITunesPlayerSegment(), +('''Return iTunes now playing information + +Requires ``osascript``. + +{0} +''').format(_common_args.format('itunes'))) + + +class MocPlayerSegment(PlayerSegment): + def get_player_status(self, pl): + '''Return Music On Console (mocp) player information. + + ``mocp -i`` returns current information i.e. + + .. code-block:: + + File: filename.format + Title: full title + Artist: artist name + SongTitle: song title + Album: album name + TotalTime: 00:00 + TimeLeft: 00:00 + TotalSec: 000 + CurrentTime: 00:00 + CurrentSec: 000 + Bitrate: 000kbps + AvgBitrate: 000kbps + Rate: 00kHz + + For the information we are looking for we don’t really care if we have + extra-timing information or bit rate level. The dictionary comprehension + in this method takes anything in ignore_info and brings the key inside + that to the right info of the dictionary. + ''' + now_playing_str = run_cmd(pl, ['mocp', '-i']) + if not now_playing_str: + return + + now_playing = dict(( + line.split(': ', 1) + for line in now_playing_str.split('\n')[:-1] + )) + state = _convert_state(now_playing.get('State', 'stop')) + return { + 'state': state, + 'album': now_playing.get('Album', ''), + 'artist': now_playing.get('Artist', ''), + 'title': now_playing.get('SongTitle', ''), + 'elapsed': _convert_seconds(now_playing.get('CurrentSec', 0)), + 'total': _convert_seconds(now_playing.get('TotalSec', 0)), + } + + +mocp = with_docstring(MocPlayerSegment(), +('''Return MOC (Music On Console) player information + +Requires version >= 2.3.0 and ``mocp`` executable in ``$PATH``. + +{0} +''').format(_common_args.format('mocp'))) + diff --git a/powerline/segments/common/sys.py b/powerline/segments/common/sys.py new file mode 100644 index 0000000..a83025b --- /dev/null +++ b/powerline/segments/common/sys.py @@ -0,0 +1,183 @@ +# 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, short=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. + :param bool short: + if True only the sys load over last 1 minute will be displayed. + + 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_groups': ['system_load_gradient', 'system_load'], + 'divider_highlight_group': 'background:divider', + 'gradient_level': gradient_level, + }) + + if short: + return ret + + 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_groups': ['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 + + if hasattr(psutil, 'boot_time'): + def _get_uptime(): + return int(time() - psutil.boot_time()) + else: + def _get_uptime(): + 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() diff --git a/powerline/segments/common/time.py b/powerline/segments/common/time.py new file mode 100644 index 0000000..1e2207b --- /dev/null +++ b/powerline/segments/common/time.py @@ -0,0 +1,94 @@ +# 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``. + ''' + try: + contents = datetime.now().strftime(format) + except UnicodeEncodeError: + contents = datetime.now().strftime(format.encode('utf-8')).decode('utf-8') + + return [{ + 'contents': contents, + 'highlight_groups': (['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 diff --git a/powerline/segments/common/vcs.py b/powerline/segments/common/vcs.py new file mode 100644 index 0000000..07679ae --- /dev/null +++ b/powerline/segments/common/vcs.py @@ -0,0 +1,89 @@ +# 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.segments import Segment, with_docstring +from powerline.theme import requires_segment_info, requires_filesystem_watcher + + +@requires_filesystem_watcher +@requires_segment_info +class BranchSegment(Segment): + divider_highlight_group = None + + @staticmethod + def get_directory(segment_info): + return segment_info['getcwd']() + + def __call__(self, pl, segment_info, create_watcher, status_colors=False, ignore_statuses=()): + name = self.get_directory(segment_info) + if name: + 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 = '?' + else: + status = status and status.strip() + if status in ignore_statuses: + status = None + scol.insert(0, 'branch_dirty' if status else 'branch_clean') + return [{ + 'contents': branch, + 'highlight_groups': scol, + 'divider_highlight_group': self.divider_highlight_group, + }] + + +branch = with_docstring(BranchSegment(), +'''Return the current VCS branch. + +:param bool status_colors: + Determines whether repository status will be used to determine highlighting. + Default: False. +:param list ignore_statuses: + List of statuses which will not result in repo being marked as dirty. Most + useful is setting this option to ``["U"]``: this will ignore repository + which has just untracked files (i.e. repository with modified, deleted or + removed files will be marked as dirty, while just untracked files will make + segment show clean repository). Only applicable if ``status_colors`` option + is True. + +Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. +''') + + +@requires_filesystem_watcher +@requires_segment_info +class StashSegment(Segment): + divider_highlight_group = None + + @staticmethod + def get_directory(segment_info): + return segment_info['getcwd']() + + def __call__(self, pl, segment_info, create_watcher): + name = self.get_directory(segment_info) + if name: + repo = guess(path=name, create_watcher=create_watcher) + if repo is not None: + stash = getattr(repo, 'stash', None) + if stash: + stashes = stash() + if stashes: + return [{ + 'contents': str(stashes), + 'highlight_groups': ['stash'], + 'divider_highlight_group': self.divider_highlight_group + }] + +stash = with_docstring(StashSegment(), +'''Return the number of current VCS stash entries, if any. + +Highlight groups used: ``stash``. +''') diff --git a/powerline/segments/common/wthr.py b/powerline/segments/common/wthr.py new file mode 100644 index 0000000..1c6d080 --- /dev/null +++ b/powerline/segments/common/wthr.py @@ -0,0 +1,235 @@ +# 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', 'windy' ), # 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://geoip.nekudo.com/api/')) + location = ','.join(( + location_data['city'], + location_data['country']['name'], + location_data['country']['code'] + )) + self.info('Location returned by nekudo 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 weather.forecast where woeid in' + ' (select woeid from geo.places(1) where text="{0}") and u="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 None + + response = json.loads(raw_response) + try: + condition = response['query']['results']['channel']['item']['condition'] + condition_code = int(condition['code']) + temp = float(condition['temp']) + except (KeyError, ValueError): + self.exception('Yahoo returned malformed or unexpected response: {0}', repr(raw_response)) + return None + + 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_groups': groups, + 'divider_highlight_group': 'background:divider', + }, + { + 'contents': temp_format.format(temp=converted_temp), + 'highlight_groups': ['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://geoip.nekudo.com 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. +''') diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py new file mode 100644 index 0000000..3f508ee --- /dev/null +++ b/powerline/segments/i3wm.py @@ -0,0 +1,155 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import re + +from powerline.theme import requires_segment_info +from powerline.bindings.wm import get_i3_connection + + +WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?') + + +def workspace_groups(w): + group = [] + if w['focused']: + group.append('w_focused') + if w['urgent']: + group.append('w_urgent') + if w['visible']: + group.append('w_visible') + group.append('workspace') + return group + + +def format_name(name, strip=False): + if strip: + return WORKSPACE_REGEX.sub('', name, count=1) + return name + + +@requires_segment_info +def workspaces(pl, segment_info, only_show=None, output=None, strip=0): + '''Return list of used workspaces + + :param list only_show: + Specifies which workspaces to show. Valid entries are ``"visible"``, + ``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces + are shown. + + :param str output: + May be set to the name of an X output. If specified, only workspaces + on that output are shown. Overrides automatic output detection by + the lemonbar renderer and bindings. + + :param int strip: + Specifies how many characters from the front of each workspace name + should be stripped (e.g. to remove workspace numbers). Defaults to zero. + + Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. + ''' + output = output or segment_info.get('output') + + return [ + { + 'contents': w['name'][strip:], + 'highlight_groups': workspace_groups(w) + } + for w in get_i3_connection().get_workspaces() + if ((not only_show or any(w[typ] for typ in only_show)) + and (not output or w['output'] == output)) + ] + + +@requires_segment_info +def workspace(pl, segment_info, workspace=None, strip=False): + '''Return the specified workspace name + + :param str workspace: + Specifies which workspace to show. If unspecified, may be set by the + ``list_workspaces`` lister if used, otherwise falls back to + currently focused workspace. + + :param bool strip: + Specifies whether workspace numbers (in the ``1: name`` format) should + be stripped from workspace names before being displayed. Defaults to false. + + Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. + ''' + if workspace: + try: + w = next(( + w for w in get_i3_connection().get_workspaces() + if w['name'] == workspace + )) + except StopIteration: + return None + elif segment_info.get('workspace'): + w = segment_info['workspace'] + else: + try: + w = next(( + w for w in get_i3_connection().get_workspaces() + if w['focused'] + )) + except StopIteration: + return None + + return [{ + 'contents': format_name(w['name'], strip=strip), + 'highlight_groups': workspace_groups(w) + }] + + +@requires_segment_info +def mode(pl, segment_info, names={'default': None}): + '''Returns current i3 mode + + :param dict names: + Specifies the string to show for various modes. + Use ``null`` to hide a mode (``default`` is hidden by default). + + Highligh groups used: ``mode`` + ''' + mode = segment_info['mode'] + if mode in names: + return names[mode] + return mode + + +def scratchpad_groups(w): + group = [] + if w.urgent: + group.append('scratchpad:urgent') + if w.nodes[0].focused: + group.append('scratchpad:focused') + if w.workspace().name != '__i3_scratch': + group.append('scratchpad:visible') + group.append('scratchpad') + return group + + +SCRATCHPAD_ICONS = { + 'fresh': 'O', + 'changed': 'X', +} + + +def scratchpad(pl, icons=SCRATCHPAD_ICONS): + '''Returns the windows currently on the scratchpad + + :param dict icons: + Specifies the strings to show for the different scratchpad window states. Must + contain the keys ``fresh`` and ``changed``. + + Highlight groups used: ``scratchpad`` or ``scratchpad:visible``, ``scratchpad`` or ``scratchpad:focused``, ``scratchpad`` or ``scratchpad:urgent``. + ''' + + return [ + { + 'contents': icons.get(w.scratchpad_state, icons['changed']), + 'highlight_groups': scratchpad_groups(w) + } + for w in get_i3_connection().get_tree().descendents() + if w.scratchpad_state != 'none' + ] diff --git a/powerline/segments/ipython.py b/powerline/segments/ipython.py new file mode 100644 index 0000000..622e0a5 --- /dev/null +++ b/powerline/segments/ipython.py @@ -0,0 +1,9 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.theme import requires_segment_info + + +@requires_segment_info +def prompt_count(pl, segment_info): + return str(segment_info['ipython'].prompt_count) diff --git a/powerline/segments/pdb.py b/powerline/segments/pdb.py new file mode 100644 index 0000000..bd6a38b --- /dev/null +++ b/powerline/segments/pdb.py @@ -0,0 +1,61 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from powerline.theme import requires_segment_info + + +@requires_segment_info +def current_line(pl, segment_info): + '''Displays line number that is next to be run + ''' + return str(segment_info['curframe'].f_lineno) + + +@requires_segment_info +def current_file(pl, segment_info, basename=True): + '''Displays current file name + + :param bool basename: + If true only basename is displayed. + ''' + filename = segment_info['curframe'].f_code.co_filename + if basename: + filename = os.path.basename(filename) + return filename + + +@requires_segment_info +def current_code_name(pl, segment_info): + '''Displays name of the code object of the current frame + ''' + return segment_info['curframe'].f_code.co_name + + +@requires_segment_info +def current_context(pl, segment_info): + '''Displays currently executed context name + + This is similar to :py:func:`current_code_name`, but gives more details. + + Currently it only gives module file name if code_name happens to be + ``<module>``. + ''' + name = segment_info['curframe'].f_code.co_name + if name == '<module>': + name = os.path.basename(segment_info['curframe'].f_code.co_filename) + return name + + +@requires_segment_info +def stack_depth(pl, segment_info, full_stack=False): + '''Displays current stack depth + + Result is relative to the stack depth at the time prompt was first run. + + :param bool full_stack: + If true then absolute depth is used. + ''' + return str(len(segment_info['pdb'].stack) - ( + 0 if full_stack else segment_info['initial_stack_length'])) diff --git a/powerline/segments/shell.py b/powerline/segments/shell.py new file mode 100644 index 0000000..a9e9609 --- /dev/null +++ b/powerline/segments/shell.py @@ -0,0 +1,174 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.theme import requires_segment_info +from powerline.segments import with_docstring +from powerline.segments.common.env import CwdSegment +from powerline.lib.unicode import out_u + + +@requires_segment_info +def jobnum(pl, segment_info, show_zero=False): + '''Return the number of jobs. + + :param bool show_zero: + If False (default) shows nothing if there are no jobs. Otherwise shows + zero for no jobs. + ''' + jobnum = segment_info['args'].jobnum + if jobnum is None or (not show_zero and jobnum == 0): + return None + else: + return str(jobnum) + + +@requires_segment_info +def last_status(pl, segment_info): + '''Return last exit code. + + Highlight groups used: ``exit_fail`` + ''' + if not segment_info['args'].last_exit_code: + return None + return [{'contents': str(segment_info['args'].last_exit_code), 'highlight_groups': ['exit_fail']}] + + +@requires_segment_info +def last_pipe_status(pl, segment_info): + '''Return last pipe status. + + Highlight groups used: ``exit_fail``, ``exit_success`` + ''' + last_pipe_status = ( + segment_info['args'].last_pipe_status + or (segment_info['args'].last_exit_code,) + ) + if any(last_pipe_status): + return [ + { + 'contents': str(status), + 'highlight_groups': ['exit_fail' if status else 'exit_success'], + 'draw_inner_divider': True + } + for status in last_pipe_status + ] + else: + return None + + +@requires_segment_info +def mode(pl, segment_info, override={'vicmd': 'COMMND', 'viins': 'INSERT'}, default=None): + '''Return the current mode. + + :param dict override: + dict for overriding mode strings. + :param str default: + If current mode is equal to this string then this segment will not get + displayed. If not specified the value is taken from + ``$POWERLINE_DEFAULT_MODE`` variable. This variable is set by zsh + bindings for any mode that does not start from ``vi``. + ''' + mode = segment_info.get('mode', None) + if not mode: + pl.debug('No mode specified') + return None + default = default or segment_info.get('default_mode', None) + if mode == default: + return None + try: + return override[mode] + except KeyError: + # Note: with zsh line editor you can emulate as much modes as you wish. + # Thus having unknown mode is not an error: maybe just some developer + # added support for his own zle widgets. As there is no built-in mode() + # function like in VimL and mode is likely be defined by our code or by + # somebody knowing what he is doing there is absolutely no need in + # keeping translations dictionary. + return mode.upper() + + +@requires_segment_info +def continuation(pl, segment_info, omit_cmdsubst=True, right_align=False, renames={}): + '''Display parser state. + + :param bool omit_cmdsubst: + Do not display cmdsubst parser state if it is the last one. + :param bool right_align: + Align to the right. + :param dict renames: + Rename states: ``{old_name : new_name}``. If ``new_name`` is ``None`` + then given state is not displayed. + + Highlight groups used: ``continuation``, ``continuation:current``. + ''' + if not segment_info.get('parser_state'): + return [{ + 'contents': '', + 'width': 'auto', + 'highlight_groups': ['continuation:current', 'continuation'], + }] + ret = [] + + for state in segment_info['parser_state'].split(): + state = renames.get(state, state) + if state: + ret.append({ + 'contents': state, + 'highlight_groups': ['continuation'], + 'draw_inner_divider': True, + }) + + if omit_cmdsubst and ret[-1]['contents'] == 'cmdsubst': + ret.pop(-1) + + if not ret: + ret.append({ + 'contents': '' + }) + + if right_align: + ret[0].update(width='auto', align='r') + ret[-1]['highlight_groups'] = ['continuation:current', 'continuation'] + else: + ret[-1].update(width='auto', align='l', highlight_groups=['continuation:current', 'continuation']) + + return ret + + +@requires_segment_info +class ShellCwdSegment(CwdSegment): + def get_shortened_path(self, pl, segment_info, use_shortened_path=True, **kwargs): + if use_shortened_path: + try: + return out_u(segment_info['shortened_path']) + except KeyError: + pass + return super(ShellCwdSegment, self).get_shortened_path(pl, segment_info, **kwargs) + + +cwd = with_docstring(ShellCwdSegment(), +'''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 use_shortened_path: + Use path from shortened_path ``--renderer-arg`` argument. If this argument + is present ``shorten_home`` argument is ignored. +: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. +''') diff --git a/powerline/segments/tmux.py b/powerline/segments/tmux.py new file mode 100644 index 0000000..1f34389 --- /dev/null +++ b/powerline/segments/tmux.py @@ -0,0 +1,22 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.bindings.tmux import get_tmux_output + + +def attached_clients(pl, minimum=1): + '''Return the number of tmux clients attached to the currently active session + + :param int minimum: + The minimum number of attached clients that must be present for this + segment to be visible. + ''' + session_output = get_tmux_output(pl, 'list-panes', '-F', '#{session_name}') + if not session_output: + return None + session_name = session_output.rstrip().split('\n')[0] + + attached_clients_output = get_tmux_output(pl, 'list-clients', '-t', session_name) + attached_count = len(attached_clients_output.rstrip().split('\n')) + + return None if attached_count < minimum else str(attached_count) diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py new file mode 100644 index 0000000..b637961 --- /dev/null +++ b/powerline/segments/vim/__init__.py @@ -0,0 +1,793 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import re +import csv +import sys + +from collections import defaultdict + +try: + import vim +except ImportError: + vim = object() + +from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption, + buffer_name, vim_getwinvar, + register_buffer_cache, current_tabpage, + list_tabpage_buffers_segment_info) +from powerline.theme import requires_segment_info, requires_filesystem_watcher +from powerline.lib import add_divider_highlight_group +from powerline.lib.vcs import guess +from powerline.lib.humanize_bytes import humanize_bytes +from powerline.lib import wraps_saveargs as wraps +from powerline.segments.common.vcs import BranchSegment, StashSegment +from powerline.segments import with_docstring +from powerline.lib.unicode import string, unicode + +try: + from __builtin__ import xrange as range +except ImportError: + pass + + +vim_funcs = { + 'virtcol': vim_get_func('virtcol', rettype='int'), + 'getpos': vim_get_func('getpos'), + 'fnamemodify': vim_get_func('fnamemodify', rettype='bytes'), + 'line2byte': vim_get_func('line2byte', rettype='int'), + 'line': vim_get_func('line', rettype='int'), +} + +vim_modes = { + 'n': 'NORMAL', + 'no': 'N-OPER', + 'v': 'VISUAL', + 'V': 'V-LINE', + '^V': 'V-BLCK', + 's': 'SELECT', + 'S': 'S-LINE', + '^S': 'S-BLCK', + 'i': 'INSERT', + 'ic': 'I-COMP', + 'ix': 'I-C_X ', + 'R': 'RPLACE', + 'Rv': 'V-RPLC', + 'Rc': 'R-COMP', + 'Rx': 'R-C_X ', + 'c': 'COMMND', + 'cv': 'VIM-EX', + 'ce': 'NRM-EX', + 'r': 'PROMPT', + 'rm': '-MORE-', + 'r?': 'CNFIRM', + '!': '!SHELL', + 't': 'TERM ', +} + + +# TODO Remove cache when needed +def window_cached(func): + cache = {} + + @requires_segment_info + @wraps(func) + def ret(segment_info, **kwargs): + window_id = segment_info['window_id'] + if segment_info['mode'] == 'nc': + return cache.get(window_id) + else: + if getattr(func, 'powerline_requires_segment_info', False): + r = func(segment_info=segment_info, **kwargs) + else: + r = func(**kwargs) + cache[window_id] = r + return r + + return ret + + +@requires_segment_info +def mode(pl, segment_info, override=None): + '''Return the current vim mode. + + If mode (returned by ``mode()`` VimL function, see ``:h mode()`` in Vim) + consists of multiple characters and necessary mode is not known to powerline + then it will fall back to mode with last character(s) ignored. + + :param dict override: + dict for overriding default mode strings, e.g. ``{ 'n': 'NORM' }`` + ''' + mode = segment_info['mode'] + if mode == 'nc': + return None + while mode: + try: + if not override: + return vim_modes[mode] + try: + return override[mode] + except KeyError: + return vim_modes[mode] + except KeyError: + mode = mode[:-1] + return 'BUG' + + +@window_cached +@requires_segment_info +def visual_range(pl, segment_info, CTRL_V_text='{rows} x {vcols}', v_text_oneline='C:{vcols}', v_text_multiline='L:{rows}', V_text='L:{rows}'): + '''Return the current visual selection range. + + :param str CTRL_V_text: + Text to display when in block visual or select mode. + :param str v_text_oneline: + Text to display when in charaterwise visual or select mode, assuming + selection occupies only one line. + :param str v_text_multiline: + Text to display when in charaterwise visual or select mode, assuming + selection occupies more then one line. + :param str V_text: + Text to display when in linewise visual or select mode. + + All texts are format strings which are passed the following parameters: + + ========= ============================================================= + Parameter Description + ========= ============================================================= + sline Line number of the first line of the selection + eline Line number of the last line of the selection + scol Column number of the first character of the selection + ecol Column number of the last character of the selection + svcol Virtual column number of the first character of the selection + secol Virtual column number of the last character of the selection + rows Number of lines in the selection + cols Number of 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:]] + eline, ecol, eoff = [int(v) for v in vim_funcs['getpos']('.')[1:]] + svcol = vim_funcs['virtcol']([sline, scol, soff]) + evcol = vim_funcs['virtcol']([eline, ecol, eoff]) + rows = abs(eline - sline) + 1 + cols = abs(ecol - scol) + 1 + vcols = abs(evcol - svcol) + 1 + return { + '^': CTRL_V_text, + 's': v_text_oneline if rows == 1 else v_text_multiline, + 'S': V_text, + 'v': v_text_oneline if rows == 1 else v_text_multiline, + 'V': V_text, + }.get(segment_info['mode'][0], '').format( + sline=sline, eline=eline, + scol=scol, ecol=ecol, + svcol=svcol, evcol=evcol, + rows=rows, cols=cols, vcols=vcols, + ) + + +@requires_segment_info +def modified_indicator(pl, segment_info, text='+'): + '''Return a file modified indicator. + + :param string text: + text to display if the current buffer is modified + ''' + return text if int(vim_getbufoption(segment_info, 'modified')) else None + + +@requires_segment_info +def tab_modified_indicator(pl, segment_info, text='+'): + '''Return a file modified indicator for tabpages. + + :param string text: + text to display if any buffer in the current tab is modified + + Highlight groups used: ``tab_modified_indicator`` or ``modified_indicator``. + ''' + for buf_segment_info in list_tabpage_buffers_segment_info(segment_info): + if int(vim_getbufoption(buf_segment_info, 'modified')): + return [{ + 'contents': text, + 'highlight_groups': ['tab_modified_indicator', 'modified_indicator'], + }] + return None + + +@requires_segment_info +def paste_indicator(pl, segment_info, text='PASTE'): + '''Return a paste mode indicator. + + :param string text: + text to display if paste mode is enabled + ''' + return text if int(vim.eval('&paste')) else None + + +@requires_segment_info +def readonly_indicator(pl, segment_info, text='RO'): + '''Return a read-only indicator. + + :param string text: + text to display if the current buffer is read-only + ''' + return text if int(vim_getbufoption(segment_info, 'readonly')) else None + + +SCHEME_RE = re.compile(b'^\\w[\\w\\d+\\-.]*(?=:)') + + +@requires_segment_info +def file_scheme(pl, segment_info): + '''Return the protocol part of the file. + + Protocol is the part of the full filename just before the colon which + starts with a latin letter and contains only latin letters, digits, plus, + period or hyphen (refer to `RFC3986 + <http://tools.ietf.org/html/rfc3986#section-3.1>`_ for the description of + URI scheme). If there is no such a thing ``None`` is returned, effectively + removing segment. + + .. note:: + Segment will not check whether there is ``//`` just after the + colon or if there is at least one slash after the scheme. Reason: it is + not always present. E.g. when opening file inside a zip archive file + name will look like :file:`zipfile:/path/to/archive.zip::file.txt`. + ``file_scheme`` segment will catch ``zipfile`` part here. + ''' + name = buffer_name(segment_info) + if not name: + return None + match = SCHEME_RE.match(name) + if match: + return match.group(0).decode('ascii') + + +@requires_segment_info +def file_directory(pl, segment_info, remove_scheme=True, shorten_user=True, shorten_cwd=True, shorten_home=False): + '''Return file directory (head component of the file path). + + :param bool remove_scheme: + Remove scheme part from the segment name, if present. See documentation + of file_scheme segment for the description of what scheme is. Also + removes the colon. + + :param bool shorten_user: + Shorten ``$HOME`` directory to :file:`~/`. Does not work for files with + scheme. + + :param bool shorten_cwd: + Shorten current directory to :file:`./`. Does not work for files with + scheme present. + + :param bool shorten_home: + Shorten all directories in :file:`/home/` to :file:`~user/` instead of + :file:`/home/user/`. Does not work for files with scheme present. + ''' + name = buffer_name(segment_info) + if not name: + return None + match = SCHEME_RE.match(name) + if match: + if remove_scheme: + name = name[len(match.group(0)) + 1:] # Remove scheme and colon + file_directory = vim_funcs['fnamemodify'](name, ':h') + else: + file_directory = vim_funcs['fnamemodify']( + name, + (':~' if shorten_user else '') + (':.' if shorten_cwd else '') + ':h' + ) + if not file_directory: + return None + if shorten_home and file_directory.startswith('/home/'): + file_directory = b'~' + file_directory[6:] + file_directory = file_directory.decode(segment_info['encoding'], 'powerline_vim_strtrans_error') + return file_directory + os.sep + + +@requires_segment_info +def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]'): + '''Return file name (tail component of the file path). + + :param bool display_no_file: + display a string if the buffer is missing a file name + :param str no_file_text: + the string to display if the buffer is missing a file name + + Highlight groups used: ``file_name_no_file`` or ``file_name``, ``file_name``. + ''' + name = buffer_name(segment_info) + if not name: + if display_no_file: + return [{ + 'contents': no_file_text, + 'highlight_groups': ['file_name_no_file', 'file_name'], + }] + else: + return None + return os.path.basename(name).decode(segment_info['encoding'], 'powerline_vim_strtrans_error') + + +@window_cached +def file_size(pl, suffix='B', si_prefix=False): + '''Return file size in &encoding. + + :param str suffix: + string appended to the file size + :param bool si_prefix: + 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 + ''' + # Note: returns file size in &encoding, not in &fileencoding. But returned + # size is updated immediately; and it is valid for any buffer + file_size = vim_funcs['line2byte'](len(vim.current.buffer) + 1) - 1 + if file_size < 0: + file_size = 0 + return humanize_bytes(file_size, suffix, si_prefix) + + +@requires_segment_info +@add_divider_highlight_group('background:divider') +def file_format(pl, segment_info): + '''Return file format (i.e. line ending type). + + :return: file format or None if unknown or missing file format + + Divider highlight group used: ``background:divider``. + ''' + return vim_getbufoption(segment_info, 'fileformat') or None + + +@requires_segment_info +@add_divider_highlight_group('background:divider') +def file_encoding(pl, segment_info): + '''Return file encoding/character set. + + :return: file encoding/character set or None if unknown or missing file encoding + + Divider highlight group used: ``background:divider``. + ''' + return vim_getbufoption(segment_info, 'fileencoding') or None + + +@requires_segment_info +@add_divider_highlight_group('background:divider') +def file_type(pl, segment_info): + '''Return file type. + + :return: file type or None if unknown file type + + Divider highlight group used: ``background:divider``. + ''' + return vim_getbufoption(segment_info, 'filetype') or None + + +@requires_segment_info +def window_title(pl, segment_info): + '''Return the window title. + + This currently looks at the ``quickfix_title`` window variable, + which is used by Syntastic and Vim itself. + + It is used in the quickfix theme.''' + try: + return vim_getwinvar(segment_info, 'quickfix_title') + except KeyError: + return None + + +@requires_segment_info +def line_percent(pl, segment_info, gradient=False): + '''Return the cursor position in the file as a percentage. + + :param bool gradient: + highlight the percentage with a color gradient (by default a green to red gradient) + + Highlight groups used: ``line_percent_gradient`` (gradient), ``line_percent``. + ''' + line_current = segment_info['window'].cursor[0] + line_last = len(segment_info['buffer']) + percentage = line_current * 100.0 / line_last + if not gradient: + return str(int(round(percentage))) + return [{ + 'contents': str(int(round(percentage))), + 'highlight_groups': ['line_percent_gradient', 'line_percent'], + 'gradient_level': percentage, + }] + + +@window_cached +def position(pl, position_strings={'top': 'Top', 'bottom': 'Bot', 'all': 'All'}, gradient=False): + '''Return the position of the current view in the file as a percentage. + + :param dict position_strings: + dict for translation of the position strings, e.g. ``{"top":"Oben", "bottom":"Unten", "all":"Alles"}`` + + :param bool gradient: + highlight the percentage with a color gradient (by default a green to red gradient) + + Highlight groups used: ``position_gradient`` (gradient), ``position``. + ''' + line_last = len(vim.current.buffer) + + winline_first = vim_funcs['line']('w0') + winline_last = vim_funcs['line']('w$') + if winline_first == 1 and winline_last == line_last: + percentage = 0.0 + content = position_strings['all'] + elif winline_first == 1: + percentage = 0.0 + content = position_strings['top'] + elif winline_last == line_last: + percentage = 100.0 + content = position_strings['bottom'] + else: + percentage = winline_first * 100.0 / (line_last - winline_last + winline_first) + content = str(int(round(percentage))) + '%' + + if not gradient: + return content + return [{ + 'contents': content, + 'highlight_groups': ['position_gradient', 'position'], + 'gradient_level': percentage, + }] + + +@requires_segment_info +def line_current(pl, segment_info): + '''Return the current cursor line.''' + return str(segment_info['window'].cursor[0]) + + +@requires_segment_info +def line_count(pl, segment_info): + '''Return the line count of the current buffer.''' + return str(len(segment_info['buffer'])) + + +@requires_segment_info +def col_current(pl, segment_info): + '''Return the current cursor column. + ''' + return str(segment_info['window'].cursor[1] + 1) + + +@window_cached +def virtcol_current(pl, gradient=True): + '''Return current visual column with concealed characters ingored + + :param bool gradient: + Determines whether it should show textwidth-based gradient (gradient level is ``virtcol * 100 / textwidth``). + + Highlight groups used: ``virtcol_current_gradient`` (gradient), ``virtcol_current`` or ``col_current``. + ''' + col = vim_funcs['virtcol']('.') + r = [{'contents': str(col), 'highlight_groups': ['virtcol_current', 'col_current']}] + if gradient: + textwidth = int(getbufvar('%', '&textwidth')) + r[-1]['gradient_level'] = min(col * 100 / textwidth, 100) if textwidth else 0 + r[-1]['highlight_groups'].insert(0, 'virtcol_current_gradient') + return r + + +def modified_buffers(pl, text='+ ', join_str=','): + '''Return a comma-separated list of modified buffers. + + :param str text: + text to display before the modified buffer list + :param str join_str: + string to use for joining the modified buffer list + ''' + buffer_mod_text = join_str.join(( + str(buffer.number) + for buffer in vim.buffers + if int(vim_getbufoption({'buffer': buffer, 'bufnr': buffer.number}, 'modified')) + )) + if buffer_mod_text: + return text + buffer_mod_text + return None + + +@requires_filesystem_watcher +@requires_segment_info +class VimBranchSegment(BranchSegment): + divider_highlight_group = 'branch:divider' + + @staticmethod + def get_directory(segment_info): + if vim_getbufoption(segment_info, 'buftype'): + return None + return buffer_name(segment_info) + + +branch = with_docstring(VimBranchSegment(), +'''Return the current working branch. + +:param bool status_colors: + Determines whether repository status will be used to determine highlighting. + Default: False. +:param bool ignore_statuses: + List of statuses which will not result in repo being marked as dirty. Most + useful is setting this option to ``["U"]``: this will ignore repository + which has just untracked files (i.e. repository with modified, deleted or + removed files will be marked as dirty, while just untracked files will make + segment show clean repository). Only applicable if ``status_colors`` option + is True. + +Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. + +Divider highlight group used: ``branch:divider``. +''') + + +@requires_filesystem_watcher +@requires_segment_info +class VimStashSegment(StashSegment): + divider_highlight_group = 'stash:divider' + + @staticmethod + def get_directory(segment_info): + if vim_getbufoption(segment_info, 'buftype'): + return None + return buffer_name(segment_info) + + +stash = with_docstring(VimStashSegment(), +'''Return the number of stashes in the current working branch. + +Highlight groups used: ``stash``. +''') + + +@requires_filesystem_watcher +@requires_segment_info +def file_vcs_status(pl, segment_info, create_watcher): + '''Return the VCS status for this buffer. + + Highlight groups used: ``file_vcs_status``. + ''' + name = buffer_name(segment_info) + skip = not (name and (not vim_getbufoption(segment_info, 'buftype'))) + if not skip: + repo = guess(path=name, create_watcher=create_watcher) + if repo is not None: + status = repo.status(os.path.relpath(name, repo.directory)) + if not status: + return None + status = status.strip() + ret = [] + for status in status: + ret.append({ + 'contents': status, + 'highlight_groups': ['file_vcs_status_' + status, 'file_vcs_status'], + }) + return ret + + +trailing_whitespace_cache = None + + +@requires_segment_info +def trailing_whitespace(pl, segment_info): + '''Return the line number for trailing whitespaces + + It is advised not to use this segment in insert mode: in Insert mode it will + iterate over all lines in buffer each time you happen to type a character + which may cause lags. It will also show you whitespace warning each time you + happen to type space. + + Highlight groups used: ``trailing_whitespace`` or ``warning``. + ''' + global trailing_whitespace_cache + if trailing_whitespace_cache is None: + trailing_whitespace_cache = register_buffer_cache(defaultdict(lambda: (0, None))) + bufnr = segment_info['bufnr'] + changedtick = getbufvar(bufnr, 'changedtick') + if trailing_whitespace_cache[bufnr][0] == changedtick: + return trailing_whitespace_cache[bufnr][1] + else: + buf = segment_info['buffer'] + bws = b' \t' + sws = str(' \t') # Ignore unicode_literals and use native str. + for i in range(len(buf)): + try: + line = buf[i] + except UnicodeDecodeError: # May happen in Python 3 + if hasattr(vim, 'bindeval'): + line = vim.bindeval('getbufline({0}, {1})'.format( + bufnr, i + 1)) + has_trailing_ws = (line[-1] in bws) + else: + line = vim.eval('strtrans(getbufline({0}, {1}))'.format( + bufnr, i + 1)) + has_trailing_ws = (line[-1] in bws) + else: + has_trailing_ws = (line and line[-1] in sws) + if has_trailing_ws: + break + if has_trailing_ws: + ret = [{ + 'contents': str(i + 1), + 'highlight_groups': ['trailing_whitespace', 'warning'], + }] + else: + ret = None + trailing_whitespace_cache[bufnr] = (changedtick, ret) + return ret + + +@requires_segment_info +def tabnr(pl, segment_info, show_current=True): + '''Show tabpage number + + :param bool show_current: + If False do not show current tabpage number. This is default because + tabnr is by default only present in tabline. + ''' + try: + tabnr = segment_info['tabnr'] + except KeyError: + return None + if show_current or tabnr != current_tabpage().number: + return str(tabnr) + + +@requires_segment_info +def bufnr(pl, segment_info, show_current=True): + '''Show buffer number + + :param bool show_current: + If False do not show current window number. + ''' + bufnr = segment_info['bufnr'] + if show_current or bufnr != vim.current.buffer.number: + return str(bufnr) + + +@requires_segment_info +def winnr(pl, segment_info, show_current=True): + '''Show window number + + :param bool show_current: + If False do not show current window number. + ''' + winnr = segment_info['winnr'] + if show_current or winnr != vim.current.window.number: + return str(winnr) + + +csv_cache = None +sniffer = csv.Sniffer() + + +def detect_text_csv_dialect(text, display_name, header_text=None): + return ( + sniffer.sniff(string(text)), + sniffer.has_header(string(header_text or text)) if display_name == 'auto' else display_name, + ) + + +CSV_SNIFF_LINES = 100 +CSV_PARSE_LINES = 10 + + +if sys.version_info < (2, 7): + def read_csv(l, dialect, fin=next): + try: + return fin(csv.reader(l, dialect)) + except csv.Error as e: + if str(e) == 'newline inside string' and dialect.quotechar: + # Maybe we are inside an unfinished quoted string. Python-2.6 + # does not handle this fine + return fin(csv.reader(l[:-1] + [l[-1] + dialect.quotechar])) + else: + raise +else: + def read_csv(l, dialect, fin=next): + return fin(csv.reader(l, dialect)) + + +def process_csv_buffer(pl, buffer, line, col, display_name): + global csv_cache + if csv_cache is None: + csv_cache = register_buffer_cache(defaultdict(lambda: (None, None, None))) + try: + cur_first_line = buffer[0] + except UnicodeDecodeError: + cur_first_line = vim.eval('strtrans(getline(1))') + dialect, has_header, first_line = csv_cache[buffer.number] + if dialect is None or (cur_first_line != first_line and display_name == 'auto'): + try: + text = '\n'.join(buffer[:CSV_SNIFF_LINES]) + except UnicodeDecodeError: # May happen in Python 3 + text = vim.eval('join(map(getline(1, {0}), "strtrans(v:val)"), "\\n")'.format(CSV_SNIFF_LINES)) + try: + dialect, has_header = detect_text_csv_dialect(text, display_name) + except csv.Error as e: + pl.warn('Failed to detect csv format: {0}', str(e)) + # Try detecting using three lines only: + if line == 1: + rng = (0, line + 2) + elif line == len(buffer): + rng = (line - 3, line) + else: + rng = (line - 2, line + 1) + try: + dialect, has_header = detect_text_csv_dialect( + '\n'.join(buffer[rng[0]:rng[1]]), + display_name, + header_text='\n'.join(buffer[:4]), + ) + except csv.Error as e: + pl.error('Failed to detect csv format: {0}', str(e)) + return None, None + if len(buffer) > 2: + csv_cache[buffer.number] = dialect, has_header, cur_first_line + column_number = len(read_csv( + buffer[max(0, line - CSV_PARSE_LINES):line - 1] + [buffer[line - 1][:col]], + dialect=dialect, + fin=list, + )[-1]) or 1 + if has_header: + try: + header = read_csv(buffer[0:1], dialect=dialect) + except UnicodeDecodeError: + header = read_csv([vim.eval('strtrans(getline(1))')], dialect=dialect) + column_name = header[column_number - 1] + else: + column_name = None + return unicode(column_number), column_name + + +@requires_segment_info +def csv_col_current(pl, segment_info, display_name='auto', name_format=' ({column_name:.15})'): + '''Display CSV column number and column name + + Requires filetype to be set to ``csv``. + + :param bool or str name: + May be ``True``, ``False`` and ``"auto"``. In the first case value from + the first raw will always be displayed. In the second case it will never + be displayed. In thi last case ``csv.Sniffer().has_header()`` will be + used to detect whether current file contains header in the first column. + :param str name_format: + String used to format column name (in case ``display_name`` is set to + ``True`` or ``"auto"``). Accepts ``column_name`` keyword argument. + + Highlight groups used: ``csv:column_number`` or ``csv``, ``csv:column_name`` or ``csv``. + ''' + if vim_getbufoption(segment_info, 'filetype') != 'csv': + return None + line, col = segment_info['window'].cursor + column_number, column_name = process_csv_buffer(pl, segment_info['buffer'], line, col, display_name) + if not column_number: + return None + return [{ + 'contents': column_number, + 'highlight_groups': ['csv:column_number', 'csv'], + }] + ([{ + 'contents': name_format.format(column_name=column_name), + 'highlight_groups': ['csv:column_name', 'csv'], + }] if column_name else []) + + +@requires_segment_info +def tab(pl, segment_info, end=False): + '''Mark start of the clickable region for tabpage + + :param bool end: + In place of starting region for the current tab end it. + + No highlight groups are used (literal segment). + ''' + try: + return [{ + 'contents': None, + 'literal_contents': (0, '%{tabnr}T'.format(tabnr=('' if end else segment_info['tabnr']))), + }] + except KeyError: + return None diff --git a/powerline/segments/vim/plugin/__init__.py b/powerline/segments/vim/plugin/__init__.py new file mode 100644 index 0000000..b2b9f10 --- /dev/null +++ b/powerline/segments/vim/plugin/__init__.py @@ -0,0 +1,6 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) +from pkgutil import extend_path + + +__path__ = extend_path(__path__, __name__) diff --git a/powerline/segments/vim/plugin/ale.py b/powerline/segments/vim/plugin/ale.py new file mode 100644 index 0000000..4f4bdee --- /dev/null +++ b/powerline/segments/vim/plugin/ale.py @@ -0,0 +1,52 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + import vim +except ImportError: + vim = object() + +from powerline.bindings.vim import vim_global_exists +from powerline.theme import requires_segment_info + + +@requires_segment_info +def ale(segment_info, pl, err_format='ERR: ln {first_line} ({num}) ', warn_format='WARN: ln {first_line} ({num}) '): + '''Show whether ALE has found any errors or warnings + + :param str err_format: + Format string for errors. + + :param str warn_format: + Format string for warnings. + + Highlight groups used: ``ale:warning`` or ``warning``, ``ale:error`` or ``error``. + ''' + if not (vim_global_exists('ale_enabled') and int(vim.eval('g:ale_enabled'))): + return None + has_errors = int(vim.eval('ale#statusline#Count(' + str(segment_info['bufnr']) + ').total')) + if not has_errors: + return + error = None + warning = None + errors_count = 0 + warnings_count = 0 + for issue in vim.eval('ale#engine#GetLoclist(' + str(segment_info['bufnr']) + ')'): + if issue['type'] == 'E': + error = error or issue + errors_count += 1 + elif issue['type'] == 'W': + warning = warning or issue + warnings_count += 1 + segments = [] + if error: + segments.append({ + 'contents': err_format.format(first_line=error['lnum'], num=errors_count), + 'highlight_groups': ['ale:error', 'error'], + }) + if warning: + segments.append({ + 'contents': warn_format.format(first_line=warning['lnum'], num=warnings_count), + 'highlight_groups': ['ale:warning', 'warning'], + }) + return segments diff --git a/powerline/segments/vim/plugin/capslock.py b/powerline/segments/vim/plugin/capslock.py new file mode 100644 index 0000000..d2c474d --- /dev/null +++ b/powerline/segments/vim/plugin/capslock.py @@ -0,0 +1,30 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + import vim +except ImportError: + vim = object() + +from powerline.bindings.vim import vim_func_exists +from powerline.theme import requires_segment_info + + +@requires_segment_info +def capslock_indicator(pl, segment_info, text='CAPS'): + '''Shows the indicator if tpope/vim-capslock plugin is enabled + + .. note:: + In the current state plugin automatically disables itself when leaving + insert mode. So trying to use this segment not in insert or replace + modes is useless. + + :param str text: + String to show when software capslock presented by this plugin is + active. + ''' + if not vim_func_exists('CapsLockStatusline'): + return None + # CapsLockStatusline() function returns an empty string when plugin is + # disabled. If it is not then string is non-empty. + return text if vim.eval('CapsLockStatusline()') else None diff --git a/powerline/segments/vim/plugin/commandt.py b/powerline/segments/vim/plugin/commandt.py new file mode 100644 index 0000000..7e5262e --- /dev/null +++ b/powerline/segments/vim/plugin/commandt.py @@ -0,0 +1,97 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + import vim +except ImportError: + vim = object() + +from powerline.bindings.vim import create_ruby_dpowerline + + +def initialize(): + global initialized + if initialized: + return + initialized = True + create_ruby_dpowerline() + vim.command(( + # When using :execute (vim.command uses the same code) one should not + # use << EOF. + ''' + ruby + if (not ($command_t.respond_to? 'active_finder')) + def $command_t.active_finder + @active_finder and @active_finder.class.name or '' + end + end + if (not ($command_t.respond_to? 'path')) + def $command_t.path + @path or '' + end + end + def $powerline.commandt_set_active_finder + ::VIM::command "let g:powerline_commandt_reply = '#{$command_t.active_finder}'" + end + def $powerline.commandt_set_path + ::VIM::command "let g:powerline_commandt_reply = '#{($command_t.path or '').gsub(/'/, "''")}'" + end + ''' + )) + + +initialized = False + + +def finder(pl): + '''Display Command-T finder name + + Requires $command_t.active_finder and methods (code above may monkey-patch + $command_t to add them). All Command-T finders have ``CommandT::`` module + prefix, but it is stripped out (actually, any ``CommandT::`` substring will + be stripped out). + + Highlight groups used: ``commandt:finder``. + ''' + initialize() + vim.command('ruby $powerline.commandt_set_active_finder') + return [{ + 'highlight_groups': ['commandt:finder'], + 'contents': vim.eval('g:powerline_commandt_reply').replace('CommandT::', '').replace('Finder::', '') + }] + + +FINDERS_WITHOUT_PATH = set(( + 'CommandT::MRUBufferFinder', + 'CommandT::BufferFinder', + 'CommandT::TagFinder', + 'CommandT::JumpFinder', + 'CommandT::Finder::MRUBufferFinder', + 'CommandT::Finder::BufferFinder', + 'CommandT::Finder::TagFinder', + 'CommandT::Finder::JumpFinder', +)) + + +def path(pl): + '''Display path used by Command-T + + Requires $command_t.active_finder and .path methods (code above may + monkey-patch $command_t to add them). + + $command_t.active_finder is required in order to omit displaying path for + finders ``MRUBufferFinder``, ``BufferFinder``, ``TagFinder`` and + ``JumpFinder`` (pretty much any finder, except ``FileFinder``). + + Highlight groups used: ``commandt:path``. + ''' + initialize() + vim.command('ruby $powerline.commandt_set_active_finder') + finder = vim.eval('g:powerline_commandt_reply') + if finder in FINDERS_WITHOUT_PATH: + return None + vim.command('ruby $powerline.commandt_set_path') + return [{ + 'highlight_groups': ['commandt:path'], + 'contents': vim.eval('g:powerline_commandt_reply') + }] diff --git a/powerline/segments/vim/plugin/nerdtree.py b/powerline/segments/vim/plugin/nerdtree.py new file mode 100644 index 0000000..f11be14 --- /dev/null +++ b/powerline/segments/vim/plugin/nerdtree.py @@ -0,0 +1,25 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + import vim +except ImportError: + vim = object() + +from powerline.bindings.vim import bufvar_exists +from powerline.segments.vim import window_cached + + +@window_cached +def nerdtree(pl): + '''Return directory that is shown by the current buffer. + + Highlight groups used: ``nerdtree:path`` or ``file_name``. + ''' + if not bufvar_exists(None, 'NERDTreeRoot'): + return None + path_str = vim.eval('getbufvar("%", "NERDTreeRoot").path.str()') + return [{ + 'contents': path_str, + 'highlight_groups': ['nerdtree:path', 'file_name'], + }] diff --git a/powerline/segments/vim/plugin/syntastic.py b/powerline/segments/vim/plugin/syntastic.py new file mode 100644 index 0000000..5bef3c7 --- /dev/null +++ b/powerline/segments/vim/plugin/syntastic.py @@ -0,0 +1,43 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + import vim +except ImportError: + vim = object() + +from powerline.segments.vim import window_cached +from powerline.bindings.vim import vim_global_exists + + +@window_cached +def syntastic(pl, err_format='ERR: {first_line} ({num}) ', warn_format='WARN: {first_line} ({num}) '): + '''Show whether syntastic has found any errors or warnings + + :param str err_format: + Format string for errors. + + :param str warn_format: + Format string for warnings. + + Highlight groups used: ``syntastic:warning`` or ``warning``, ``syntastic:error`` or ``error``. + ''' + if not vim_global_exists('SyntasticLoclist'): + return None + has_errors = int(vim.eval('g:SyntasticLoclist.current().hasErrorsOrWarningsToDisplay()')) + if not has_errors: + return + errors = vim.eval('g:SyntasticLoclist.current().errors()') + warnings = vim.eval('g:SyntasticLoclist.current().warnings()') + segments = [] + if errors: + segments.append({ + 'contents': err_format.format(first_line=errors[0]['lnum'], num=len(errors)), + 'highlight_groups': ['syntastic:error', 'error'], + }) + if warnings: + segments.append({ + 'contents': warn_format.format(first_line=warnings[0]['lnum'], num=len(warnings)), + 'highlight_groups': ['syntastic:warning', 'warning'], + }) + return segments diff --git a/powerline/segments/vim/plugin/tagbar.py b/powerline/segments/vim/plugin/tagbar.py new file mode 100644 index 0000000..e683758 --- /dev/null +++ b/powerline/segments/vim/plugin/tagbar.py @@ -0,0 +1,51 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + import vim +except ImportError: + vim = object() + +from powerline.bindings.vim import vim_command_exists, vim_get_autoload_func +from powerline.theme import requires_segment_info + + +currenttag = None +tag_cache = {} + + +@requires_segment_info +def current_tag(segment_info, 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 + ''' + global currenttag + global tag_cache + window_id = segment_info['window_id'] + if segment_info['mode'] == 'nc': + return tag_cache.get(window_id, (None,))[-1] + if not currenttag: + if vim_command_exists('Tagbar'): + currenttag = vim_get_autoload_func('tagbar#currenttag') + if not currenttag: + return None + else: + return None + prev_key, r = tag_cache.get(window_id, (None, None)) + key = (int(vim.eval('b:changedtick')), segment_info['window'].cursor[0]) + if prev_key and key == prev_key: + return r + r = currenttag('%s', '', flags) + tag_cache[window_id] = (key, r) + return r diff --git a/powerline/selectors/__init__.py b/powerline/selectors/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/powerline/selectors/__init__.py diff --git a/powerline/selectors/vim.py b/powerline/selectors/vim.py new file mode 100644 index 0000000..d111de9 --- /dev/null +++ b/powerline/selectors/vim.py @@ -0,0 +1,10 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.bindings.vim import list_tabpages + + +def single_tab(pl, segment_info, mode): + '''Returns True if Vim has only one tab opened + ''' + return len(list_tabpages()) == 1 diff --git a/powerline/shell.py b/powerline/shell.py new file mode 100644 index 0000000..e10692c --- /dev/null +++ b/powerline/shell.py @@ -0,0 +1,38 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline import Powerline +from powerline.lib.dict import mergedicts + + +class ShellPowerline(Powerline): + def init(self, args, **kwargs): + self.args = args + super(ShellPowerline, self).init(args.ext[0], args.renderer_module, **kwargs) + + def load_main_config(self): + r = super(ShellPowerline, self).load_main_config() + if self.args.config_override: + mergedicts(r, self.args.config_override) + return r + + def load_theme_config(self, name): + r = super(ShellPowerline, self).load_theme_config(name) + if self.args.theme_override and name in self.args.theme_override: + mergedicts(r, self.args.theme_override[name]) + return r + + def get_config_paths(self): + return self.args.config_path or super(ShellPowerline, self).get_config_paths() + + def get_local_themes(self, local_themes): + if not local_themes: + return {} + + return dict(( + (key, {'config': self.load_theme_config(val)}) + for key, val in local_themes.items() + )) + + def do_setup(self, obj): + obj.powerline = self diff --git a/powerline/theme.py b/powerline/theme.py new file mode 100644 index 0000000..b3a23a1 --- /dev/null +++ b/powerline/theme.py @@ -0,0 +1,182 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import itertools + +from powerline.segment import gen_segment_getter, process_segment, get_fallback_segment +from powerline.lib.unicode import u, safe_unicode + + +def requires_segment_info(func): + func.powerline_requires_segment_info = True + return func + + +def requires_filesystem_watcher(func): + func.powerline_requires_filesystem_watcher = True + return func + + +def new_empty_segment_line(): + return { + 'left': [], + 'right': [] + } + + +def add_spaces_left(pl, amount, segment): + return (' ' * amount) + segment['contents'] + + +def add_spaces_right(pl, amount, segment): + return segment['contents'] + (' ' * amount) + + +def add_spaces_center(pl, amount, segment): + amount, remainder = divmod(amount, 2) + return (' ' * (amount + remainder)) + segment['contents'] + (' ' * amount) + + +expand_functions = { + 'l': add_spaces_right, + 'r': add_spaces_left, + 'c': add_spaces_center, +} + + +class Theme(object): + def __init__(self, + ext, + theme_config, + common_config, + pl, + get_module_attr, + top_theme, + colorscheme, + main_theme_config=None, + run_once=False, + shutdown_event=None): + self.colorscheme = colorscheme + self.dividers = theme_config['dividers'] + self.dividers = dict(( + (key, dict((k, u(v)) + for k, v in val.items())) + for key, val in self.dividers.items() + )) + try: + self.cursor_space_multiplier = 1 - (theme_config['cursor_space'] / 100) + except KeyError: + self.cursor_space_multiplier = None + self.cursor_columns = theme_config.get('cursor_columns') + self.spaces = theme_config['spaces'] + self.outer_padding = int(theme_config.get('outer_padding', 1)) + self.segments = [] + self.EMPTY_SEGMENT = { + 'contents': None, + 'highlight': {'fg': False, 'bg': False, 'attrs': 0} + } + self.pl = pl + theme_configs = [theme_config] + if main_theme_config: + theme_configs.append(main_theme_config) + get_segment = gen_segment_getter( + pl, + ext, + common_config, + theme_configs, + theme_config.get('default_module'), + get_module_attr, + top_theme + ) + for segdict in itertools.chain((theme_config['segments'],), + theme_config['segments'].get('above', ())): + self.segments.append(new_empty_segment_line()) + for side in ['left', 'right']: + for segment in segdict.get(side, []): + segment = get_segment(segment, side) + if segment: + if not run_once: + if segment['startup']: + try: + segment['startup'](pl, shutdown_event) + except Exception as e: + pl.error('Exception during {0} startup: {1}', segment['name'], str(e)) + continue + self.segments[-1][side].append(segment) + + def shutdown(self): + for line in self.segments: + for segments in line.values(): + for segment in segments: + try: + segment['shutdown']() + except TypeError: + pass + + def get_divider(self, side='left', type='soft'): + '''Return segment divider.''' + return self.dividers[side][type] + + def get_spaces(self): + return self.spaces + + def get_line_number(self): + return len(self.segments) + + def get_segments(self, side=None, line=0, segment_info=None, mode=None): + '''Return all segments. + + Function segments are called, and all segments get their before/after + and ljust/rjust properties applied. + + :param int line: + Line number for which segments should be obtained. Is counted from + zero (botmost line). + ''' + for side in [side] if side else ['left', 'right']: + parsed_segments = [] + for segment in self.segments[line][side]: + if segment['display_condition'](self.pl, segment_info, mode): + process_segment( + self.pl, + side, + segment_info, + parsed_segments, + segment, + mode, + self.colorscheme, + ) + for segment in parsed_segments: + self.pl.prefix = segment['name'] + try: + width = segment['width'] + align = segment['align'] + if width == 'auto' and segment['expand'] is None: + segment['expand'] = expand_functions.get(align) + if segment['expand'] is None: + self.pl.error('Align argument must be “r”, “l” or “c”, not “{0}”', align) + + try: + segment['contents'] = segment['before'] + u( + segment['contents'] if segment['contents'] is not None else '' + ) + segment['after'] + except Exception as e: + self.pl.exception('Failed to compute segment contents: {0}', str(e)) + segment['contents'] = safe_unicode(segment.get('contents')) + # Align segment contents + if segment['width'] and segment['width'] != 'auto': + if segment['align'] == 'l': + segment['contents'] = segment['contents'].ljust(segment['width']) + elif segment['align'] == 'r': + segment['contents'] = segment['contents'].rjust(segment['width']) + elif segment['align'] == 'c': + segment['contents'] = segment['contents'].center(segment['width']) + # We need to yield a copy of the segment, or else mode-dependent + # segment contents can’t be cached correctly e.g. when caching + # non-current window contents for vim statuslines + yield segment.copy() + except Exception as e: + self.pl.exception('Failed to compute segment: {0}', str(e)) + fallback = get_fallback_segment() + fallback.update(side=side) + yield fallback diff --git a/powerline/vim.py b/powerline/vim.py new file mode 100644 index 0000000..603a6a5 --- /dev/null +++ b/powerline/vim.py @@ -0,0 +1,347 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import json +import logging + +from itertools import count + +try: + import vim +except ImportError: + vim = object() + +from powerline.bindings.vim import vim_get_func, vim_getvar, get_vim_encoding, python_to_vim +from powerline import Powerline, FailedUnicode, finish_common_config +from powerline.lib.dict import mergedicts +from powerline.lib.unicode import u + + +def _override_from(config, override_varname, key=None): + try: + overrides = vim_getvar(override_varname) + except KeyError: + return config + if key is not None: + try: + overrides = overrides[key] + except KeyError: + return config + mergedicts(config, overrides) + return config + + +class VimVarHandler(logging.Handler, object): + '''Vim-specific handler which emits messages to Vim global variables + + :param str varname: + Variable where + ''' + def __init__(self, varname): + super(VimVarHandler, self).__init__() + utf_varname = u(varname) + self.vim_varname = utf_varname.encode('ascii') + vim.command('unlet! g:' + utf_varname) + vim.command('let g:' + utf_varname + ' = []') + + def emit(self, record): + message = u(record.message) + if record.exc_text: + message += '\n' + u(record.exc_text) + vim.eval(b'add(g:' + self.vim_varname + b', ' + python_to_vim(message) + b')') + + +class VimPowerline(Powerline): + def init(self, pyeval='PowerlinePyeval', **kwargs): + super(VimPowerline, self).init('vim', **kwargs) + self.last_window_id = 1 + self.pyeval = pyeval + self.construct_window_statusline = self.create_window_statusline_constructor() + if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))): + self.win_idx = self.new_win_idx + else: + self.win_idx = self.old_win_idx + self._vim_getwinvar = vim_get_func('getwinvar', 'bytes') + self._vim_setwinvar = vim_get_func('setwinvar') + + if sys.version_info < (3,): + def create_window_statusline_constructor(self): + window_statusline = b'%!' + str(self.pyeval) + b'(\'powerline.statusline({0})\')' + return window_statusline.format + else: + def create_window_statusline_constructor(self): + startstr = b'%!' + self.pyeval.encode('ascii') + b'(\'powerline.statusline(' + endstr = b')\')' + return lambda idx: ( + startstr + str(idx).encode('ascii') + endstr + ) + + create_window_statusline_constructor.__doc__ = ( + '''Create function which returns &l:stl value being given window index + + Created function must return :py:class:`bytes` instance because this is + what ``window.options['statusline']`` returns (``window`` is + :py:class:`vim.Window` instance). + + :return: + Function with type ``int → bytes``. + ''' + ) + + default_log_stream = sys.stdout + + def add_local_theme(self, key, config): + '''Add local themes at runtime (during vim session). + + :param str key: + Matcher name (in format ``{matcher_module}.{module_attribute}`` or + ``{module_attribute}`` if ``{matcher_module}`` is + ``powerline.matchers.vim``). Function pointed by + ``{module_attribute}`` should be hashable and accept a dictionary + with information about current buffer and return boolean value + indicating whether current window matched conditions. See also + :ref:`local_themes key description <config-ext-local_themes>`. + + :param dict config: + :ref:`Theme <config-themes>` dictionary. + + :return: + ``True`` if theme was added successfully and ``False`` if theme with + the same matcher already exists. + ''' + self.update_renderer() + matcher = self.get_matcher(key) + theme_config = {} + for cfg_path in self.theme_levels: + try: + lvl_config = self.load_config(cfg_path, 'theme') + except IOError: + pass + else: + mergedicts(theme_config, lvl_config) + mergedicts(theme_config, config) + try: + self.renderer.add_local_theme(matcher, {'config': theme_config}) + except KeyError: + return False + else: + # Hack for local themes support: when reloading modules it is not + # guaranteed that .add_local_theme will be called once again, so + # this function arguments will be saved here for calling from + # .do_setup(). + self.setup_kwargs.setdefault('_local_themes', []).append((key, config)) + return True + + get_encoding = staticmethod(get_vim_encoding) + + def load_main_config(self): + main_config = _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides') + try: + use_var_handler = bool(int(vim_getvar('powerline_use_var_handler'))) + except KeyError: + use_var_handler = False + if use_var_handler: + main_config.setdefault('common', {}) + main_config['common'] = finish_common_config(self.get_encoding(), main_config['common']) + main_config['common']['log_file'].append(['powerline.vim.VimVarHandler', [['powerline_log_messages']]]) + return main_config + + def load_theme_config(self, name): + return _override_from( + super(VimPowerline, self).load_theme_config(name), + 'powerline_theme_overrides', + name + ) + + def get_local_themes(self, local_themes): + if not local_themes: + return {} + + return dict(( + (matcher, {'config': self.load_theme_config(val)}) + for matcher, key, val in ( + ( + (None if k == '__tabline__' else self.get_matcher(k)), + k, + v + ) + for k, v in local_themes.items() + ) if ( + matcher or + key == '__tabline__' + ) + )) + + def get_matcher(self, match_name): + match_module, separator, match_function = match_name.rpartition('.') + if not separator: + match_module = 'powerline.matchers.{0}'.format(self.ext) + match_function = match_name + return self.get_module_attr(match_module, match_function, prefix='matcher_generator') + + def get_config_paths(self): + try: + return vim_getvar('powerline_config_paths') + except KeyError: + return super(VimPowerline, self).get_config_paths() + + def do_setup(self, pyeval=None, pycmd=None, can_replace_pyeval=True, _local_themes=()): + import __main__ + if not pyeval: + pyeval = 'pyeval' if sys.version_info < (3,) else 'py3eval' + can_replace_pyeval = True + if not pycmd: + pycmd = get_default_pycmd() + + set_pycmd(pycmd) + + # pyeval() and vim.bindeval were both introduced in one patch + if (not hasattr(vim, 'bindeval') and can_replace_pyeval) or pyeval == 'PowerlinePyeval': + vim.command((''' + function! PowerlinePyeval(e) + {pycmd} powerline.do_pyeval() + endfunction + ''').format(pycmd=pycmd)) + pyeval = 'PowerlinePyeval' + + self.pyeval = pyeval + self.construct_window_statusline = self.create_window_statusline_constructor() + + self.update_renderer() + __main__.powerline = self + + try: + if ( + bool(int(vim.eval('has(\'gui_running\') && argc() == 0'))) + 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 value purges startup screen. + # - Defining highlight group while computing statusline purges + # startup screen. + # 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 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 + # context newline is considered part of the command in just the same cases + # when bar is considered part of the command (unless defining function + # inside :execute)). vim.command is :execute equivalent regarding this case. + vim.command('augroup Powerline') + vim.command(' autocmd! ColorScheme * :{pycmd} powerline.reset_highlight()'.format(pycmd=pycmd)) + vim.command(' autocmd! VimLeavePre * :{pycmd} powerline.shutdown()'.format(pycmd=pycmd)) + vim.command('augroup END') + + # Hack for local themes support after reloading. + for args in _local_themes: + self.add_local_theme(*args) + + def reset_highlight(self): + try: + self.renderer.reset_highlight() + except AttributeError: + # Renderer object appears only after first `.render()` call. Thus if + # ColorScheme event happens before statusline is drawn for the first + # time AttributeError will be thrown for the self.renderer. It is + # fine to ignore it: no renderer == no colors to reset == no need to + # do anything. + pass + + def new_win_idx(self, window_id): + r = None + for window in vim.windows: + try: + curwindow_id = window.vars['powerline_window_id'] + if r is not None and curwindow_id == window_id: + raise KeyError + except KeyError: + curwindow_id = self.last_window_id + self.last_window_id += 1 + window.vars['powerline_window_id'] = curwindow_id + statusline = self.construct_window_statusline(curwindow_id) + if window.options['statusline'] != statusline: + window.options['statusline'] = statusline + if curwindow_id == window_id if window_id else window is vim.current.window: + r = (window, curwindow_id, window.number) + return r + + def old_win_idx(self, window_id): + r = None + for winnr, window in zip(count(1), vim.windows): + curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id') + if curwindow_id and not (r is not None and curwindow_id == window_id): + curwindow_id = int(curwindow_id) + else: + curwindow_id = self.last_window_id + self.last_window_id += 1 + self._vim_setwinvar(winnr, 'powerline_window_id', curwindow_id) + statusline = self.construct_window_statusline(curwindow_id) + if self._vim_getwinvar(winnr, '&statusline') != statusline: + self._vim_setwinvar(winnr, '&statusline', statusline) + if curwindow_id == window_id if window_id else window is vim.current.window: + r = (window, curwindow_id, winnr) + return r + + def statusline(self, window_id): + window, window_id, winnr = self.win_idx(window_id) or (None, None, None) + if not window: + return FailedUnicode('No window {0}'.format(window_id)) + return self.render(window, window_id, winnr) + + def tabline(self): + return self.render(*self.win_idx(None), is_tabline=True) + + def new_window(self): + return self.render(*self.win_idx(None)) + + @staticmethod + def do_pyeval(): + '''Evaluate python string passed to PowerlinePyeval + + Is here to reduce the number of requirements to __main__ globals to just + 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__))) + + def setup_components(self, components): + if components is None: + components = ('statusline', 'tabline') + if 'statusline' in components: + # Is immediately changed after new_window function is run. Good for + # global value. + vim.command('set statusline=%!{pyeval}(\'powerline.new_window()\')'.format( + pyeval=self.pyeval)) + if 'tabline' in components: + vim.command('set tabline=%!{pyeval}(\'powerline.tabline()\')'.format( + pyeval=self.pyeval)) + + +pycmd = None + + +def set_pycmd(new_pycmd): + global pycmd + pycmd = new_pycmd + + +def get_default_pycmd(): + return 'python' if sys.version_info < (3,) else 'python3' + + +def setup(*args, **kwargs): + powerline = VimPowerline() + return powerline.setup(*args, **kwargs) diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..f2ffc12 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +powerline diff --git a/scripts/powerline-config b/scripts/powerline-config new file mode 100755 index 0000000..1fb2029 --- /dev/null +++ b/scripts/powerline-config @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +try: + from powerline.commands.config import get_argparser +except ImportError: + import sys + import os + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))) + from powerline.commands.config import get_argparser + +import powerline.bindings.config as config + + +if __name__ == '__main__': + parser = get_argparser() + args = parser.parse_args() + + pl = config.create_powerline_logger(args) + + args.function(pl, args) diff --git a/scripts/powerline-daemon b/scripts/powerline-daemon new file mode 100755 index 0000000..f15ac69 --- /dev/null +++ b/scripts/powerline-daemon @@ -0,0 +1,495 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import socket +import os +import errno +import sys +import fcntl +import atexit +import stat + +from argparse import ArgumentParser +from select import select +from signal import signal, SIGTERM +from time import sleep +from functools import partial +from io import BytesIO +from threading import Event +from itertools import chain +from logging import StreamHandler + +from powerline.shell import ShellPowerline +from powerline.commands.main import finish_args, write_output +from powerline.lib.monotonic import monotonic +from powerline.lib.encoding import get_preferred_output_encoding, get_preferred_arguments_encoding, get_unicode_writer +from powerline.bindings.wm import wm_threads + +from powerline.commands.main import get_argparser as get_main_argparser +from powerline.commands.daemon import get_argparser as get_daemon_argparser + + +USE_FILESYSTEM = not sys.platform.lower().startswith('linux') + + +class NonInteractiveArgParser(ArgumentParser): + def print_usage(self, file=None): + raise Exception(self.format_usage()) + + def print_help(self, file=None): + raise Exception(self.format_help()) + + def exit(self, status=0, message=None): + pass + + def error(self, message): + raise Exception(self.format_usage()) + + +EOF = b'EOF\0\0' + + +class State(object): + __slots__ = ('powerlines', 'logger', 'config_loader', 'started_wm_threads', + 'ts_shutdown_event') + + def __init__(self, **kwargs): + self.logger = None + self.config_loader = None + self.started_wm_threads = {} + self.powerlines = {} + self.ts_shutdown_event = Event() + + +HOME = os.path.expanduser('~') + + +class NonDaemonShellPowerline(ShellPowerline): + def get_log_handler(self): + return StreamHandler() + + +def start_wm(args, environ, cwd, is_daemon, state): + wm_name = args.ext[0][3:] + if wm_name in state.started_wm_threads: + return b'' + thread_shutdown_event = Event() + thread = wm_threads[wm_name]( + thread_shutdown_event=thread_shutdown_event, + pl_shutdown_event=state.ts_shutdown_event, + pl_config_loader=state.config_loader, + ) + thread.start() + state.started_wm_threads[wm_name] = (thread, thread_shutdown_event) + return b'' + + +def render(args, environ, cwd, is_daemon, state): + segment_info = { + 'getcwd': lambda: cwd, + 'home': environ.get('HOME', HOME), + 'environ': environ, + 'args': args, + } + key = ( + args.ext[0], + args.renderer_module, + tuple(args.config_override) if args.config_override else None, + tuple(args.theme_override) if args.theme_override else None, + tuple(args.config_path) if args.config_path else None, + environ.get('POWERLINE_THEME_OVERRIDES', ''), + environ.get('POWERLINE_CONFIG_OVERRIDES', ''), + environ.get('POWERLINE_CONFIG_PATHS', ''), + ) + + PowerlineClass = ShellPowerline if is_daemon else NonDaemonShellPowerline + powerline = None + try: + powerline = state.powerlines[key] + except KeyError: + try: + powerline = state.powerlines[key] = PowerlineClass( + args, + logger=state.logger, + config_loader=state.config_loader, + run_once=False, + shutdown_event=state.ts_shutdown_event, + ) + if state.logger is None: + state.logger = powerline.logger + if state.config_loader is None: + state.config_loader = powerline.config_loader + except SystemExit: + # Somebody thought raising system exit was a good idea, + return '' + except Exception as e: + if powerline: + powerline.pl.exception('Failed to render {0}: {1}', str(key), str(e)) + else: + return 'Failed to render {0}: {1}'.format(str(key), str(e)) + s = BytesIO() + write_output(args, powerline, segment_info, get_unicode_writer(stream=s)) + s.seek(0) + return s.read() + + +def eintr_retry_call(func, *args, **kwargs): + while True: + try: + return func(*args, **kwargs) + except EnvironmentError as e: + if getattr(e, 'errno', None) == errno.EINTR: + continue + raise + + +def do_read(conn, timeout=2.0): + ''' Read data from the client. If the client fails to send data within + timeout seconds, abort. ''' + read = [] + end_time = monotonic() + timeout + while not read or not read[-1].endswith(b'\0\0'): + r, w, e = select((conn,), (), (conn,), timeout) + if e: + return + if monotonic() > end_time: + return + if not r: + continue + x = eintr_retry_call(conn.recv, 4096) + if x: + read.append(x) + else: + break + return b''.join(read) + + +def do_write(conn, result): + try: + eintr_retry_call(conn.sendall, result) + except Exception: + pass + + +def safe_bytes(o, encoding=get_preferred_output_encoding()): + '''Return bytes instance without ever throwing an exception.''' + try: + try: + # We are assuming that o is a unicode object + return o.encode(encoding, 'replace') + except Exception: + # Object may have defined __bytes__ (python 3) or __str__ method + # (python 2) + # This also catches problem with non_ascii_bytes.encode('utf-8') + # that first tries to decode to UTF-8 using ascii codec (and fails + # in this case) and then encode to given encoding: errors= argument + # is not used in the first stage. + return bytes(o) + except Exception as e: + return safe_bytes(str(e), encoding) + + +def parse_args(req, parser, encoding=get_preferred_arguments_encoding()): + args = [x.decode(encoding) for x in req.split(b'\0') if x] + numargs = int(args[0], 16) + shell_args = parser.parse_args(args[1:numargs + 1]) + cwd = args[numargs + 1] + environ = dict(((k, v) for k, v in (x.partition('=')[0::2] for x in args[numargs + 2:]))) + cwd = cwd or environ.get('PWD', '/') + return shell_args, environ, cwd + + +def get_answer(req, is_daemon, argparser, state): + try: + args, environ, cwd = parse_args(req, argparser) + finish_args(argparser, environ, args, is_daemon=True) + if args.ext[0].startswith('wm.'): + return safe_bytes(start_wm(args, environ, cwd, is_daemon, state)) + else: + return safe_bytes(render(args, environ, cwd, is_daemon, state)) + except Exception as e: + return safe_bytes(str(e)) + + +def do_one(sock, read_sockets, write_sockets, result_map, is_daemon, argparser, + state): + r, w, e = select( + tuple(read_sockets) + (sock,), + tuple(write_sockets), + tuple(read_sockets) + tuple(write_sockets) + (sock,), + 60.0 + ) + + if sock in e: + # We cannot accept any more connections, so we exit + raise SystemExit(1) + + for s in e: + # Discard all broken connections to clients + s.close() + read_sockets.discard(s) + write_sockets.discard(s) + + for s in r: + if s == sock: + # A client wants to connect + conn, _ = eintr_retry_call(sock.accept) + read_sockets.add(conn) + else: + # A client has sent some data + read_sockets.discard(s) + req = do_read(s) + if req == EOF: + raise SystemExit(0) + elif req: + ans = get_answer(req, is_daemon, argparser, state) + result_map[s] = ans + write_sockets.add(s) + else: + s.close() + + for s in w: + # A client is ready to receive the result + write_sockets.discard(s) + result = result_map.pop(s) + try: + do_write(s, result) + finally: + s.close() + + +def shutdown(sock, read_sockets, write_sockets, state): + '''Perform operations necessary for nicely shutting down daemon + + Specifically it + + #. Closes all sockets. + #. Notifies segments based on + :py:class:`powerline.lib.threaded.ThreadedSegment` and WM-specific + threads that daemon is shutting down. + #. Waits for threads to finish, but no more then 2 seconds total. + #. Waits so that total execution time of this function is 2 seconds in order + to allow ThreadedSegments to finish. + ''' + total_wait_time = 2 + shutdown_start_time = monotonic() + + for s in chain((sock,), read_sockets, write_sockets): + s.close() + + # Notify ThreadedSegments + state.ts_shutdown_event.set() + for thread, shutdown_event in state.started_wm_threads.values(): + shutdown_event.set() + + for thread, shutdown_event in state.started_wm_threads.values(): + wait_time = total_wait_time - (monotonic() - shutdown_start_time) + if wait_time > 0: + thread.join(wait_time) + + wait_time = total_wait_time - (monotonic() - shutdown_start_time) + sleep(wait_time) + + +def main_loop(sock, is_daemon): + sock.listen(128) + sock.setblocking(0) + + read_sockets, write_sockets = set(), set() + result_map = {} + parser = get_main_argparser(NonInteractiveArgParser) + state = State() + try: + try: + while True: + do_one( + sock, read_sockets, write_sockets, result_map, + is_daemon=is_daemon, + argparser=parser, + state=state, + ) + except KeyboardInterrupt: + raise SystemExit(0) + except SystemExit as e: + shutdown(sock, read_sockets, write_sockets, state) + raise e + return 0 + + +def daemonize(stdin=os.devnull, stdout=os.devnull, stderr=os.devnull): + try: + pid = os.fork() + if pid > 0: + # exit first parent + raise SystemExit(0) + except OSError as e: + sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) + raise SystemExit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + raise SystemExit(0) + except OSError as e: + sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) + raise SystemExit(1) + + # Redirect standard file descriptors. + si = open(stdin, 'rb') + so = open(stdout, 'a+b') + se = open(stderr, 'a+b', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + return True + + +def check_existing(address): + if USE_FILESYSTEM: + # We cannot bind if the socket file already exists so remove it, we + # already have a lock on pidfile, so this should be safe. + try: + os.unlink(address) + except EnvironmentError: + pass + + sock = socket.socket(family=socket.AF_UNIX) + try: + sock.bind(address) + except socket.error as e: + if getattr(e, 'errno', None) == errno.EADDRINUSE: + return None + raise + return sock + + +def kill_daemon(address): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + try: + eintr_retry_call(sock.connect, address) + except socket.error: + return False + else: + eintr_retry_call(sock.sendall, EOF) + finally: + sock.close() + return True + + +def cleanup_lockfile(pidfile, fd, *args): + try: + # Remove the directory entry for the lock file + os.unlink(pidfile) + # Close the file descriptor + os.close(fd) + except EnvironmentError: + pass + if args: + # Called in signal handler + raise SystemExit(1) + + +def lockpidfile(pidfile): + fd = os.open( + pidfile, + os.O_WRONLY | os.O_CREAT, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH + ) + try: + fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except EnvironmentError: + os.close(fd) + return None + os.lseek(fd, 0, os.SEEK_SET) + os.ftruncate(fd, 0) + os.write(fd, ('%d' % os.getpid()).encode('ascii')) + os.fsync(fd) + cleanup = partial(cleanup_lockfile, pidfile, fd) + signal(SIGTERM, cleanup) + atexit.register(cleanup) + return fd + + +def main(): + parser = get_daemon_argparser() + args = parser.parse_args() + is_daemon = False + address = None + pidfile = None + + if args.socket: + address = args.socket + if not USE_FILESYSTEM: + address = '\0' + address + else: + if USE_FILESYSTEM: + address = '/tmp/powerline-ipc-%d' + else: + # Use the abstract namespace for sockets rather than the filesystem + # (Available only in linux) + address = '\0powerline-ipc-%d' + + address = address % os.getuid() + + if USE_FILESYSTEM: + pidfile = address + '.pid' + + if args.kill: + if args.foreground or args.replace: + parser.error('--kill and --foreground/--replace cannot be used together') + if kill_daemon(address): + if not args.quiet: + print ('Kill command sent to daemon, if it does not die in a couple of seconds use kill to kill it') + raise SystemExit(0) + else: + if not args.quiet: + print ('No running daemon found') + raise SystemExit(1) + + if args.replace: + while kill_daemon(address): + if not args.quiet: + print ('Kill command sent to daemon, waiting for daemon to exit, press Ctrl-C to terminate wait and exit') + sleep(2) + + if USE_FILESYSTEM and not args.foreground: + # We must daemonize before creating the locked pidfile, unfortunately, + # this means further print statements are discarded + is_daemon = daemonize() + + if USE_FILESYSTEM: + # Create a locked pid file containing the daemon’s PID + if lockpidfile(pidfile) is None: + if not args.quiet: + sys.stderr.write( + 'The daemon is already running. Use %s -k to kill it.\n' % ( + os.path.basename(sys.argv[0]))) + raise SystemExit(1) + + # Bind to address or bail if we cannot bind + sock = check_existing(address) + if sock is None: + if not args.quiet: + sys.stderr.write( + 'The daemon is already running. Use %s -k to kill it.\n' % ( + os.path.basename(sys.argv[0]))) + raise SystemExit(1) + + if not USE_FILESYSTEM and not args.foreground: + # We daemonize on linux + is_daemon = daemonize() + + return main_loop(sock, is_daemon) + + +if __name__ == '__main__': + main() diff --git a/scripts/powerline-lint b/scripts/powerline-lint new file mode 100755 index 0000000..f665ba1 --- /dev/null +++ b/scripts/powerline-lint @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +from powerline.lint import check +from powerline.commands.lint import get_argparser + + +if __name__ == '__main__': + args = get_argparser().parse_args() + sys.exit(check(args.config_path, args.debug)) diff --git a/scripts/powerline-release.py b/scripts/powerline-release.py new file mode 100755 index 0000000..42381bd --- /dev/null +++ b/scripts/powerline-release.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import argparse +import codecs +import os +import re + +from subprocess import check_output, check_call, CalledProcessError +from getpass import getpass + +from github import Github + + +OVERLAY_NAME = 'raiagent' +OVERLAY = 'leycec/' + OVERLAY_NAME +OVERLAY_BRANCH_FORMAT = 'powerline-release-{0}' + + +def parse_version(s): + if s == ('+' * len(s)): + try: + last_version = next(iter(sorted([ + tuple(map(int, tag.split('.'))) + for tag in check_output(['git', 'tag', '-l', '[0-9]*.*']).split('\n')[:-1] + ], reverse=True))) + except StopIteration: + raise ValueError('No existing versions found') + + version = [] + for i in range(len(s) - 1): + try: + version.append(last_version[i]) + except IndexError: + version.append(0) + + try: + version.append(last_version[len(s) - 1] + 1) + except IndexError: + version.append(1) + + if len(version) == 1: + version.append(0) + + return tuple(version) + else: + return tuple(map(int, s.split('.'))) + + +def setup_py_filter(filter_func): + with codecs.open('.setup.py.new', 'w', encoding='utf-8') as NS: + with codecs.open('setup.py', 'r', encoding='utf-8') as OS: + for line in OS: + line = filter_func(line) + NS.write(line) + + os.unlink('setup.py') + os.rename('.setup.py.new', 'setup.py') + + +def setup_py_develop_filter(line, version_string): + if line.startswith('\tbase_version = '): + line = '\tbase_version = \'' + version_string + '\'\n' + return line + + +def setup_py_master_filter(line, version_string): + if line.startswith('\tversion='): + line = '\tversion=\'' + version_string + '\',\n' + elif 'Development Status' in line: + line = '\t\t\'Development Status :: 5 - Production/Stable\',\n' + return line + + +def merge(version_string, rev, **kwargs): + check_call(['git', 'checkout', rev]) + + temp_branch_name = 'release-' + version_string + check_call(['git', 'checkout', '-b', temp_branch_name]) + setup_py_filter(lambda line: setup_py_develop_filter(line, version_string)) + check_call(['git', 'add', 'setup.py']) + check_call(['git', 'commit', '-m', 'Update base version']) + check_call(['git', 'checkout', rev]) + check_call(['git', 'merge', '--no-ff', + '--strategy', 'recursive', + '--strategy-option', 'theirs', + '--commit', + '-m', 'Merge branch \'{0}\' into {1}'.format(temp_branch_name, rev), + temp_branch_name]) + check_call(['git', 'branch', '-d', temp_branch_name]) + + rev = check_output(['git', 'rev-parse', 'HEAD']).strip() + + check_call(['git', 'checkout', 'master']) + try: + check_call(['git', 'merge', '--no-ff', '--no-commit', '--log', rev]) + except CalledProcessError: + check_call(['git', 'mergetool', '--tool', 'vimdiff2']) + + setup_py_filter(lambda line: setup_py_master_filter(line, version_string)) + check_call(['git', 'add', 'setup.py']) + + check_call(['git', 'commit']) + check_call(['git', 'tag', '-m', 'Release ' + version_string, '-a', version_string]) + + +def push(version_string, rev, **kwargs): + check_call(['git', 'push', 'upstream', 'master']) + check_call(['git', 'push', 'upstream', version_string]) + check_call(['git', 'push', 'upstream', rev]) + + +def upload(**args): + check_call(['python', 'setup.py', 'sdist', 'upload']) + check_call(['python', 'setup.py', 'upload_docs']) + + +gh = None + + +def get_gh(user, password): + global gh + if not gh: + gh = Github(user, password) + return gh + + +def create_ebuilds(version_string, overlay, user, **kwargs): + overlay_url = 'git://github.com/' + OVERLAY + if not os.path.isdir(overlay): + check_call(['git', 'clone', overlay_url, overlay]) + check_call(['git', 'checkout', 'master'], cwd=overlay) + check_call(['git', 'pull', '--ff-only', overlay_url, 'master'], cwd=overlay) + branch = OVERLAY_BRANCH_FORMAT.format(version_string) + check_call(['git', 'branch', branch], cwd=overlay) + check_call(['git', 'checkout', branch], cwd=overlay) + os.environ['DISTDIR'] = '/tmp/powerline-distfiles' + if not os.path.isdir(os.environ['DISTDIR']): + os.mkdir(os.environ['DISTDIR']) + new_files = [] + for category, pn in ( + ('app-misc', 'powerline'), + ('app-vim', 'powerline-vim'), + ): + pdir = os.path.join(overlay, category, pn) + live_ebuild = None + for ebuild in os.listdir(pdir): + if ebuild.endswith('.ebuild') and '9999' in ebuild: + live_ebuild_base = ebuild + live_ebuild = os.path.join(pdir, ebuild) + break + assert(live_ebuild) + vcur = os.path.join(pdir, '{0}-{1}.ebuild'.format(pn, version_string)) + if pn == 'powerline-vim': + with open(live_ebuild) as LEF: + with open(vcur, 'w') as F: + dropnext = False + for line in LEF: + if line.startswith('EGIT'): + # Drop all EGIT_… and the next empty line + dropnext = True + next_re = re.compile('^$') + continue + if dropnext: + assert(next_re.match(line)) + dropnext = False + continue + if line.startswith('# Note the lack of an assignment to ${S}'): + next_re = re.compile('^#') + dropnext = True + line = 'S="${WORKDIR}/${MY_P}"\n' + if line.startswith('inherit'): + line = line.replace(' git-r3', '') + line += '\n' + line += 'MY_PN="powerline-status"\n' + line += 'MY_P="${MY_PN}-${PV}"' + line += '\n' + elif line.startswith('HOMEPAGE'): + line += 'SRC_URI="mirror://pypi/p/${MY_PN}/${MY_P}.tar.gz"\n' + elif line.startswith('KEYWORDS'): + line = 'KEYWORDS="~amd64 ~ppc ~x86 ~x86-fbsd"\n' + F.write(line) + else: + os.symlink(live_ebuild_base, vcur) + new_files.append(vcur) + check_call(['ebuild', vcur, 'manifest']) + new_files.append(os.path.join(pdir, 'Manifest')) + check_call(['git', 'add', '--'] + new_files, cwd=overlay) + check_call(['git', 'commit'] + new_files + ['-m', 'powerline*: Release {0}'.format(version_string)], + cwd=overlay) + check_call(['git', 'push', '-f', 'git@github.com:{0}/{1}'.format(user, OVERLAY_NAME), branch], cwd=overlay) + + +def update_overlay(version_string, user, password, **kwargs): + gh = get_gh(user, password) + overlay = gh.get_repo(OVERLAY) + overlay.create_pull( + title='New powerline version: ' + version_string, + body='Add ebuilds for new powerline version\n\n---\n\nCreated automatically by release script', + base='master', + head=user + ':' + OVERLAY_BRANCH_FORMAT.format(version_string), + ) + + +stages = ( + ('merge', merge), + ('push', push), + ('upload', upload), + ('create_ebuilds', create_ebuilds), + ('update_overlay', update_overlay), +) + + +def create_release(version, user, password=None, run_stages=None, **kwargs): + version_string = '.'.join((str(v) for v in version)) + if not password: + password = getpass('Password for {0}: '.format(user)) + for stname, stfunc in stages: + if run_stages is None or stname in run_stages: + stfunc(version_string=version_string, user=user, password=password, **kwargs) + + +p = argparse.ArgumentParser(description='Powerline release script') +p.add_argument('-u', '--user', type=str, metavar='USER', help='Github username.', required=True) +p.add_argument('-v', '--version', type=parse_version, metavar='VERSION', help='Use given version for the release. If version contains only `+\' signs then it will increase latest version number: one `+\' increases major version number (e.g. 1.2.3 -> 2.0), `++\' increases minor version number (e.g. 1.2.3 -> 1.3), `+++\' increases patch level (e.g. 1.2.3 -> 1.2.4). Defaults to `+++\'.', default='+++') +p.add_argument('-r', '--rev', metavar='COMMIT', help='Use given revision for the release. Defaults to `develop\'.', default='develop') +p.add_argument('-s', '--stages', action='append', metavar='STAGE', help='Only run one of the given stages (default to all).', choices=tuple((stname for stname, stfunc in stages))) +p.add_argument('-p', '--password', type=str, metavar='PASS', help='Github password. You will be prompted if it is not supplied.') +p.add_argument('-o', '--overlay', type=str, metavar='PATH', help='Location of the local clone of the {0} overlay. If provided directory does not exist it will be created by “git clone”. Defaults to /tmp/powerline-{0}.'.format(OVERLAY_NAME), default='/tmp/powerline-' + OVERLAY_NAME) + + +if __name__ == '__main__': + args = p.parse_args() + create_release( + version=args.version, + rev=args.rev, + user=args.user, + password=args.password, + overlay=args.overlay, + run_stages=args.stages, + ) diff --git a/scripts/powerline-render b/scripts/powerline-render new file mode 100755 index 0000000..8b71b00 --- /dev/null +++ b/scripts/powerline-render @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os + +try: + from powerline.shell import ShellPowerline +except ImportError: + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))) + from powerline.shell import ShellPowerline + +from powerline.commands.main import get_argparser, finish_args, write_output +from powerline.lib.encoding import get_unicode_writer + + +if sys.version_info < (3,): + write = sys.stdout.write +else: + write = sys.stdout.buffer.write + + +if __name__ == '__main__': + parser = get_argparser() + args = parser.parse_args() + finish_args(parser, os.environ, args) + powerline = ShellPowerline(args, run_once=True) + segment_info = {'args': args, 'environ': os.environ} + write_output(args, powerline, segment_info, get_unicode_writer()) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..30592b0 --- /dev/null +++ b/setup.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys +import subprocess +import logging +import shlex + +from traceback import print_exc +from setuptools import setup, find_packages + + +CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) +try: + README = open(os.path.join(CURRENT_DIR, 'README.rst'), 'rb').read().decode('utf-8') +except IOError: + README = '' + +OLD_PYTHON = sys.version_info < (2, 7) + + +def compile_client(): + '''Compile the C powerline-client script.''' + + if hasattr(sys, 'getwindowsversion'): + raise NotImplementedError() + else: + from distutils.ccompiler import new_compiler + compiler = new_compiler().compiler + cflags = os.environ.get('CFLAGS', str('-O3')) + # A normal split would do a split on each space which might be incorrect. The + # shlex will not split if a space occurs in an arguments value. + subprocess.check_call(compiler + shlex.split(cflags) + ['client/powerline.c', '-o', 'scripts/powerline']) + +try: + compile_client() +except Exception as e: + print('Compiling C version of powerline-client failed') + logging.exception(e) + # FIXME Catch more specific exceptions + import shutil + if hasattr(shutil, 'which'): + which = shutil.which + else: + sys.path.append(CURRENT_DIR) + from powerline.lib.shell import which + if which('socat') and which('sed') and which('sh'): + print('Using powerline.sh script instead of C version (requires socat, sed and sh)') + shutil.copyfile('client/powerline.sh', 'scripts/powerline') + can_use_scripts = True + else: + print('Using powerline.py script instead of C version') + shutil.copyfile('client/powerline.py', 'scripts/powerline') + can_use_scripts = True +else: + can_use_scripts = False + + +def get_version(): + base_version = '2.7' + base_version += '.dev9999' + try: + return base_version + '+git.' + str(subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()) + except Exception: + print_exc() + return base_version + + +setup( + name='powerline-status', + version='2.7', + description='The ultimate statusline/prompt utility.', + long_description=README, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Environment :: Plugins', + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + ], + download_url='https://github.com/powerline/powerline/archive/develop.zip', + author='Kim Silkebaekken', + author_email='kim.silkebaekken+vim@gmail.com', + url='https://github.com/powerline/powerline', + license='MIT', + # 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 + # ASCII, and fails when powerline-client is a binary. + # + # XXX Python 2 fucks up script contents*. Not using it to install scripts + # any longer. + # * Consider the following input: + # % alias hex1=$'hexdump -e \'"" 1/1 "%02X\n"\'' + # % diff <(hex1 ./scripts/powerline) <(hex1 ~/.local/bin/powerline) + # This will show output like + # 375c375 + # < 0D + # --- + # > 0A + # (repeated, with diff segment header numbers growing up). + # + # FIXME Current solution does not work with `pip install -e`. Still better + # then solution that is not working at all. + scripts=[ + 'scripts/powerline-lint', + 'scripts/powerline-daemon', + 'scripts/powerline-render', + 'scripts/powerline-config', + ] + (['scripts/powerline'] if can_use_scripts else []), + data_files=(None if can_use_scripts else (('bin', ['scripts/powerline']),)), + keywords='', + packages=find_packages(exclude=('tests', 'tests.*')), + include_package_data=True, + zip_safe=False, + install_requires=['argparse'] if OLD_PYTHON else [], + extras_require={ + 'docs': [ + 'Sphinx', + 'sphinx_rtd_theme', + ], + }, + test_suite='tests' if not OLD_PYTHON else None, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/__init__.py diff --git a/tests/install.sh b/tests/install.sh new file mode 100755 index 0000000..6925a34 --- /dev/null +++ b/tests/install.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +set -e +set -x + +remote_master_hex() { + local url="$1" + git ls-remote "$url" refs/heads/master | cut -f1 +} + +checkout_cached_dir() { + local url="$1" + local target="$2" + if ! test -e "$target/.version" || \ + test "$(cat "$target/.version")" != "$(remote_master_hex "$url")" ; then + rm -rf "$target" + fi + if ! test -d "$target" ; then + git clone --depth=1 "$url" "$target" + git rev-parse HEAD > "$target/.version" + rm -rf "$target"/.git + fi +} + +checkout_cached_dir git://github.com/powerline/bot-ci tests/bot-ci +checkout_cached_dir git://github.com/powerline/deps tests/bot-ci/deps + +. tests/bot-ci/scripts/common/main.sh + +mkdir -p "$HOME/opt" + +if test -n "$USE_UCS2_PYTHON" ; then + if test "$UCS2_PYTHON_VARIANT" = "2.6" ; then + pip install 'virtualenvwrapper==4.6.0' + else + pip install virtualenvwrapper + fi + set +e + . virtualenvwrapper.sh + set -e + archive="${PWD:-$(pwd)}/tests/bot-ci/deps/cpython-ucs2/cpython-ucs2-${UCS2_PYTHON_VARIANT}.tar.gz" + sh -c "cd $HOME/opt && tar xzf $archive" + PYTHON="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/bin/python$UCS2_PYTHON_VARIANT" + export LD_LIBRARY_PATH="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/lib${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}" + set +e + mkvirtualenv -p "$PYTHON" cpython-ucs2-$UCS2_PYTHON_VARIANT + set -e + . tests/bot-ci/scripts/common/main.sh + pip install --verbose --verbose --verbose . + if test "$UCS2_PYTHON_VARIANT" = "2.6" ; then + rm tests/bot-ci/deps/wheels/ucs2-CPython-${UCS2_PYTHON_VARIANT}*/pyuv*.whl + fi + pip install --no-deps tests/bot-ci/deps/wheels/ucs2-CPython-${UCS2_PYTHON_VARIANT}*/*.whl +else + pip install --verbose --verbose --verbose . + # FIXME Uv watcher sometimes misses events and INotify is not available in + # Python-2.6, thus pyuv should be removed in order for VCS tests to + # pass. + if test "$PYTHON_VERSION_MAJOR" -eq 2 && test "$PYTHON_VERSION_MINOR" -lt 7 ; then + rm tests/bot-ci/deps/wheels/$PYTHON_SUFFIX/pyuv*.whl + fi + pip install --no-deps tests/bot-ci/deps/wheels/$PYTHON_SUFFIX/*.whl +fi +if test "$PYTHON_IMPLEMENTATION" = "CPython" ; then + archive="${PWD:-$(pwd)}/tests/bot-ci/deps/zpython/zsh-${PYTHON_MM}${USE_UCS2_PYTHON:+-ucs2}.tar.gz" + sh -c "cd $HOME/opt && tar xzf $archive" +fi + +archive="${PWD:-$(pwd)}/tests/bot-ci/deps/fish/fish.tar.gz" +sh -c "cd $HOME/opt && tar xzf $archive" + +mkdir tests/vim-plugins + +for archive in "$ROOT"/tests/bot-ci/deps/vim-plugins/*.tar.gz ; do + ( + cd tests/vim-plugins + tar -xzvf "$archive" + ) +done + +true diff --git a/tests/modules/__init__.py b/tests/modules/__init__.py new file mode 100644 index 0000000..12aae20 --- /dev/null +++ b/tests/modules/__init__.py @@ -0,0 +1,94 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os + +if sys.version_info < (2, 7): + from unittest2 import TestCase as _TestCase # NOQA + from unittest2 import main as _main # NOQA + from unittest2.case import SkipTest # NOQA +else: + from unittest import TestCase as _TestCase # NOQA + from unittest import main as _main # NOQA + from unittest.case import SkipTest # NOQA + +from tests.modules.lib import PowerlineSingleTest + + +class PowerlineDummyTest(object): + def __enter__(self): + return self + + def __exit__(self, *args): + pass + + def fail(self, *args, **kwargs): + pass + + def exception(self, *args, **kwargs): + pass + + +class PowerlineTestSuite(object): + def __init__(self, name): + self.name = name + + def __enter__(self): + self.saved_current_suite = os.environ['POWERLINE_CURRENT_SUITE'] + os.environ['POWERLINE_CURRENT_SUITE'] = ( + self.saved_current_suite + '/' + self.name) + self.suite = self.saved_current_suite + '/' + self.name + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is not None: + self.exception( + 'suite_noexcept', + 'Exception while running test suite: {0!r}'.format(exc_value), + ) + os.environ['POWERLINE_CURRENT_SUITE'] = self.saved_current_suite + + def record_test_failure(self, fail_char, test_name, message, allow_failure=False): + if allow_failure: + fail_char = 'A' + fail_char + full_msg = '{fail_char} {suite}|{test_name} :: {message}'.format( + fail_char=fail_char, + suite=self.suite, + test_name=test_name, + message=message, + ) + with open(os.environ['FAILURES_FILE'], 'a') as ffd: + ffd.write(full_msg + '\n') + return False + + def exception(self, test_name, message, allow_failure=False): + return self.record_test_failure('E', test_name, message, allow_failure) + + def fail(self, test_name, message, allow_failure=False): + return self.record_test_failure('F', test_name, message, allow_failure) + + def test(self, name, attempts_left=0): + if not attempts_left: + return PowerlineSingleTest(self, name) + else: + return PowerlineDummyTest() + + def subsuite(self, name): + return PowerlineTestSuite(name) + + +suite = None + + +def main(*args, **kwargs): + global suite + suite = PowerlineTestSuite(sys.argv[0]) + _main(*args, **kwargs) + + +class TestCase(_TestCase): + def fail(self, msg=None): + suite.fail(self.__class__.__name__, + msg or 'Test failed without message') + super(TestCase, self).fail(*args, **kwargs) diff --git a/tests/modules/lib/__init__.py b/tests/modules/lib/__init__.py new file mode 100644 index 0000000..d45ccae --- /dev/null +++ b/tests/modules/lib/__init__.py @@ -0,0 +1,183 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import imp +import sys + + +class Pl(object): + def __init__(self): + self.exceptions = [] + self.errors = [] + self.warns = [] + self.debugs = [] + self.infos = [] + self.prefix = None + self.use_daemon_threads = True + + for meth in ('error', 'warn', 'debug', 'exception', 'info'): + exec(( + 'def {0}(self, msg, *args, **kwargs):\n' + ' self.{0}s.append((kwargs.get("prefix") or self.prefix, msg, args, kwargs))\n' + ).format(meth)) + + def __nonzero__(self): + return bool(self.exceptions or self.errors or self.warns) + + __bool__ = __nonzero__ + + +class Args(object): + theme_override = {} + config_override = {} + config_path = None + ext = ['shell'] + renderer_module = None + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +def urllib_read(query_url): + if query_url.startswith('http://ipv'): + if query_url.startswith('http://ipv4.icanhazip.com'): + return '127.0.0.1' + elif query_url.startswith('http://ipv4.icanhazip.com'): + return '2001:4801:7818:6:abc5:ba2c:ff10:275f' + elif query_url.startswith('http://geoip.nekudo.com/api/'): + return '{"city":"Meppen","country":{"name":"Germany", "code":"DE"},"location":{"accuracy_radius":100,"latitude":52.6833,"longitude":7.3167,"time_zone":"Europe\/Berlin"},"ip":"82.145.55.16"}' + elif query_url.startswith('http://query.yahooapis.com/v1/public/'): + if 'Meppen' in query_url: + return r'{"query":{"count":1,"created":"2016-05-13T19:43:18Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Meppen, NI, DE","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","description":"Yahoo! Weather for Meppen, NI, DE","language":"en-us","lastBuildDate":"Fri, 13 May 2016 09:43 PM CEST","ttl":"60","location":{"city":"Meppen","country":"Germany","region":" NI"},"wind":{"chill":"55","direction":"350","speed":"25"},"atmosphere":{"humidity":"57","pressure":"1004.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"5:35 am","sunset":"9:21 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 Meppen, NI, DE at 08:00 PM CEST","lat":"52.68993","long":"7.29115","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","pubDate":"Fri, 13 May 2016 08:00 PM CEST","condition":{"code":"23","date":"Fri, 13 May 2016 08:00 PM CEST","temp":"14","text":"Breezy"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"71","low":"48","text":"Partly Cloudy"},{"code":"28","date":"14 May 2016","day":"Sat","high":"54","low":"44","text":"Mostly Cloudy"},{"code":"11","date":"15 May 2016","day":"Sun","high":"55","low":"43","text":"Showers"},{"code":"28","date":"16 May 2016","day":"Mon","high":"54","low":"42","text":"Mostly Cloudy"},{"code":"28","date":"17 May 2016","day":"Tue","high":"57","low":"43","text":"Mostly Cloudy"},{"code":"12","date":"18 May 2016","day":"Wed","high":"62","low":"45","text":"Rain"},{"code":"28","date":"19 May 2016","day":"Thu","high":"63","low":"48","text":"Mostly Cloudy"},{"code":"28","date":"20 May 2016","day":"Fri","high":"67","low":"50","text":"Mostly Cloudy"},{"code":"30","date":"21 May 2016","day":"Sat","high":"71","low":"50","text":"Partly Cloudy"},{"code":"30","date":"22 May 2016","day":"Sun","high":"74","low":"54","text":"Partly Cloudy"}],"description":"<![CDATA[<img src=\"http://l.yimg.com/a/i/us/we/52/23.gif\"/>\n<BR />\n<b>Current Conditions:</b>\n<BR />Breezy\n<BR />\n<BR />\n<b>Forecast:</b>\n<BR /> Fri - Partly Cloudy. High: 71Low: 48\n<BR /> Sat - Mostly Cloudy. High: 54Low: 44\n<BR /> Sun - Showers. High: 55Low: 43\n<BR /> Mon - Mostly Cloudy. High: 54Low: 42\n<BR /> Tue - Mostly Cloudy. High: 57Low: 43\n<BR />\n<BR />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/\">Full Forecast at Yahoo! Weather</a>\n<BR />\n<BR />\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)\n<BR />\n]]>","guid":{"isPermaLink":"false"}}}}}}' + elif 'Moscow' in query_url: + return r'{"query":{"count":1,"created":"2016-05-13T19:47:01Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Moscow, Moscow Federal City, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","description":"Yahoo! Weather for Moscow, Moscow Federal City, RU","language":"en-us","lastBuildDate":"Fri, 13 May 2016 10:47 PM MSK","ttl":"60","location":{"city":"Moscow","country":"Russia","region":" Moscow Federal City"},"wind":{"chill":"45","direction":"80","speed":"11"},"atmosphere":{"humidity":"52","pressure":"993.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"4:19 am","sunset":"8:34 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 Moscow, Moscow Federal City, RU at 09:00 PM MSK","lat":"55.741638","long":"37.605061","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","pubDate":"Fri, 13 May 2016 09:00 PM MSK","condition":{"code":"33","date":"Fri, 13 May 2016 09:00 PM MSK","temp":"9","text":"Mostly Clear"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"62","low":"41","text":"Partly Cloudy"},{"code":"30","date":"14 May 2016","day":"Sat","high":"64","low":"43","text":"Partly Cloudy"},{"code":"30","date":"15 May 2016","day":"Sun","high":"63","low":"44","text":"Partly Cloudy"},{"code":"12","date":"16 May 2016","day":"Mon","high":"60","low":"47","text":"Rain"},{"code":"12","date":"17 May 2016","day":"Tue","high":"64","low":"48","text":"Rain"},{"code":"28","date":"18 May 2016","day":"Wed","high":"67","low":"48","text":"Mostly Cloudy"},{"code":"12","date":"19 May 2016","day":"Thu","high":"68","low":"49","text":"Rain"},{"code":"39","date":"20 May 2016","day":"Fri","high":"66","low":"50","text":"Scattered Showers"},{"code":"39","date":"21 May 2016","day":"Sat","high":"69","low":"49","text":"Scattered Showers"},{"code":"30","date":"22 May 2016","day":"Sun","high":"73","low":"50","text":"Partly Cloudy"}],"description":"<![CDATA[<img src=\"http://l.yimg.com/a/i/us/we/52/33.gif\"/>\n<BR />\n<b>Current Conditions:</b>\n<BR />Mostly Clear\n<BR />\n<BR />\n<b>Forecast:</b>\n<BR /> Fri - Partly Cloudy. High: 62Low: 41\n<BR /> Sat - Partly Cloudy. High: 64Low: 43\n<BR /> Sun - Partly Cloudy. High: 63Low: 44\n<BR /> Mon - Rain. High: 60Low: 47\n<BR /> Tue - Rain. High: 64Low: 48\n<BR />\n<BR />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/\">Full Forecast at Yahoo! Weather</a>\n<BR />\n<BR />\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)\n<BR />\n]]>","guid":{"isPermaLink":"false"}}}}}}' + else: + raise NotImplementedError + + +class Process(object): + def __init__(self, output, err): + self.output = output + self.err = err + + def communicate(self): + return self.output, self.err + + +class ModuleReplace(object): + def __init__(self, name, new): + self.name = name + self.new = new + + def __enter__(self): + self.old = sys.modules.get(self.name) + if not self.old: + try: + self.old = __import__(self.name) + except ImportError: + pass + sys.modules[self.name] = self.new + + def __exit__(self, *args): + if self.old: + sys.modules[self.name] = self.old + else: + sys.modules.pop(self.name) + + +def replace_module(name, new=None, **kwargs): + if not new: + new = new_module(name, **kwargs) + return ModuleReplace(name, new) + + +def new_module(name, **kwargs): + module = imp.new_module(name) + for k, v in kwargs.items(): + setattr(module, k, v) + return module + + +class AttrReplace(object): + def __init__(self, obj, *args): + self.obj = obj + self.attrs = args[::2] + self.new = args[1::2] + + def __enter__(self): + self.old = {} + for i, attr in enumerate(self.attrs): + try: + self.old[i] = getattr(self.obj, attr) + except AttributeError: + pass + for attr, new in zip(self.attrs, self.new): + setattr(self.obj, attr, new) + + def __exit__(self, *args): + for i, attr in enumerate(self.attrs): + try: + old = self.old[i] + except KeyError: + delattr(self.obj, attr) + else: + setattr(self.obj, attr, old) + + +replace_attr = AttrReplace + + +def replace_module_module(module, name, **kwargs): + return replace_attr(module, name, new_module(name, **kwargs)) + + +class ItemReplace(object): + def __init__(self, d, key, new, r=None): + self.key = key + self.new = new + self.d = d + self.r = r + + def __enter__(self): + self.old = self.d.get(self.key) + self.d[self.key] = self.new + return self.r + + def __exit__(self, *args): + if self.old is None: + try: + self.d.pop(self.key) + except KeyError: + pass + else: + self.d[self.key] = self.old + + +def replace_item(d, key, new): + return ItemReplace(d, key, new, d) + + +def replace_env(key, new, environ=None, **kwargs): + r = kwargs.copy() + r['environ'] = environ or {} + return ItemReplace(r['environ'], key, new, r) + + +class PowerlineSingleTest(object): + def __init__(self, suite, name): + self.suite = suite + self.name = name + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is not None: + self.exception('Exception while running test: {0!r}'.format( + exc_value)) + + def fail(self, message, allow_failure=False): + return self.suite.fail(self.name, message, allow_failure) + + def exception(self, message, allow_failure=False): + return self.suite.exception(self.name, message, allow_failure) diff --git a/tests/modules/lib/config_mock.py b/tests/modules/lib/config_mock.py new file mode 100644 index 0000000..900b60f --- /dev/null +++ b/tests/modules/lib/config_mock.py @@ -0,0 +1,230 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from threading import Lock +from copy import deepcopy +from time import sleep +from functools import wraps + +from powerline.renderer import Renderer +from powerline.lib.config import ConfigLoader +from powerline import Powerline, get_default_theme + +from tests.modules.lib import Args, replace_attr + + +UT = get_default_theme(is_unicode=True) +AT = get_default_theme(is_unicode=False) + + +class TestHelpers(object): + def __init__(self, config): + self.config = config + self.access_log = [] + self.access_lock = Lock() + + def loader_condition(self, path): + return (path in self.config) and path + + def find_config_files(self, cfg_path, config_loader, loader_callback): + if cfg_path.endswith('.json'): + cfg_path = cfg_path[:-5] + if cfg_path.startswith('/'): + cfg_path = cfg_path.lstrip('/') + with self.access_lock: + self.access_log.append('check:' + cfg_path) + if cfg_path in self.config: + yield cfg_path + else: + if config_loader: + config_loader.register_missing(self.loader_condition, loader_callback, cfg_path) + raise IOError(('fcf:' if cfg_path.endswith('raise') else '') + cfg_path) + + def load_json_config(self, config_file_path, *args, **kwargs): + if config_file_path.endswith('.json'): + config_file_path = config_file_path[:-5] + if config_file_path.startswith('/'): + config_file_path = config_file_path.lstrip('/') + with self.access_lock: + self.access_log.append('load:' + config_file_path) + try: + return deepcopy(self.config[config_file_path]) + except KeyError: + raise IOError(config_file_path) + + def pop_events(self): + with self.access_lock: + r = self.access_log[:] + self.access_log = [] + return r + + +def log_call(func): + @wraps(func) + def ret(self, *args, **kwargs): + self._calls.append((func.__name__, args, kwargs)) + return func(self, *args, **kwargs) + return ret + + +class TestWatcher(object): + events = set() + lock = Lock() + + def __init__(self): + self._calls = [] + + @log_call + def watch(self, file): + pass + + @log_call + def __call__(self, file): + with self.lock: + if file in self.events: + self.events.remove(file) + return True + return False + + def _reset(self, files): + with self.lock: + self.events.clear() + self.events.update(files) + + @log_call + def unsubscribe(self): + pass + + +class Logger(object): + def __init__(self): + self.messages = [] + self.lock = Lock() + + def _add_msg(self, attr, msg): + with self.lock: + self.messages.append(attr + ':' + msg) + + def _pop_msgs(self): + with self.lock: + r = self.messages + self.messages = [] + return r + + def __getattr__(self, attr): + return lambda *args, **kwargs: self._add_msg(attr, *args, **kwargs) + + +class SimpleRenderer(Renderer): + def hlstyle(self, fg=None, bg=None, attrs=None): + return '<{fg} {bg} {attrs}>'.format(fg=fg and fg[0], bg=bg and bg[0], attrs=attrs) + + +class EvenSimplerRenderer(Renderer): + def hlstyle(self, fg=None, bg=None, attrs=None): + return '{{{fg}{bg}{attrs}}}'.format( + fg=fg and fg[0] or '-', + bg=bg and bg[0] or '-', + attrs=attrs if attrs else '', + ) + + +class TestPowerline(Powerline): + _created = False + + def __init__(self, _helpers, **kwargs): + super(TestPowerline, self).__init__(**kwargs) + self._helpers = _helpers + self.find_config_files = _helpers.find_config_files + + @staticmethod + def get_local_themes(local_themes): + return local_themes + + @staticmethod + def get_config_paths(): + return [''] + + def _will_create_renderer(self): + return self.cr_kwargs + + def _pop_events(self): + return self._helpers.pop_events() + + +renderer = EvenSimplerRenderer + + +class TestConfigLoader(ConfigLoader): + def __init__(self, _helpers, **kwargs): + watcher = TestWatcher() + super(TestConfigLoader, self).__init__( + load=_helpers.load_json_config, + watcher=watcher, + watcher_type='test', + **kwargs + ) + + +def get_powerline(config, **kwargs): + helpers = TestHelpers(config) + return get_powerline_raw( + helpers, + TestPowerline, + _helpers=helpers, + ext='test', + renderer_module='tests.modules.lib.config_mock', + logger=Logger(), + **kwargs + ) + + +def select_renderer(simpler_renderer=False): + global renderer + renderer = EvenSimplerRenderer if simpler_renderer else SimpleRenderer + + +def get_powerline_raw(helpers, PowerlineClass, replace_gcp=False, **kwargs): + if not isinstance(helpers, TestHelpers): + helpers = TestHelpers(helpers) + select_renderer(kwargs.pop('simpler_renderer', False)) + + if replace_gcp: + class PowerlineClass(PowerlineClass): + @staticmethod + def get_config_paths(): + return ['/'] + + pl = PowerlineClass( + config_loader=TestConfigLoader( + _helpers=helpers, + run_once=kwargs.get('run_once') + ), + **kwargs + ) + pl._watcher = pl.config_loader.watcher + return pl + + +def swap_attributes(config, powerline_module): + return replace_attr(powerline_module, 'os', Args( + path=Args( + isfile=lambda path: path.lstrip('/').replace('.json', '') in config, + join=os.path.join, + expanduser=lambda path: path, + realpath=lambda path: path, + dirname=os.path.dirname, + ), + environ={}, + )) + + +def add_watcher_events(p, *args, **kwargs): + if isinstance(p._watcher, TestWatcher): + p._watcher._reset(args) + while not p._will_create_renderer(): + sleep(kwargs.get('interval', 0.1)) + if not kwargs.get('wait', True): + return diff --git a/tests/modules/lib/fsconfig.py b/tests/modules/lib/fsconfig.py new file mode 100644 index 0000000..757e874 --- /dev/null +++ b/tests/modules/lib/fsconfig.py @@ -0,0 +1,83 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import json + +from subprocess import check_call +from shutil import rmtree +from itertools import chain + +from powerline import Powerline + + +CONFIG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config') + + +class TestPowerline(Powerline): + def __init__(self, _paths, *args, **kwargs): + super(TestPowerline, self).__init__(*args, **kwargs) + self._paths = _paths + + def get_config_paths(self): + return self._paths + + +def mkdir_recursive(directory): + if os.path.isdir(directory): + return + mkdir_recursive(os.path.dirname(directory)) + os.mkdir(directory) + + +class FSTree(object): + __slots__ = ('tree', 'p', 'p_kwargs', 'create_p', 'get_config_paths', 'root') + + def __init__( + self, + tree, + p_kwargs={'run_once': True}, + root=CONFIG_DIR, + get_config_paths=lambda p: (p,), + create_p=False + ): + self.tree = tree + self.root = root + self.get_config_paths = get_config_paths + self.create_p = create_p + self.p = None + self.p_kwargs = p_kwargs + + def __enter__(self, *args): + os.mkdir(self.root) + for k, v in self.tree.items(): + fname = os.path.join(self.root, k) + '.json' + mkdir_recursive(os.path.dirname(fname)) + with open(fname, 'w') as F: + json.dump(v, F) + if self.create_p: + self.p = TestPowerline( + _paths=self.get_config_paths(self.root), + ext='test', + renderer_module='tests.modules.lib.config_mock', + **self.p_kwargs + ) + if os.environ.get('POWERLINE_RUN_LINT_DURING_TESTS'): + try: + check_call(chain(['scripts/powerline-lint'], *[ + ('-p', d) for d in ( + self.p.get_config_paths() if self.p + else self.get_config_paths(self.root) + ) + ])) + except: + self.__exit__() + raise + return self.p and self.p.__enter__(*args) + + def __exit__(self, *args): + try: + rmtree(self.root) + finally: + if self.p: + self.p.__exit__(*args) diff --git a/tests/modules/lib/terminal.py b/tests/modules/lib/terminal.py new file mode 100644 index 0000000..540135d --- /dev/null +++ b/tests/modules/lib/terminal.py @@ -0,0 +1,307 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import threading +import os + +from time import sleep +from itertools import groupby +from signal import SIGKILL +from difflib import ndiff + +import pexpect + +from powerline.lib.unicode import u + +from tests.modules.lib.vterm import VTerm, Dimensions + + +class MutableDimensions(object): + def __init__(self, rows, cols): + super(MutableDimensions, self).__init__() + self._list = [rows, cols] + + def __getitem__(self, idx): + return self._list[idx] + + def __setitem__(self, idx, val): + self._list[idx] = val + + def __iter__(self): + return iter(self._list) + + def __len__(self): + return 2 + + def __nonzero__(self): + return True + + __bool__ = __nonzero__ + + rows = property( + fget = lambda self: self._list[0], + fset = lambda self, val: self._list.__setitem__(0, val), + ) + cols = property( + fget = lambda self: self._list[1], + fset = lambda self, val: self._list.__setitem__(1, val), + ) + + +class ExpectProcess(threading.Thread): + def __init__(self, lib, dim, cmd, args, cwd=None, env=None): + super(ExpectProcess, self).__init__() + self.vterm = VTerm(lib, dim) + self.lock = threading.Lock() + self.dim = Dimensions(*dim) + self.cmd = cmd + self.args = args + self.cwd = cwd + self.env = env + self.buffer = [] + self.child_lock = threading.Lock() + self.shutdown_event = threading.Event() + self.started_event = threading.Event() + + def run(self): + with self.child_lock: + child = pexpect.spawn(self.cmd, self.args, cwd=self.cwd, + env=self.env) + sleep(0.5) + child.setwinsize(self.dim.rows, self.dim.cols) + sleep(0.5) + self.child = child + self.started_event.set() + status = None + while status is None and not self.shutdown_event.is_set(): + try: + with self.child_lock: + s = child.read_nonblocking(size=1024, timeout=0) + status = child.status + except pexpect.TIMEOUT: + pass + except pexpect.EOF: + break + else: + with self.lock: + self.vterm.push(s) + self.buffer.append(s) + + if status is None: + child.kill(SIGKILL) + + def kill(self): + self.shutdown_event.set() + + def resize(self, dim): + with self.child_lock: + self.dim = Dimensions(*dim) + self.child.setwinsize(self.dim.rows, self.dim.cols) + self.vterm.resize(self.dim) + + def __getitem__(self, position): + with self.lock: + return self.vterm.vtscreen[position] + + def read(self): + with self.lock: + ret = b''.join(self.buffer) + del self.buffer[:] + return ret + + def send(self, data): + with self.child_lock: + self.child.send(data) + + def get_highlighted_text(self, text, attrs, default_props=(), + use_escapes=False): + ret = [] + new_attrs = attrs.copy() + for cell_properties, segment_text in text: + if use_escapes: + escapes = ('\033[38;2;{0};{1};{2};48;2;{3};{4};{5}'.format( + *(cell_properties[0] + cell_properties[1]))) + ( + ';1' if cell_properties[2] else '' + ) + ( + ';3' if cell_properties[3] else '' + ) + ( + ';4' if cell_properties[4] else '' + ) + 'm' + ret.append(escapes + segment_text + '\033[0m') + else: + segment_text = segment_text.translate({'{': '{{', '}': '}}'}) + if cell_properties not in new_attrs: + new_attrs[cell_properties] = len(new_attrs) + 1 + props_name = new_attrs[cell_properties] + if props_name in default_props: + ret.append(segment_text) + else: + ret.append('{' + str(props_name) + ':' + segment_text + '}') + return ''.join(ret), new_attrs + + def get_row(self, row, attrs, default_props=(), use_escapes=False): + with self.lock: + return self.get_highlighted_text(( + (key, ''.join((cell.text for cell in subline))) + for key, subline in groupby(( + self.vterm.vtscreen[row, col] + for col in range(self.dim.cols) + ), lambda cell: cell.cell_properties_key) + ), attrs, default_props, use_escapes) + + def get_screen(self, attrs, default_props=(), use_escapes=False): + lines = [] + for row in range(self.dim.rows): + line, attrs = self.get_row(row, attrs, default_props, use_escapes) + lines.append(line) + return '\n'.join(lines), attrs + + +def test_expected_result(p, test, last_attempt, last_attempt_cb, attempts): + debugging_tests = not not os.environ.get('_POWERLINE_DEBUGGING_TESTS') + expected_text, attrs = test['expected_result'] + result = None + while attempts: + if 'row' in test: + row = test['row'] + else: + row = p.dim.rows - 1 + while row >= 0 and not p[row, 0].text: + row -= 1 + if row < 0: + row = 0 + actual_text, all_attrs = p.get_row(row, attrs) + if actual_text == expected_text: + return True + attempts -= 1 + print('Actual result does not match expected for row {0}. Attempts left: {1}.'.format( + row, attempts)) + sleep(2) + print('Result (row {0}):'.format(row)) + print(actual_text) + print('Expected:') + print(expected_text) + print('Attributes:') + for v, k in sorted( + ((v, k) for k, v in all_attrs.items()), + key=(lambda t: '%02u'.format(t[0]) if isinstance(t[0], int) else t[0]), + ): + print('{k!r}: {v!r},'.format(v=v, k=k)) + print('Screen:') + screen, screen_attrs = p.get_screen(attrs, use_escapes=debugging_tests) + print(screen) + print(screen_attrs) + print('_' * 80) + print('Diff:') + print('=' * 80) + print(''.join(( + u(line) for line in ndiff([actual_text + '\n'], [expected_text + '\n'])) + )) + if last_attempt and last_attempt_cb: + last_attempt_cb() + return False + + +ENV_BASE = { + # Reasoning: + # 1. vt* TERMs (used to be vt100 here) make tmux-1.9 use different and + # identical colors for inactive windows. This is not like tmux-1.6: + # foreground color is different from separator color and equal to (0, + # 102, 153) for some reason (separator has correct color). tmux-1.8 is + # fine, so are older versions (though tmux-1.6 and tmux-1.7 do not have + # highlighting for previously active window) and my system tmux-1.9a. + # 2. screen, xterm and some other non-256color terminals both have the same + # issue and make libvterm emit complains like `Unhandled CSI SGR 3231`. + # 3. screen-256color, xterm-256color and other -256color terminals make + # libvterm emit complains about unhandled escapes to stderr. + # 4. `st-256color` does not have any of the above problems, but it may be + # not present on the target system because it is installed with + # x11-terms/st and not with sys-libs/ncurses. + # + # For the given reasons decision was made: to fix tmux-1.9 tests and not + # make libvterm emit any data to stderr st-256color $TERM should be used, up + # until libvterm has its own terminfo database entry (if it ever will). To + # make sure that relevant terminfo entry is present on the target system it + # should be distributed with powerline test package. To make distribution + # not require modifying anything outside of powerline test directory + # TERMINFO variable is set. + # + # This fix propagates to non-tmux vterm tests just in case. + 'TERM': 'st-256color', + # Also $TERMINFO definition in get_env + + 'POWERLINE_CONFIG_PATHS': os.path.abspath('powerline/config_files'), + 'POWERLINE_COMMAND': 'powerline-render', + 'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH', ''), + 'PYTHONPATH': os.environ.get('PYTHONPATH', ''), +} + + +def get_env(vterm_path, test_dir, *args, **kwargs): + env = ENV_BASE.copy() + env.update({ + 'TERMINFO': os.path.join(test_dir, 'terminfo'), + 'PATH': vterm_path, + 'SHELL': os.path.join(vterm_path, 'bash'), + }) + env.update(*args, **kwargs) + return env + + +def do_terminal_tests(tests, cmd, dim, args, env, suite, cwd=None, fin_cb=None, + last_attempt_cb=None, attempts=None): + debugging_tests = not not os.environ.get('_POWERLINE_DEBUGGING_TESTS') + default_attempts = 2 if debugging_tests else 3 + if attempts is None: + attempts = default_attempts + lib = os.environ.get('POWERLINE_LIBVTERM') + if not lib: + if os.path.exists('tests/bot-ci/deps/libvterm/libvterm.so'): + lib = 'tests/bot-ci/deps/libvterm/libvterm.so' + else: + lib = 'libvterm.so' + + while attempts: + try: + p = ExpectProcess( + lib=lib, + dim=dim, + cmd=cmd, + args=args, + cwd=cwd, + env=env, + ) + p.start() + p.started_event.wait() + + ret = True + + for i, test in enumerate(tests): + with suite.test(test.get('name', 'test_{0}'.format(i)), + attempts - 1) as ptest: + try: + test_prep = test['prep_cb'] + except KeyError: + pass + else: + test_prep(p) + test_result = test_expected_result( + p, test, attempts == 0, last_attempt_cb, + test.get('attempts', default_attempts) + ) + if not test_result: + ptest.fail('Result does not match expected') + ret = ret and test_result + + if ret: + return ret + finally: + if fin_cb: + fin_cb(p=p, cmd=cmd, env=env) + p.kill() + p.join(10) + assert(not p.isAlive()) + + attempts -= 1 + + return False diff --git a/tests/modules/lib/vterm.py b/tests/modules/lib/vterm.py new file mode 100644 index 0000000..1984e1b --- /dev/null +++ b/tests/modules/lib/vterm.py @@ -0,0 +1,193 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import ctypes + +from collections import namedtuple + +from powerline.lib.unicode import unicode, unichr, tointiter + + +Dimensions = namedtuple('Dimensions', ('rows', 'cols')) + + +class CTypesFunction(object): + def __init__(self, library, name, rettype, args): + self.name = name + self.prototype = ctypes.CFUNCTYPE(rettype, *[ + arg[1] for arg in args + ]) + self.args = args + self.func = self.prototype((name, library), tuple(( + (1, arg[0]) for arg in args + ))) + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + def __repr__(self): + return '{cls}(<library>, {name!r}, {rettype!r}, {args!r})'.format( + cls=self.__class__.__name__, + **self.__dict__ + ) + + +class CTypesLibraryFuncsCollection(object): + def __init__(self, lib, **kwargs): + self.lib = lib + library_loader = ctypes.LibraryLoader(ctypes.CDLL) + library = library_loader.LoadLibrary(lib) + self.library = library + for name, args in kwargs.items(): + self.__dict__[name] = CTypesFunction(library, name, *args) + + +class VTermPos_s(ctypes.Structure): + _fields_ = ( + ('row', ctypes.c_int), + ('col', ctypes.c_int), + ) + + +class VTermColor_s(ctypes.Structure): + _fields_ = ( + ('red', ctypes.c_uint8), + ('green', ctypes.c_uint8), + ('blue', ctypes.c_uint8), + ) + + +class VTermScreenCellAttrs_s(ctypes.Structure): + _fields_ = ( + ('bold', ctypes.c_uint, 1), + ('underline', ctypes.c_uint, 2), + ('italic', ctypes.c_uint, 1), + ('blink', ctypes.c_uint, 1), + ('reverse', ctypes.c_uint, 1), + ('strike', ctypes.c_uint, 1), + ('font', ctypes.c_uint, 4), + ('dwl', ctypes.c_uint, 1), + ('dhl', ctypes.c_uint, 2), + ) + + +VTERM_MAX_CHARS_PER_CELL = 6 + + +class VTermScreenCell_s(ctypes.Structure): + _fields_ = ( + ('chars', ctypes.ARRAY(ctypes.c_uint32, VTERM_MAX_CHARS_PER_CELL)), + ('width', ctypes.c_char), + ('attrs', VTermScreenCellAttrs_s), + ('fg', VTermColor_s), + ('bg', VTermColor_s), + ) + + +VTerm_p = ctypes.c_void_p +VTermScreen_p = ctypes.c_void_p + + +def get_functions(lib): + return CTypesLibraryFuncsCollection( + lib, + vterm_new=(VTerm_p, ( + ('rows', ctypes.c_int), + ('cols', ctypes.c_int) + )), + vterm_obtain_screen=(VTermScreen_p, (('vt', VTerm_p),)), + vterm_set_size=(None, ( + ('vt', VTerm_p), + ('rows', ctypes.c_int), + ('cols', ctypes.c_int) + )), + vterm_screen_reset=(None, ( + ('screen', VTermScreen_p), + ('hard', ctypes.c_int) + )), + vterm_input_write=(ctypes.c_size_t, ( + ('vt', VTerm_p), + ('bytes', ctypes.POINTER(ctypes.c_char)), + ('size', ctypes.c_size_t), + )), + vterm_screen_get_cell=(ctypes.c_int, ( + ('screen', VTermScreen_p), + ('pos', VTermPos_s), + ('cell', ctypes.POINTER(VTermScreenCell_s)) + )), + vterm_free=(None, (('vt', VTerm_p),)), + vterm_set_utf8=(None, (('vt', VTerm_p), ('is_utf8', ctypes.c_int))), + ) + + +class VTermColor(object): + __slots__ = ('red', 'green', 'blue') + + def __init__(self, color): + self.red = color.red + self.green = color.green + self.blue = color.blue + + @property + def color_key(self): + return (self.red, self.green, self.blue) + + +class VTermScreenCell(object): + def __init__(self, vtsc): + for field in VTermScreenCellAttrs_s._fields_: + field_name = field[0] + setattr(self, field_name, getattr(vtsc.attrs, field_name)) + self.text = ''.join(( + unichr(vtsc.chars[i]) for i in range(VTERM_MAX_CHARS_PER_CELL) + )).rstrip('\x00') + self.width = next(tointiter(vtsc.width)) + self.fg = VTermColor(vtsc.fg) + self.bg = VTermColor(vtsc.bg) + self.cell_properties_key = ( + self.fg.color_key, + self.bg.color_key, + self.bold, + self.underline, + self.italic, + ) + + +class VTermScreen(object): + def __init__(self, functions, screen): + self.functions = functions + self.screen = screen + + def __getitem__(self, position): + pos = VTermPos_s(*position) + cell = VTermScreenCell_s() + ret = self.functions.vterm_screen_get_cell(self.screen, pos, cell) + if ret != 1: + raise ValueError('vterm_screen_get_cell returned {0}'.format(ret)) + return VTermScreenCell(cell) + + def reset(self, hard): + self.functions.vterm_screen_reset(self.screen, int(bool(hard))) + + +class VTerm(object): + def __init__(self, lib, dim): + self.functions = get_functions(lib) + self.vt = self.functions.vterm_new(dim.rows, dim.cols) + self.functions.vterm_set_utf8(self.vt, 1) + self.vtscreen = VTermScreen(self.functions, self.functions.vterm_obtain_screen(self.vt)) + self.vtscreen.reset(True) + + def push(self, data): + if isinstance(data, unicode): + data = data.encode('utf-8') + return self.functions.vterm_input_write(self.vt, data, len(data)) + + def resize(self, dim): + self.functions.vterm_set_size(self.vt, dim.rows, dim.cols) + + def __del__(self): + try: + self.functions.vterm_free(self.vt) + except AttributeError: + pass diff --git a/tests/modules/matchers.py b/tests/modules/matchers.py new file mode 100644 index 0000000..e905de3 --- /dev/null +++ b/tests/modules/matchers.py @@ -0,0 +1,6 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + + +def always_true(matcher_info): + return True diff --git a/tests/modules/vim.py b/tests/modules/vim.py new file mode 100644 index 0000000..3f6882c --- /dev/null +++ b/tests/modules/vim.py @@ -0,0 +1,927 @@ +# vim:fileencoding=utf-8:noet +_log = [] +vars = {} +vvars = {'version': 703} +_tabpage = 0 +_mode = 'n' +_buf_purge_events = set() +options = { + 'paste': 0, + 'ambiwidth': 'single', + 'columns': 80, + 'encoding': 'utf-8', +} +_last_bufnr = 0 +_highlights = {} +from collections import defaultdict as _defaultdict +_environ = _defaultdict(lambda: '') +del _defaultdict + + +_thread_id = None + + +def _set_thread_id(): + global _thread_id + from threading import current_thread + _thread_id = current_thread().ident + + +# Assuming import is done from the main thread +_set_thread_id() + + +def _print_log(): + for item in _log: + print (item) + _log[:] = () + + +def _vim(func): + from functools import wraps + from threading import current_thread + + @wraps(func) + def f(*args, **kwargs): + global _thread_id + if _thread_id != current_thread().ident: + raise RuntimeError('Accessing vim from separate threads is not allowed') + _log.append((func.__name__, args)) + return func(*args, **kwargs) + + return f + + +def _unicode(func): + from functools import wraps + import sys + + if sys.version_info < (3,): + return func + + @wraps(func) + def f(*args, **kwargs): + from powerline.lib.unicode import u + ret = func(*args, **kwargs) + if isinstance(ret, bytes): + ret = u(ret) + return ret + + return f + + +class _Buffers(object): + @_vim + def __init__(self): + self.d = {} + + @_vim + def __len__(self): + return len(self.d) + + @_vim + def __getitem__(self, item): + return self.d[item] + + @_vim + def __setitem__(self, item, value): + self.d[item] = value + + @_vim + def __iter__(self): + return iter(self.d.values()) + + @_vim + def __contains__(self, item): + return item in self.d + + @_vim + def _keys(self): + return self.d.keys() + + @_vim + def _pop(self, *args, **kwargs): + return self.d.pop(*args, **kwargs) + + +buffers = _Buffers() + + +class _ObjList(object): + @_vim + def __init__(self, objtype): + self.l = [] + self.objtype = objtype + + @_vim + def __getitem__(self, item): + return self.l[item - int(item > 0)] + + @_vim + def __len__(self): + return len(self.l) + + @_vim + def __iter__(self): + return iter(self.l) + + @_vim + def _pop(self, idx): + obj = self.l.pop(idx - 1) + for moved_obj in self.l[idx - 1:]: + moved_obj.number -= 1 + return obj + + @_vim + def _append(self, *args, **kwargs): + return self.l.append(*args, **kwargs) + + @_vim + def _new(self, *args, **kwargs): + number = len(self) + 1 + new_obj = self.objtype(number, *args, **kwargs) + self._append(new_obj) + return new_obj + + +def _construct_result(r): + import sys + if sys.version_info < (3,): + return r + else: + if isinstance(r, str): + return r.encode('utf-8') + elif isinstance(r, list): + return [_construct_result(i) for i in r] + elif isinstance(r, dict): + return dict(( + (_construct_result(k), _construct_result(v)) + for k, v in r.items() + )) + return r + + +def _str_func(func): + from functools import wraps + + @wraps(func) + def f(*args, **kwargs): + return _construct_result(func(*args, **kwargs)) + return f + + +def _log_print(): + import sys + for entry in _log: + sys.stdout.write(repr(entry) + '\n') + + +_current_group = None +_on_wipeout = [] + + +@_vim +def command(cmd): + global _current_group + cmd = cmd.lstrip() + if cmd.startswith('let g:'): + import re + varname, value = re.compile(r'^let g:(\w+)\s*=\s*(.*)').match(cmd).groups() + vars[varname] = value + elif cmd.startswith('hi '): + sp = cmd.split() + _highlights[sp[1]] = sp[2:] + elif cmd.startswith('augroup'): + augroup = cmd.partition(' ')[2] + if augroup.upper() == 'END': + _current_group = None + else: + _current_group = augroup + elif cmd.startswith('autocmd'): + rest = cmd.partition(' ')[2] + auevent, rest = rest.partition(' ')[::2] + pattern, aucmd = rest.partition(' ')[::2] + if auevent != 'BufWipeout' or pattern != '*': + raise NotImplementedError + import sys + if sys.version_info < (3,): + if not aucmd.startswith(':python '): + raise NotImplementedError + else: + if not aucmd.startswith(':python3 '): + raise NotImplementedError + _on_wipeout.append(aucmd.partition(' ')[2]) + elif cmd.startswith('set '): + if cmd.startswith('set statusline='): + options['statusline'] = cmd[len('set statusline='):] + elif cmd.startswith('set tabline='): + options['tabline'] = cmd[len('set tabline='):] + else: + raise NotImplementedError(cmd) + else: + raise NotImplementedError(cmd) + + +@_vim +@_unicode +def eval(expr): + if expr.startswith('g:'): + return vars[expr[2:]] + elif expr.startswith('v:'): + return vvars[expr[2:]] + elif expr.startswith('&'): + return options[expr[1:]] + elif expr.startswith('$'): + return _environ[expr[1:]] + elif expr.startswith('PowerlineRegisterCachePurgerEvent'): + _buf_purge_events.add(expr[expr.find('"') + 1:expr.rfind('"') - 1]) + return '0' + elif expr.startswith('exists('): + return '0' + elif expr.startswith('getwinvar('): + import re + match = re.match(r'^getwinvar\((\d+), "(\w+)"\)$', expr) + if not match: + raise NotImplementedError(expr) + winnr = int(match.group(1)) + varname = match.group(2) + return _emul_getwinvar(winnr, varname) + elif expr.startswith('has_key('): + import re + match = re.match(r'^has_key\(getwinvar\((\d+), ""\), "(\w+)"\)$', expr) + if match: + winnr = int(match.group(1)) + varname = match.group(2) + return 0 + (varname in current.tabpage.windows[winnr].vars) + else: + match = re.match(r'^has_key\(gettabwinvar\((\d+), (\d+), ""\), "(\w+)"\)$', expr) + if not match: + raise NotImplementedError(expr) + tabnr = int(match.group(1)) + winnr = int(match.group(2)) + varname = match.group(3) + return 0 + (varname in tabpages[tabnr].windows[winnr].vars) + elif expr == 'getbufvar("%", "NERDTreeRoot").path.str()': + import os + assert os.path.basename(current.buffer.name).startswith('NERD_tree_') + return '/usr/include' + elif expr.startswith('getbufvar('): + import re + match = re.match(r'^getbufvar\((\d+), ["\'](.+)["\']\)$', expr) + if not match: + raise NotImplementedError(expr) + bufnr = int(match.group(1)) + varname = match.group(2) + return _emul_getbufvar(bufnr, varname) + elif expr == 'tabpagenr()': + return current.tabpage.number + elif expr == 'tabpagenr("$")': + return len(tabpages) + elif expr.startswith('tabpagewinnr('): + tabnr = int(expr[len('tabpagewinnr('):-1]) + return tabpages[tabnr].window.number + elif expr.startswith('tabpagebuflist('): + import re + match = re.match(r'tabpagebuflist\((\d+)\)\[(\d+)\]', expr) + tabnr = int(match.group(1)) + winnr = int(match.group(2)) + 1 + return tabpages[tabnr].windows[winnr].buffer.number + elif expr.startswith('gettabwinvar('): + import re + match = re.match(r'gettabwinvar\((\d+), (\d+), "(\w+)"\)', expr) + tabnr = int(match.group(1)) + winnr = int(match.group(2)) + varname = match.group(3) + return tabpages[tabnr].windows[winnr].vars[varname] + elif expr.startswith('type(function('): + import re + match = re.match(r'^type\(function\("([^"]+)"\)\) == 2$', expr) + if not match: + raise NotImplementedError(expr) + return 0 + raise NotImplementedError(expr) + + +@_vim +def bindeval(expr): + if expr == 'g:': + return vars + elif expr == '{}': + return {} + elif expr == '[]': + return [] + import re + match = re.compile(r'^function\("([^"\\]+)"\)$').match(expr) + if match: + return globals()['_emul_' + match.group(1)] + else: + raise NotImplementedError + + +@_vim +@_str_func +def _emul_mode(*args): + if args and args[0]: + return _mode + else: + return _mode[0] + + +@_vim +@_str_func +def _emul_getbufvar(bufnr, varname): + import re + if varname[0] == '&': + if bufnr == '%': + bufnr = current.buffer.number + if bufnr not in buffers: + return '' + try: + return buffers[bufnr].options[varname[1:]] + except KeyError: + try: + return options[varname[1:]] + except KeyError: + return '' + elif re.match('^[a-zA-Z_]+$', varname): + if bufnr == '%': + bufnr = current.buffer.number + if bufnr not in buffers: + return '' + return buffers[bufnr].vars[varname] + raise NotImplementedError + + +@_vim +@_str_func +def _emul_getwinvar(winnr, varname): + return current.tabpage.windows[winnr].vars.get(varname, '') + + +@_vim +def _emul_setwinvar(winnr, varname, value): + current.tabpage.windows[winnr].vars[varname] = value + + +@_vim +def _emul_virtcol(expr): + if expr == '.': + return current.window.cursor[1] + 1 + if isinstance(expr, list) and len(expr) == 3: + return expr[-2] + expr[-1] + raise NotImplementedError + + +_v_pos = None + + +@_vim +def _emul_getpos(expr): + if expr == '.': + return [0, current.window.cursor[0] + 1, current.window.cursor[1] + 1, 0] + if expr == 'v': + return _v_pos or [0, current.window.cursor[0] + 1, current.window.cursor[1] + 1, 0] + raise NotImplementedError + + +@_vim +@_str_func +def _emul_fnamemodify(path, modstring): + import os + _modifiers = { + '~': lambda path: path.replace(os.environ['HOME'].encode('utf-8'), b'~') if path.startswith(os.environ['HOME'].encode('utf-8')) else path, + '.': lambda path: (lambda tpath: path if tpath[:3] == b'..' + os.sep.encode() else tpath)(os.path.relpath(path)), + 't': lambda path: os.path.basename(path), + 'h': lambda path: os.path.dirname(path), + } + + for mods in modstring.split(':')[1:]: + path = _modifiers[mods](path) + return path + + +@_vim +@_str_func +def _emul_expand(expr): + global _abuf + if expr == '<abuf>': + return _abuf or current.buffer.number + raise NotImplementedError + + +@_vim +def _emul_bufnr(expr): + if expr == '$': + return _last_bufnr + raise NotImplementedError + + +@_vim +def _emul_exists(ident): + if ident.startswith('g:'): + return ident[2:] in vars + elif ident.startswith(':'): + return 0 + raise NotImplementedError + + +@_vim +def _emul_line2byte(line): + buflines = current.buffer._buf_lines + if line == len(buflines) + 1: + return sum((len(s) for s in buflines)) + 1 + raise NotImplementedError + + +@_vim +def _emul_line(expr): + cursorline = current.window.cursor[0] + 1 + numlines = len(current.buffer._buf_lines) + if expr == 'w0': + return max(cursorline - 5, 1) + if expr == 'w$': + return min(cursorline + 5, numlines) + raise NotImplementedError + + +@_vim +@_str_func +def _emul_strtrans(s): + # FIXME Do more replaces + return s.replace(b'\xFF', b'<ff>') + + +@_vim +@_str_func +def _emul_bufname(bufnr): + try: + return buffers[bufnr]._name or b'' + except KeyError: + return b'' + + +_window_id = 0 + + +class _Window(object): + def __init__(self, number, buffer=None, cursor=(1, 0), width=80): + global _window_id + self.cursor = cursor + self.width = width + self.number = number + if buffer: + if type(buffer) is _Buffer: + self.buffer = buffer + else: + self.buffer = _Buffer(**buffer) + else: + self.buffer = _Buffer() + _window_id += 1 + self._window_id = _window_id + self.options = {} + self.vars = { + 'powerline_window_id': self._window_id, + } + + def __repr__(self): + return '<window ' + str(self.number - 1) + '>' + + +class _Tabpage(object): + def __init__(self, number): + self.windows = _ObjList(_Window) + self.number = number + + def _new_window(self, **kwargs): + self.window = self.windows._new(**kwargs) + return self.window + + def _close_window(self, winnr, open_window=True): + curwinnr = self.window.number + win = self.windows._pop(winnr) + if self.windows and winnr == curwinnr: + self.window = self.windows[-1] + elif open_window: + current.tabpage._new_window() + return win + + def _close(self): + global _tabpage + while self.windows: + self._close_window(1, False) + tabpages._pop(self.number) + _tabpage = len(tabpages) + + +tabpages = _ObjList(_Tabpage) + + +_abuf = None + + +class _Buffer(object): + def __init__(self, name=None): + global _last_bufnr + _last_bufnr += 1 + bufnr = _last_bufnr + self.number = bufnr + # FIXME Use unicode() for python-3 + self.name = name + self.vars = {'changedtick': 1} + self.options = { + 'modified': 0, + 'readonly': 0, + 'fileformat': 'unix', + 'filetype': '', + 'buftype': '', + 'fileencoding': 'utf-8', + 'textwidth': 80, + } + self._buf_lines = [''] + self._undostate = [self._buf_lines[:]] + self._undo_written = len(self._undostate) + buffers[bufnr] = self + + @property + def name(self): + import sys + if sys.version_info < (3,): + return self._name + else: + return str(self._name, 'utf-8') if self._name else None + + @name.setter + def name(self, name): + if name is None: + self._name = None + else: + import os + if type(name) is not bytes: + name = name.encode('utf-8') + if b':/' in name: + self._name = name + else: + self._name = os.path.abspath(name) + + def __getitem__(self, line): + return self._buf_lines[line] + + def __setitem__(self, line, value): + self.options['modified'] = 1 + self.vars['changedtick'] += 1 + self._buf_lines[line] = value + from copy import copy + self._undostate.append(copy(self._buf_lines)) + + def __setslice__(self, *args): + self.options['modified'] = 1 + self.vars['changedtick'] += 1 + self._buf_lines.__setslice__(*args) + from copy import copy + self._undostate.append(copy(self._buf_lines)) + + def __getslice__(self, *args): + return self._buf_lines.__getslice__(*args) + + def __len__(self): + return len(self._buf_lines) + + def __repr__(self): + return '<buffer ' + str(self.name) + '>' + + def __del__(self): + global _abuf + bufnr = self.number + try: + import __main__ + except ImportError: + pass + except RuntimeError: + # Module may have already been garbage-collected + pass + else: + if _on_wipeout: + _abuf = bufnr + try: + for event in _on_wipeout: + exec(event, __main__.__dict__) + finally: + _abuf = None + + +class _Current(object): + @property + def buffer(self): + return self.window.buffer + + @property + def window(self): + return self.tabpage.window + + @property + def tabpage(self): + return tabpages[_tabpage - 1] + + +current = _Current() + + +_dict = None + + +@_vim +def _init(): + global _dict + + if _dict: + return _dict + + _dict = {} + for varname, value in globals().items(): + if varname[0] != '_': + _dict[varname] = value + _tabnew() + return _dict + + +@_vim +def _get_segment_info(): + mode_translations = { + chr(ord('V') - 0x40): '^V', + chr(ord('S') - 0x40): '^S', + } + mode = _mode + mode = mode_translations.get(mode, mode) + window = current.window + buffer = current.buffer + tabpage = current.tabpage + return { + 'window': window, + 'winnr': window.number, + 'buffer': buffer, + 'bufnr': buffer.number, + 'tabpage': tabpage, + 'tabnr': tabpage.number, + 'window_id': window._window_id, + 'mode': mode, + 'encoding': options['encoding'], + } + + +@_vim +def _launch_event(event): + pass + + +@_vim +def _start_mode(mode): + global _mode + if mode == 'i': + _launch_event('InsertEnter') + elif _mode == 'i': + _launch_event('InsertLeave') + _mode = mode + + +@_vim +def _undo(): + if len(current.buffer._undostate) == 1: + return + buffer = current.buffer + buffer._undostate.pop(-1) + buffer._buf_lines = buffer._undostate[-1] + if buffer._undo_written == len(buffer._undostate): + buffer.options['modified'] = 0 + + +@_vim +def _edit(name=None): + if current.buffer.name is None: + buffer = current.buffer + buffer.name = name + else: + buffer = _Buffer(name) + current.window.buffer = buffer + + +@_vim +def _tabnew(name=None): + global windows + global _tabpage + tabpage = tabpages._new() + windows = tabpage.windows + _tabpage = len(tabpages) + _new(name) + return tabpage + + +@_vim +def _new(name=None): + current.tabpage._new_window(buffer={'name': name}) + + +@_vim +def _split(): + current.tabpage._new_window(buffer=current.buffer) + + +@_vim +def _close(winnr, wipe=True): + win = current.tabpage._close_window(winnr) + if wipe: + for w in current.tabpage.windows: + if w.buffer.number == win.buffer.number: + break + else: + _bw(win.buffer.number) + + +@_vim +def _bw(bufnr=None): + bufnr = bufnr or current.buffer.number + winnr = 1 + for win in current.tabpage.windows: + if win.buffer.number == bufnr: + _close(winnr, wipe=False) + winnr += 1 + buffers._pop(bufnr) + if not buffers: + _Buffer() + _b(max(buffers._keys())) + + +@_vim +def _b(bufnr): + current.window.buffer = buffers[bufnr] + + +@_vim +def _set_cursor(line, col): + current.window.cursor = (line, col) + if _mode == 'n': + _launch_event('CursorMoved') + elif _mode == 'i': + _launch_event('CursorMovedI') + + +@_vim +def _get_buffer(): + return current.buffer + + +@_vim +def _set_bufoption(option, value, bufnr=None): + buffers[bufnr or current.buffer.number].options[option] = value + if option == 'filetype': + _launch_event('FileType') + + +class _WithNewBuffer(object): + def __init__(self, func, *args, **kwargs): + self.call = lambda: func(*args, **kwargs) + + def __enter__(self): + self.call() + self.bufnr = current.buffer.number + return _get_segment_info() + + def __exit__(self, *args): + _bw(self.bufnr) + + +@_vim +def _set_dict(d, new, setfunc=None): + if not setfunc: + def setfunc(k, v): + d[k] = v + + old = {} + na = [] + for k, v in new.items(): + try: + old[k] = d[k] + except KeyError: + na.append(k) + setfunc(k, v) + return old, na + + +class _WithBufOption(object): + def __init__(self, **new): + self.new = new + + def __enter__(self): + self.buffer = current.buffer + self.old = _set_dict(self.buffer.options, self.new, _set_bufoption)[0] + + def __exit__(self, *args): + self.buffer.options.update(self.old) + + +class _WithMode(object): + def __init__(self, new): + self.new = new + + def __enter__(self): + self.old = _mode + _start_mode(self.new) + return _get_segment_info() + + def __exit__(self, *args): + _start_mode(self.old) + + +class _WithDict(object): + def __init__(self, d, **new): + self.new = new + self.d = d + + def __enter__(self): + self.old, self.na = _set_dict(self.d, self.new) + + def __exit__(self, *args): + self.d.update(self.old) + for k in self.na: + self.d.pop(k) + + +class _WithSplit(object): + def __enter__(self): + _split() + + def __exit__(self, *args): + _close(2, wipe=False) + + +class _WithBufName(object): + def __init__(self, new): + self.new = new + + def __enter__(self): + import os + buffer = current.buffer + self.buffer = buffer + self.old = buffer.name + buffer.name = self.new + + def __exit__(self, *args): + self.buffer.name = self.old + + +class _WithNewTabPage(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + def __enter__(self): + self.tab = _tabnew(*self.args, **self.kwargs) + + def __exit__(self, *args): + self.tab._close() + + +class _WithGlobal(object): + def __init__(self, **kwargs): + self.kwargs = kwargs + + def __enter__(self): + self.empty = object() + self.old = dict(((key, globals().get(key, self.empty)) for key in self.kwargs)) + globals().update(self.kwargs) + + def __exit__(self, *args): + for k, v in self.old.items(): + if v is self.empty: + globals().pop(k, None) + else: + globals()[k] = v + + +@_vim +def _with(key, *args, **kwargs): + if key == 'buffer': + return _WithNewBuffer(_edit, *args, **kwargs) + elif key == 'bufname': + return _WithBufName(*args, **kwargs) + elif key == 'mode': + return _WithMode(*args, **kwargs) + elif key == 'bufoptions': + return _WithBufOption(**kwargs) + elif key == 'options': + return _WithDict(options, **kwargs) + elif key == 'globals': + return _WithDict(vars, **kwargs) + elif key == 'wvars': + return _WithDict(current.window.vars, **kwargs) + elif key == 'environ': + return _WithDict(_environ, **kwargs) + elif key == 'split': + return _WithSplit() + elif key == 'tabpage': + return _WithNewTabPage(*args, **kwargs) + elif key == 'vpos': + return _WithGlobal(_v_pos=[0, kwargs['line'], kwargs['col'], kwargs['off']]) + + +class error(Exception): + pass diff --git a/tests/shlib/common.sh b/tests/shlib/common.sh new file mode 100644 index 0000000..6330262 --- /dev/null +++ b/tests/shlib/common.sh @@ -0,0 +1,150 @@ +: ${USER:=`id -un`} +: ${HOME:=`getent passwd $USER | cut -d: -f6`} + +if test -z "${PYTHON}" ; then + if test -n "$USE_UCS2_PYTHON" ; then + LD_LIBRARY_PATH="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/lib${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}" + fi +fi + +export LD_LIBRARY_PATH +export USER +export HOME + +if test -n "$USE_UCS2_PYTHON" ; then + POWERLINE_VIRTUALENV="cpython-ucs2-$UCS2_PYTHON_VARIANT" + PYTHON="$HOME/.virtualenvs/$POWERLINE_VIRTUALENV/bin/python" + if test -n "$BASH_VERSION" ; then + set +e + . virtualenvwrapper.sh + workon "$POWERLINE_VIRTUALENV" + set -e + fi +fi + +. tests/bot-ci/scripts/common/main.sh silent + +export USER HOME + +if test -z "$FAILED" ; then + FAILED=0 + + FAIL_SUMMARY="" + + TMP_ROOT="$ROOT/tests/tmp" + export FAILURES_FILE="$ROOT/tests/status" +fi + +ANSI_CLEAR="\033[0K" + +travis_fold() { + local action="$1" + local name="$2" + name="$(echo -n "$name" | tr '\n\0' '--' | sed -r 's/[^A-Za-z0-9]+/-/g')" + name="$(echo -n "$name" | sed -r 's/-$//')" + echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}" +} + +print_environ() { + echo "Using $PYTHON_IMPLEMENTATION version $PYTHON_VERSION." + echo "Path to Python executable: $PYTHON." + echo "Root: $ROOT." + echo "Branch: $BRANCH_NAME." + echo "sys.path:" + "$PYTHON" -c "for path in __import__('sys').path: print(' %r' % path)" +} + +enter_suite() { + set +x + local suite_name="$1" ; shift + local final="$1" + export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE}/$suite_name" + travis_fold start "$POWERLINE_CURRENT_SUITE" + print_environ + if test "$final" = final ; then + if test -n "$POWERLINE_SUITE_FINAL" ; then + fail __suite__/enter/final E "Final suites do not allow nesting" + fi + export POWERLINE_SUITE_FINAL=1 + # set -x + fi +} + +exit_suite() { + if test "$POWERLINE_CURRENT_SUITE" = "$POWERLINE_TMP_DIR_SUITE" ; then + rm_test_root + fi + if test $FAILED -ne 0 ; then + echo "Suite ${POWERLINE_CURRENT_SUITE} failed, summary:" + echo "${FAIL_SUMMARY}" + fi + set +x + travis_fold end "$POWERLINE_CURRENT_SUITE" + export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE%/*}" + if test "$1" != "--continue" ; then + exit $FAILED + else + unset POWERLINE_SUITE_FINAL + fi +} + +_fail() { + local allow_failure= + if test "$1" = "--allow-failure" ; then + shift + allow_failure=A + fi + local test_name="$1" ; shift + local fail_char="$allow_failure$1" ; shift + local message="$1" ; shift + local verb="$1" ; shift + local full_msg="$fail_char $POWERLINE_CURRENT_SUITE|$test_name :: $message" + FAIL_SUMMARY="${FAIL_SUMMARY}${NL}${full_msg}" + echo "$verb: $full_msg" + echo "$full_msg" >> "$FAILURES_FILE" + if test -z "$allow_failure" ; then + FAILED=1 + fi +} + +fail() { + _fail "$@" "Failed" +} + +skip() { + local test_name="$1" ; shift + local message="$1" ; shift + _fail --allow-failure "$test_name" S "$message" "Skipped" +} + +make_test_root() { + local suffix="${POWERLINE_CURRENT_SUITE##*/}" + + local tmpdir="$TMP_ROOT/$suffix/" + export POWERLINE_TMP_DIR_SUITE="$POWERLINE_CURRENT_SUITE" + + if test -d "$tmpdir" ; then + rm -r "$tmpdir" + fi + + mkdir -p "$tmpdir" + + export TEST_ROOT="$tmpdir" +} + +rm_test_root() { + if test -e "$FAILURES_FILE" ; then + return 0 + fi + local suffix="${POWERLINE_CURRENT_SUITE##*/}" + if test -d "$TMP_ROOT/$suffix" ; then + rm -r "$TMP_ROOT/$suffix" + rmdir "$TMP_ROOT" &>/dev/null || true + fi +} + +if ! which realpath ; then + realpath() { + $PYTHON -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$1" + } +fi diff --git a/tests/shlib/vim.sh b/tests/shlib/vim.sh new file mode 100644 index 0000000..49346da --- /dev/null +++ b/tests/shlib/vim.sh @@ -0,0 +1,33 @@ +. tests/bot-ci/scripts/common/main.sh + +if test -z "$POWERLINE_VIM_EXE" ; then + if test -n "$USE_UCS2_PYTHON" ; then + NEW_VIM="$ROOT/tests/bot-ci/deps/vim/master-$UCS2_PYTHON_VARIANT-ucs2-double/vim" + OLD_VIM="$ROOT/tests/bot-ci/deps/vim/v7.0.112-$UCS2_PYTHON_VARIANT-ucs2/vim" + opt_dir="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT" + main_path="$opt_dir/lib/python$UCS2_PYTHON_VARIANT" + site_path="$main_path/site-packages" + venv_main_path="$VIRTUAL_ENV/lib/python$UCS2_PYTHON_VARIANT" + venv_site_path="$venv_main_path/site-packages" + new_paths="${main_path}:${site_path}:${venv_main_path}:${venv_site_path}" + export PYTHONPATH="$new_paths${PYTHONPATH:+:}$PYTHONPATH" + else + if test "$PYTHON_IMPLEMENTATION" != "CPython" ; then + exit 0 + fi + if test -d "$ROOT/tests/bot-ci/deps" ; then + NEW_VIM="$ROOT/tests/bot-ci/deps/vim/master-$PYTHON_MM/vim" + OLD_VIM="$ROOT/tests/bot-ci/deps/vim/v7.0.112-$PYTHON_MM/vim" + else + NEW_VIM="vim" + fi + if test -e "$OLD_VIM" ; then + VIMS="NEW_VIM OLD_VIM" + else + VIMS="NEW_VIM" + fi + fi +else + NEW_VIM="$POWERLINE_VIM_EXE" + OLD_VIM="$POWERLINE_VIM_EXE" +fi diff --git a/tests/shlib/vterm.sh b/tests/shlib/vterm.sh new file mode 100644 index 0000000..56b6c89 --- /dev/null +++ b/tests/shlib/vterm.sh @@ -0,0 +1,17 @@ +. tests/shlib/common.sh +set +x + +vterm_setup() { + make_test_root + + mkdir "$TEST_ROOT/path" + + ln -s "$(which "${PYTHON}")" "$TEST_ROOT/path/python" + ln -s "$(which bash)" "$TEST_ROOT/path" + + cp -r "$ROOT/tests/terminfo" "$TEST_ROOT" +} + +vterm_shutdown() { + rm_test_root +} diff --git a/tests/terminfo/s/screen b/tests/terminfo/s/screen Binary files differnew file mode 100644 index 0000000..d380072 --- /dev/null +++ b/tests/terminfo/s/screen diff --git a/tests/terminfo/s/st-256color b/tests/terminfo/s/st-256color Binary files differnew file mode 100644 index 0000000..9c2db04 --- /dev/null +++ b/tests/terminfo/s/st-256color diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..1beb5c6 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,42 @@ +#!/bin/bash +. tests/shlib/common.sh + +enter_suite root + +if test "$TRAVIS" = true ; then + export PATH="$HOME/opt/fish/bin:${PATH}" + export PATH="$PWD/tests/bot-ci/deps/rc:$PATH" + + if test "$PYTHON_IMPLEMENTATION" = "CPython" ; then + export PATH="$HOME/opt/zsh-${PYTHON_MM}${USE_UCS2_PYTHON:+-ucs2}/bin:${PATH}" + fi + + if test -n "$USE_UCS2_PYTHON" ; then + export LD_LIBRARY_PATH="$HOME/opt/cpython-ucs2-$UCS2_PYTHON_VARIANT/lib${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH" + set +e + . virtualenvwrapper.sh + workon cpython-ucs2-$UCS2_PYTHON_VARIANT + set -e + else + LIBRARY_PATH="$(ldd "$(which python)" | grep libpython | sed 's/^.* => //;s/ .*$//')" + LIBRARY_DIR="$(dirname "${LIBRARY_PATH}")" + export LD_LIBRARY_PATH="$LIBRARY_DIR${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH" + fi +fi + +export PYTHON="${PYTHON:=python}" +export PYTHONPATH="${PYTHONPATH}${PYTHONPATH:+:}`realpath .`" +for script in "$ROOT"/tests/test_*/test.sh ; do + test_name="${script##*/run_}" + if ! sh $script ; then + fail "${test_name%_tests.sh}" F "Failed $script" + fi +done + +if test -e "$FAILURES_FILE" ; then + echo "Fails and skips summary:" + cat "$FAILURES_FILE" + rm "$FAILURES_FILE" +fi + +exit_suite diff --git a/tests/test_awesome/path/awesome-client b/tests/test_awesome/path/awesome-client new file mode 100755 index 0000000..2088ff0 --- /dev/null +++ b/tests/test_awesome/path/awesome-client @@ -0,0 +1,3 @@ +#!/bin/sh +echo "$@" >> "$TEST_ROOT/results/args" +cat >> "$TEST_ROOT/results/requests" diff --git a/tests/test_awesome/powerline/config.json b/tests/test_awesome/powerline/config.json new file mode 100644 index 0000000..b8dfd01 --- /dev/null +++ b/tests/test_awesome/powerline/config.json @@ -0,0 +1,7 @@ +{ + "ext": { + "wm": { + "update_interval": 0.5 + } + } +} diff --git a/tests/test_awesome/powerline/themes/wm/default.json b/tests/test_awesome/powerline/themes/wm/default.json new file mode 100644 index 0000000..d9eaf5f --- /dev/null +++ b/tests/test_awesome/powerline/themes/wm/default.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "default-left" + } + ], + "right": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "default-right" + } + ] + } +} diff --git a/tests/test_awesome/powerline/themes/wm/dvi.json b/tests/test_awesome/powerline/themes/wm/dvi.json new file mode 100644 index 0000000..4cf3731 --- /dev/null +++ b/tests/test_awesome/powerline/themes/wm/dvi.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "dvi-left" + } + ], + "right": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "dvi-right" + } + ] + } +} diff --git a/tests/test_awesome/test.sh b/tests/test_awesome/test.sh new file mode 100755 index 0000000..fc85dc2 --- /dev/null +++ b/tests/test_awesome/test.sh @@ -0,0 +1,188 @@ +#!/bin/sh +. tests/shlib/common.sh + +enter_suite awesome + +make_test_root + +TEST_PATH="$TEST_ROOT/path" +TEST_STATIC_ROOT="$ROOT/tests/test_awesome" + +cp -r "$TEST_STATIC_ROOT/path" "$TEST_ROOT" +cp -r "$TEST_STATIC_ROOT/powerline" "$TEST_ROOT" + +export PYTHONPATH="$ROOT${PYTHONPATH:+:}$PYTHONPATH" + +ln -s "$(which "${PYTHON}")" "$TEST_PATH"/python +ln -s "$(which cat)" "$TEST_PATH" +ln -s "$(which sh)" "$TEST_PATH" +ln -s "$(which env)" "$TEST_PATH" +if which socat ; then + ln -s "$(which socat)" "$TEST_PATH" +fi +for pexe in powerline powerline.sh powerline.py ; do + if test -e scripts/$pexe ; then + ln -s "$PWD/scripts/$pexe" $TEST_ROOT/path + elif test -e client/$pexe ; then + ln -s "$PWD/client/$pexe" $TEST_ROOT/path + elif which $pexe ; then + ln -s "$(which $pexe)" $TEST_ROOT/path + else + continue + fi + if test "$pexe" != 'powerline.sh' || test -e "$TEST_PATH/socat" ; then + POWERLINE_COMMAND="$pexe" + break + fi +done + +DEPRECATED_SCRIPT="$ROOT/powerline/bindings/awesome/powerline-awesome.py" +POWERLINE_DAEMON="scripts/powerline-daemon" + +run() { + env -i \ + LANG=C \ + PATH="$TEST_PATH" \ + XDG_CONFIG_HOME="$TEST_ROOT" \ + XDG_CONFIG_DIRS="$TEST_ROOT/dummy" \ + PYTHONPATH="$PYTHONPATH" \ + TEST_ROOT="$TEST_ROOT" \ + LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + "$@" || true +} + +display_log() { + local log_file="$1" + echo "$log_file:" + echo '============================================================' + cat -v "$log_file" + echo + echo '____________________________________________________________' +} + +check_log() { + local args_file="$TEST_ROOT/results/args" + local log_file="$TEST_ROOT/results/requests" + local line="$(head -n1 "$log_file")" + local linenum="$(cat "$log_file" | wc -l)" + echo "Number of runs: $linenum (expected approx 5 / 0.5 = 10 runs)" + if test $linenum -lt 5 ; then + fail "log:lt" F "Script was run not enough times: $linenum < 5" + return 1 + elif test $linenum -gt 15 ; then + fail "log:gt" E "Script was run too many times: $linenum > 15" + return 1 + fi + local expline="powerline_widget:set_markup('<span foreground=\"#303030\"> </span><span foreground=\"#d0d0d0\" background=\"#303030\" font_weight=\"bold\"> default-right </span>')" + if test "$expline" != "$line" ; then + echo "Line: '$line'" + echo "Expected: '$expline'" + fail "log:line" F "Unexpected line" + return 1 + fi + local ret=0 + while test $linenum -gt 0 ; do + echo "$line" >> "$TEST_ROOT/ok" + linenum=$(( linenum - 1 )) + done + if ! diff "$TEST_ROOT/ok" "$log_file" ; then + fail "log:diff" F "Unexpected output" + ret=1 + fi + rm "$TEST_ROOT/ok" + return $ret +} + +killscript() { + kill -KILL $1 || true +} + +if ! test -e "$DEPRECATED_SCRIPT" ; then + # TODO: uncomment when skip is available + # skip "deprecated" "Missing deprecated bar bindings script" + : +else + enter_suite "deprecated" final + for args in "" "0.5"; do + rm -rf "$TEST_ROOT/results" + mkdir "$TEST_ROOT/results" + DEPRECATED_LOG="$TEST_ROOT/deprecated.log" + run env \ + DEPRECATED_SCRIPT="$DEPRECATED_SCRIPT" \ + args="$args" \ + DEPRECATED_LOG="$DEPRECATED_LOG" \ + TEST_ROOT="$TEST_ROOT" \ + sh -c ' + echo $$ > "$TEST_ROOT/$args-pid" + exec "$DEPRECATED_SCRIPT" $args > "$DEPRECATED_LOG" 2>&1 + ' & + sleep 5 + killscript "$(cat "$TEST_ROOT/$args-pid")" + rm "$TEST_ROOT/$args-pid" + if test -n "$(cat "$DEPRECATED_LOG")" ; then + display_log "$DEPRECATED_LOG" + fail "output" E "Nonempty $DEPRECATED_SCRIPT output" + fi + rm "$DEPRECATED_LOG" + if ! check_log ; then + display_log "$TEST_ROOT/results/args" + fail "log" F "Checking log failed" + fi + done + exit_suite --continue +fi + +enter_suite "awesome" final +ADDRESS="powerline-ipc-test-$$" +echo "Powerline address: $ADDRESS" +rm -rf "$TEST_ROOT/results" +mkdir "$TEST_ROOT/results" +run env \ + POWERLINE_DAEMON="$POWERLINE_DAEMON" \ + TEST_ROOT="$TEST_ROOT" \ + ADDRESS="$ADDRESS" \ + sh -c ' + echo $$ > "$TEST_ROOT/dpid" + exec python "$POWERLINE_DAEMON" --socket $ADDRESS --foreground > "$TEST_ROOT/daemon.log" 2>&1 + ' & +DPID=$! +sleep 2 +run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.1" 2>&1 +run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.2" 2>&1 +run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.3" 2>&1 +run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.4" 2>&1 +run "$POWERLINE_COMMAND" --socket $ADDRESS wm.awesome > "$TEST_ROOT/output.log.5" 2>&1 +for log_file in "$TEST_ROOT"/output.log.* ; do + if test -n "$(cat "$log_file")" ; then + display_log "$log_file" + fail "output" E "Nonempty $POWERLINE_COMMAND output at run ${log_file#*.}" + fi + rm "$log_file" +done +sleep 5 +run python "$POWERLINE_DAEMON" --socket $ADDRESS --quiet --kill > "$TEST_ROOT/kill.log" 2>&1 +if test -n "$(cat "$TEST_ROOT/kill.log")" ; then + display_log "$TEST_ROOT/kill.log" + fail "daemonlog" E "Nonempty kill log" +fi +rm "$TEST_ROOT/kill.log" +wait $DPID +if test -n "$(cat "$TEST_ROOT/daemon.log")" ; then + display_log "$TEST_ROOT/daemon.log" + fail "daemonlog" E "Nonempty daemon log" +fi +rm "$TEST_ROOT/daemon.log" +if ! check_log ; then + display_log "$TEST_ROOT/results/args" + fail "log" F "Checking log failed" +fi +exit_suite --continue + +if ! powerline-lint \ + -p "$ROOT/powerline/config_files" \ + -p "$TEST_STATIC_ROOT/powerline" +then + fail "lint" F "Checking test config failed" +fi + +exit_suite diff --git a/tests/test_bar/path/lemonbar b/tests/test_bar/path/lemonbar new file mode 100755 index 0000000..13c6030 --- /dev/null +++ b/tests/test_bar/path/lemonbar @@ -0,0 +1,11 @@ +#!/bin/sh + +RES_DIR="$TEST_ROOT/results" +mkdir -p "$RES_DIR" +RES_FILE="$RES_DIR/$$" +while test -e "$RES_FILE.log" ; do + RES_FILE="$RES_FILE.${RANDOM:-`date +%N | sed s/^0*//`}" +done + +echo $(basename $0) "$@" > "$RES_FILE.args" +cat > "$RES_FILE.log" diff --git a/tests/test_bar/path/xrandr b/tests/test_bar/path/xrandr new file mode 100755 index 0000000..d02e300 --- /dev/null +++ b/tests/test_bar/path/xrandr @@ -0,0 +1,31 @@ +#!/bin/sh + +cat << EOF +Screen 0: minimum 8 x 8, current 1920 x 1200, maximum 16384 x 16384 +DVI-I-0 disconnected (normal left inverted right x axis y axis) +VGA-0 connected 1920x1200+1+0 (normal left inverted right x axis y axis) 520mm x 330mm + 1920x1200 59.95*+ + 1920x1080 60.00 + 1680x1050 59.95 + 1600x1200 60.00 + 1440x900 59.89 + 1280x1024 75.02 60.02 + 1280x800 59.81 + 1152x864 75.00 + 1024x768 75.03 60.00 + 800x600 75.00 60.32 + 640x480 75.00 59.94 +DVI-I-1 connected 1920x1200+0+0 (normal left inverted right x axis y axis) 520mm x 330mm + 1920x1200 59.95*+ + 1920x1080 60.00 + 1680x1050 59.95 + 1600x1200 60.00 + 1440x900 59.89 + 1280x1024 75.02 60.02 + 1280x800 59.81 + 1152x864 75.00 + 1024x768 75.03 60.00 + 800x600 75.00 60.32 + 640x480 75.00 59.94 +HDMI-0 disconnected (normal left inverted right x axis y axis) +EOF diff --git a/tests/test_bar/powerline/config.json b/tests/test_bar/powerline/config.json new file mode 100644 index 0000000..0b35d56 --- /dev/null +++ b/tests/test_bar/powerline/config.json @@ -0,0 +1,9 @@ +{ + "ext": { + "wm": { + "local_themes": { + "DVI-I-1": "dvi" + } + } + } +} diff --git a/tests/test_bar/powerline/themes/wm/default.json b/tests/test_bar/powerline/themes/wm/default.json new file mode 100644 index 0000000..d9eaf5f --- /dev/null +++ b/tests/test_bar/powerline/themes/wm/default.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "default-left" + } + ], + "right": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "default-right" + } + ] + } +} diff --git a/tests/test_bar/powerline/themes/wm/dvi.json b/tests/test_bar/powerline/themes/wm/dvi.json new file mode 100644 index 0000000..4cf3731 --- /dev/null +++ b/tests/test_bar/powerline/themes/wm/dvi.json @@ -0,0 +1,18 @@ +{ + "segments": { + "left": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "dvi-left" + } + ], + "right": [ + { + "type": "string", + "highlight_groups": ["time"], + "contents": "dvi-right" + } + ] + } +} diff --git a/tests/test_bar/test.sh b/tests/test_bar/test.sh new file mode 100755 index 0000000..a0838fa --- /dev/null +++ b/tests/test_bar/test.sh @@ -0,0 +1,195 @@ +#!/bin/sh +. tests/shlib/common.sh + +enter_suite bar + +make_test_root +TEST_PATH="$TEST_ROOT/path" +TEST_STATIC_ROOT="$ROOT/tests/test_bar" + +cp -r "$TEST_STATIC_ROOT/path" "$TEST_ROOT" +cp -r "$TEST_STATIC_ROOT/powerline" "$TEST_ROOT" + +export PYTHONPATH="$ROOT${PYTHONPATH:+:}$PYTHONPATH" + +ln -s "$(which "${PYTHON}")" "$TEST_PATH"/python +ln -s "$(which sed)" "$TEST_PATH" +ln -s "$(which cat)" "$TEST_PATH" +ln -s "$(which mkdir)" "$TEST_PATH" +ln -s "$(which basename)" "$TEST_PATH" +ln -s "$TEST_PATH/lemonbar" "$TEST_PATH/bar-aint-recursive" + +DEPRECATED_SCRIPT="$ROOT/powerline/bindings/bar/powerline-bar.py" + +run() { + env -i \ + LANG=C \ + PATH="$TEST_PATH" \ + XDG_CONFIG_HOME="$TEST_ROOT" \ + XDG_CONFIG_DIRS="$TEST_ROOT/dummy" \ + PYTHONPATH="$PYTHONPATH" \ + TEST_ROOT="$TEST_ROOT" \ + LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + "$@" || true +} + +display_log() { + local log_file="$1" + echo "$log_file:" + echo '============================================================' + cat -v "$log_file" + echo + echo '____________________________________________________________' +} + +check_log() { + local log_file="$1" + local text="$2" + local warns="$3" + if test "$warns" = "warns" ; then + local warning="$(head -n1 "$log_file" | sed 's/.*://')" + local expwarning="The 'bar' bindings are deprecated, please switch to 'lemonbar'" + if test "$warning" != "$expwarning" ; then + echo "Got: $warning" + echo "Exp: $expwarning" + fail "warn" F "Expected warning" + fi + sed -r -i -e '1d' "$log_file" + fi + local line="$(head -n1 "$log_file")" + local linenum="$(cat "$log_file" | wc -l)" + if test $linenum -lt 5 ; then + fail "log:lt" F "Script was run not enough times" + return 1 + elif test $linenum -gt 15 ; then + fail "log:gt" E "Script was run too many times" + return 1 + fi + local expline="%{l}%{F#ffd0d0d0}%{B#ff303030} $text-left %{F-B--u}%{F#ff303030} %{F-B--u}%{r}%{F#ff303030} %{F-B--u}%{F#ffd0d0d0}%{B#ff303030} $text-right %{F-B--u}" + if test "$expline" != "$line" ; then + echo "Line: '$line'" + echo "Expected: '$expline'" + fail "log:line" F "Unexpected line" + return 1 + fi + local ret=0 + while test $linenum -gt 0 ; do + echo "$line" >> "$TEST_ROOT/ok" + linenum=$(( linenum - 1 )) + done + if ! diff "$TEST_ROOT/ok" "$log_file" ; then + fail "log:diff" F "Unexpected output" + ret=1 + fi + rm "$TEST_ROOT/ok" + return $ret +} + +killscript() { + kill -KILL $1 || true +} + +if ! test -e "$DEPRECATED_SCRIPT" ; then + # TODO: uncomment when skip is available + # skip "deprecated" "Missing deprecated bar bindings script" + : +else + enter_suite "deprecated" final + run python "$DEPRECATED_SCRIPT" $args > "$TEST_ROOT/deprecated.log" 2>&1 & + SPID=$! + sleep 5 + killscript $SPID + if ! check_log "$TEST_ROOT/deprecated.log" "default" warns ; then + display_log "$TEST_ROOT/deprecated.log" + fail "log" F "Checking log failed" + fi + rm "$TEST_ROOT/deprecated.log" + exit_suite --continue +fi + +LEMONBAR_SCRIPT="$ROOT/powerline/bindings/lemonbar/powerline-lemonbar.py" + +if ! test -e "$LEMONBAR_SCRIPT" ; then + # TODO: uncomment when skip is available + # skip "lemonbar" "Missing lemonbar bindings script" + : +else + enter_suite "lemonbar" + for args in "" "-i0.5" "--interval=0.5" "-- test args" "--bar-command bar-aint-recursive" "--height=10"; do + rm -rf "$TEST_ROOT/results" + run python "$LEMONBAR_SCRIPT" $args > "$TEST_ROOT/lemonbar.log" 2>&1 & + SPID=$! + sleep 5 + killscript $SPID + sleep 0.5 + enter_suite "args($args)" final + fnum=0 + for file in "$TEST_ROOT/results"/*.log ; do + if ! test -e "$file" ; then + fail "log" E "Log file is missing" + break + fi + fnum=$(( fnum + 1 )) + args_file="${file%.log}.args" + if ! test -e "$args_file" ; then + fail "args" E "$args_file is missing" + else + cat "$args_file" >> "$TEST_ROOT/args.log" + fi + text="dvi" + if cat "$args_file" | grep -q +1 ; then + text="default" + fi + if ! check_log "$file" "$text" ; then + display_log "$file" + fail "log" F "Checking log failed" + fi + rm "$file" + done + if test "$fnum" -ne 2 ; then + fail "fnum" F "Expected two output files" + fi + if test "${args#--height}" != "$args" ; then + height="${args#--height}" + height="${height# }" + height="${height#=}" + height="${height%% *}" + fi + command="lemonbar" + if test "${args#--bar-command}" != "$args" ; then + command="${args#--bar-command}" + command="${command# }" + command="${command#=}" + command="${command%% *}" + fi + received_args="$(cat "$TEST_ROOT/args.log" | sort)" + rm "$TEST_ROOT/args.log" + script_args="${args#*-- }" + script_args="${script_args# }" + if test "${script_args}" = "$args" ; then + script_args= + fi + expected_args="$command -g 1920x$height+0${script_args:+ }$script_args${NL}$command -g 1920x$height+1${script_args:+ }$script_args" + if test "$expected_args" != "$received_args" ; then + echo "args:${NL}<$received_args>" + echo "expected:${NL}<$expected_args>" + fail "args" F "Expected different args" + fi + if ! test -z "$(cat "$TEST_ROOT/lemonbar.log")" ; then + display_log "$TEST_ROOT/lemonbar.log" + fail "stderr" E "Unexpected script output" + fi + rm "$TEST_ROOT/lemonbar.log" + exit_suite --continue + done + exit_suite --continue +fi + +if ! powerline-lint \ + -p "$ROOT/powerline/config_files" \ + -p "$TEST_STATIC_ROOT/powerline" +then + fail "lint" F "Checking test config failed" +fi + +exit_suite diff --git a/tests/test_daemon/test.sh b/tests/test_daemon/test.sh new file mode 100755 index 0000000..a5c12c1 --- /dev/null +++ b/tests/test_daemon/test.sh @@ -0,0 +1,41 @@ +#!/bin/sh +. tests/shlib/common.sh + +enter_suite daemon final + +export ADDRESS="powerline-ipc-test-$$" +echo "Powerline address: $ADDRESS" +if "$PYTHON" "$ROOT/scripts/powerline-daemon" -s"$ADDRESS" ; then + sleep 1 + if ! ( \ + "$PYTHON" "$ROOT/client/powerline.py" \ + --socket "$ADDRESS" -p/dev/null shell left \ + | grep "file not found" + ) ; then + fail "devnull" F "-p/dev/null argument ignored or not treated properly" + fi + if ( \ + "$PYTHON" "$ROOT/client/powerline.py" --socket "$ADDRESS" \ + -p"$ROOT/powerline/config_files" shell left \ + | grep "file not found" + ) ; then + fail "nodevnull" F "-p/dev/null argument remembered while it should not" + fi + if ! ( \ + cd "$ROOT/tests/test_daemon" \ + && "$PYTHON" "$ROOT/client/powerline.py" --socket "$ADDRESS" \ + -p"$ROOT/powerline/config_files" shell left \ + | grep "test_daemon" + ) ; then + fail "segment" F "Output lacks string “tests”" + fi +else + fail "exitcode" E "Daemon exited with status $?" +fi +if "$PYTHON" "$ROOT/scripts/powerline-daemon" -s"$ADDRESS" -k ; then + : +else + fail "-k" F "powerline-daemon -k failed with exit code $?" +fi + +exit_suite diff --git a/tests/test_in_vterm/shell/inits/dash b/tests/test_in_vterm/shell/inits/dash new file mode 100644 index 0000000..7b146ff --- /dev/null +++ b/tests/test_in_vterm/shell/inits/dash @@ -0,0 +1,16 @@ +# vim: ft=sh + +set_theme_option() { + export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2" +} +set_theme() { + export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" +} +set_virtual_env() { + export VIRTUAL_ENV="$HOME/.virtenvs/$1" +} +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +set_theme default_leftonly +. "$ROOT/powerline/bindings/shell/powerline.sh" +export VIRTUAL_ENV= +cd "$TEST_ROOT/3rd" diff --git a/tests/test_in_vterm/test.sh b/tests/test_in_vterm/test.sh new file mode 100755 index 0000000..bbd4958 --- /dev/null +++ b/tests/test_in_vterm/test.sh @@ -0,0 +1,13 @@ +#!/bin/sh +. tests/shlib/common.sh + +enter_suite vterm + +for t in "$ROOT"/tests/test_in_vterm/test_*.sh ; do + test_name="${t##*/test_}" + if ! "$t" ; then + fail "${test_name%.sh}" F "Failed running $t" + fi +done + +exit_suite diff --git a/tests/test_in_vterm/test_shells.py b/tests/test_in_vterm/test_shells.py new file mode 100755 index 0000000..faf7976 --- /dev/null +++ b/tests/test_in_vterm/test_shells.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys + +from time import sleep +from subprocess import check_call +from glob import glob1 +from traceback import print_exc + +from argparse import ArgumentParser + +from powerline.lib.dict import updated + +from tests.modules.lib.terminal import (ExpectProcess, MutableDimensions, + do_terminal_tests, get_env) +from tests.modules import PowerlineTestSuite + + +TEST_ROOT = os.path.abspath(os.environ['TEST_ROOT']) + + +def get_parser(): + parser = ArgumentParser() + parser.add_argument('--type', action='store') + parser.add_argument('--client', action='store') + parser.add_argument('--binding', action='store') + parser.add_argument('args', action='append') + return parser + + +BINDING_OPTIONS = { + 'dash': { + 'cmd': 'dash', + 'args': ['-i'], + 'init': [ + '. "$ROOT/tests/test_in_vterm/shell/inits/dash"', + ], + }, +} + + +def main(argv): + script_args = get_parser().parse_args(argv) + + vterm_path = os.path.join(TEST_ROOT, 'path') + + env = get_env(vterm_path, TEST_ROOT) + env['ROOT'] = os.path.abspath('.') + env['TEST_ROOT'] = TEST_ROOT + env['TEST_TYPE'] = script_args.type + env['TEST_CLIENT'] = script_args.client + env['LANG'] = 'en_US.UTF_8' + env['_POWERLINE_RUNNING_SHELL_TESTS'] = ( + 'ee5bcdc6-b749-11e7-9456-50465d597777') + + dim = MutableDimensions(rows=50, cols=200) + + binding_opts = BINDING_OPTIONS[script_args.binding] + + cmd = os.path.join(vterm_path, binding_opts['cmd']) + args = binding_opts['args'] + + def gen_init(binding): + def init(p): + for line in binding_opts['init']: + p.send(line + '\n') + sleep(1) + + return init + + def gen_feed(line): + def feed(p): + p.send(line + '\n') + sleep(0.1) + + return feed + + base_attrs = { + ((255, 204,0), (204, 51, 0), 0, 0, 0): 'H', + ((204, 51, 0), (0, 102, 153), 0, 0, 0): 'sHU', + ((255, 255, 255), (0, 102, 153), 1, 0, 0): 'U', + ((0, 102, 153), (44, 44, 44), 0, 0, 0): 'sUB', + ((199, 199, 199), (44, 44, 44), 0, 0, 0): 'B', + ((44, 44, 44), (88, 88, 88), 0, 0, 0): 'sBD', + ((199, 199, 199), (88, 88, 88), 0, 0, 0): 'D', + ((144, 144, 144), (88, 88, 88), 0, 0, 0): 'sD', + ((221, 221, 221), (88, 88, 88), 1, 0, 0): 'C', + ((88, 88, 88), (0, 0, 0), 0, 0, 0): 'sDN', + ((240, 240, 240), (0, 0, 0), 0, 0, 0): 'N', + ((0, 102, 153), (51, 153, 204), 0, 0, 0): 'sUE', + ((255, 255, 255), (51, 153, 204), 0, 0, 0): 'E', + ((51, 153, 204), (44, 44, 44), 0, 0, 0): 'sEB', + } + + tests = ( + { + 'expected_result': ( + '{H: hostname }{sHU: }' + '{U:user }{sUB: }' + '{B: BRANCH }{sBD: }' + '{D:… }{sD: }{D:tmp }{sD: }{D:vshells }{sD: }{C:3rd }{sDN: }' + '{N:}', + base_attrs, + ), + 'prep_cb': gen_init(script_args.binding), + }, + { + 'expected_result': ( + '{H: hostname }{sHU: }' + '{U:user }{sUB: }' + '{B: BRANCH }{sBD: }' + '{D:… }{sD: }{D:vshells }{sD: }{D:3rd }{sD: }{C:.git }{sDN: }' + '{N:}', + base_attrs + ), + 'prep_cb': gen_feed('cd .git'), + }, + { + 'expected_result': ( + '{H: hostname }{sHU: }' + '{U:user }{sUB: }' + '{B: BRANCH }{sBD: }' + '{D:… }{sD: }{D:tmp }{sD: }{D:vshells }{sD: }{C:3rd }{sDN: }' + '{N:}', + base_attrs, + ), + 'prep_cb': gen_feed('cd ..'), + }, + { + 'expected_result': ( + '{H: hostname }{sHU: }' + '{U:user }{sUE: }' + '{E:(e) some-venv }{sEB: }' + '{B: BRANCH }{sBD: }' + '{D:… }{sD: }{D:tmp }{sD: }{D:vshells }{sD: }{C:3rd }{sDN: }' + '{N:}', + base_attrs, + ), + 'prep_cb': gen_feed('set_virtual_env some-venv'), + }, + ) + + with PowerlineTestSuite('shell') as suite: + return do_terminal_tests( + tests=tests, + cmd=cmd, + dim=dim, + args=args, + env=env, + cwd=TEST_ROOT, + suite=suite, + ) + + +if __name__ == '__main__': + if main(sys.argv[1:]): + raise SystemExit(0) + else: + raise SystemExit(1) diff --git a/tests/test_in_vterm/test_shells.sh b/tests/test_in_vterm/test_shells.sh new file mode 100755 index 0000000..d4d0eef --- /dev/null +++ b/tests/test_in_vterm/test_shells.sh @@ -0,0 +1,119 @@ +#!/bin/bash +. tests/shlib/common.sh +. tests/shlib/vterm.sh + +enter_suite vshells + +vterm_setup + +HAS_SOCAT= +HAS_C_CLIENT= + +git init "$TEST_ROOT/3rd" +git --git-dir="$TEST_ROOT/3rd/.git" checkout -b BRANCH +export DIR1="[32m" +export DIR2="" +mkdir "$TEST_ROOT/3rd/$DIR1" +mkdir "$TEST_ROOT/3rd/$DIR2" +mkdir "$TEST_ROOT"/3rd/'\[\]' +mkdir "$TEST_ROOT"/3rd/'%%' +mkdir "$TEST_ROOT"/3rd/'#[bold]' +mkdir "$TEST_ROOT"/3rd/'(echo)' +mkdir "$TEST_ROOT"/3rd/'$(echo)' +mkdir "$TEST_ROOT"/3rd/'`echo`' +mkdir "$TEST_ROOT"/3rd/'«Unicode!»' +mkdir "$TEST_ROOT/fish_home" +mkdir "$TEST_ROOT/fish_home/fish" +mkdir "$TEST_ROOT/fish_home/fish/generated_completions" +cp -r "$ROOT/tests/test_shells/ipython_home" "$TEST_ROOT" + +ln -s "$(which env)" "$TEST_ROOT/path" +ln -s "$(which git)" "$TEST_ROOT/path" +ln -s "$(which sleep)" "$TEST_ROOT/path" +ln -s "$(which cat)" "$TEST_ROOT/path" +ln -s "$(which false)" "$TEST_ROOT/path" +ln -s "$(which true)" "$TEST_ROOT/path" +ln -s "$(which kill)" "$TEST_ROOT/path" +ln -s "$(which echo)" "$TEST_ROOT/path" +ln -s "$(which which)" "$TEST_ROOT/path" +ln -s "$(which dirname)" "$TEST_ROOT/path" +ln -s "$(which wc)" "$TEST_ROOT/path" +ln -s "$(which stty)" "$TEST_ROOT/path" +ln -s "$(which cut)" "$TEST_ROOT/path" +ln -s "$(which bc)" "$TEST_ROOT/path" +ln -s "$(which expr)" "$TEST_ROOT/path" +ln -s "$(which mktemp)" "$TEST_ROOT/path" +ln -s "$(which grep)" "$TEST_ROOT/path" +ln -s "$(which sed)" "$TEST_ROOT/path" +ln -s "$(which rm)" "$TEST_ROOT/path" +ln -s "$(which tr)" "$TEST_ROOT/path" +ln -s "$(which uname)" "$TEST_ROOT/path" +ln -s "$(which test)" "$TEST_ROOT/path" +ln -s "$(which pwd)" "$TEST_ROOT/path" +ln -s "$(which hostname)" "$TEST_ROOT/path" +ln -s "$ROOT/tests/test_shells/bgscript.sh" "$TEST_ROOT/path" +ln -s "$ROOT/tests/test_shells/waitpid.sh" "$TEST_ROOT/path" + +ln -s "$ROOT/scripts/powerline-config" "$TEST_ROOT/path" +ln -s "$ROOT/scripts/powerline-render" "$TEST_ROOT/path" +ln -s "$ROOT/client/powerline.py" "$TEST_ROOT/path" + +if test -e "$ROOT/scripts/powerline" ; then + ln -s "$ROOT/scripts/powerline" "$TEST_ROOT/path" +elif test -e client/powerline ; then + ln -s "$ROOT/client/powerline" "$TEST_ROOT/path" +elif which powerline ; then + ln -s "$(which powerline)" "$TEST_ROOT/path" +else + echo "Executable powerline was not found" + exit 1 +fi + +if test "$( + file --mime-type --brief --dereference "$TEST_ROOT/path/powerline" \ + | cut -d/ -f1)" = "application" ; then + HAS_C_CLIENT=1 +fi + +if which socat ; then + HAS_SOCAT=1 + ln -s "$(which socat)" "$TEST_ROOT/path" + ln -s "$ROOT/client/powerline.sh" "$TEST_ROOT/path" +fi + +# Test type: daemon, renderer, … +# Test client: python, shell, c, none +# Test binding: *sh, ipython, pdb, … +test_shell() { + local test_type="$1" ; shift + local test_client="$1" ; shift + local test_binding="$1" ; shift + + if test "$test_client" = shell && test -z "$HAS_SOCAT" ; then + echo "Skipping test, socat not available" + return + fi + if test "$test_client" = c && test -z "$HAS_C_CLIENT" ; then + echo "Skipping test, C client not available" + return + fi + if which "$test_binding" ; then + ln -s "$(which "$test_binding")" "$TEST_ROOT/path" + fi + + if ! "${PYTHON}" "$ROOT/tests/test_in_vterm/test_shells.py" \ + --type=$test_type \ + --client=$test_client \ + --binding=$test_binding \ + -- "$@" + then + local test_name="$test_type-$test_client-$test_binding" + fail "$test_name" F "Failed vterm shell test" + fi +} + +test_shell renderer python dash -i || true + +vterm_shutdown + +exit_suite diff --git a/tests/test_in_vterm/test_tmux.py b/tests/test_in_vterm/test_tmux.py new file mode 100755 index 0000000..c1e126b --- /dev/null +++ b/tests/test_in_vterm/test_tmux.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys +import json + +from time import sleep +from subprocess import check_call +from glob import glob1 +from traceback import print_exc + +from powerline.lib.dict import updated +from powerline.bindings.tmux import get_tmux_version +from powerline import get_fallback_logger + +from tests.modules.lib.terminal import (ExpectProcess, MutableDimensions, + do_terminal_tests, get_env) +from tests.modules import PowerlineTestSuite + + +TEST_ROOT = os.path.abspath(os.environ['TEST_ROOT']) + + +def tmux_logs_iter(test_dir): + for tail in glob1(test_dir, '*.log'): + yield os.path.join(test_dir, tail) + + +def print_tmux_logs(): + for f in tmux_logs_iter(TEST_ROOT): + print('_' * 80) + print(os.path.basename(f) + ':') + print('=' * 80) + with open(f, 'r') as fp: + for line in fp: + sys.stdout.write(line) + os.unlink(f) + + +def get_expected_result(tmux_version, + expected_result_old, + expected_result_1_7=None, + expected_result_1_8=None, + expected_result_2_0=None): + if tmux_version >= (2, 0) and expected_result_2_0: + return expected_result_2_0 + elif tmux_version >= (1, 8) and expected_result_1_8: + return expected_result_1_8 + elif tmux_version >= (1, 7) and expected_result_1_7: + return expected_result_1_7 + else: + return expected_result_old + + +def tmux_fin_cb(p, cmd, env): + try: + check_call([ + cmd, '-S', env['POWERLINE_TMUX_SOCKET_PATH'], 'kill-server' + ], env=env, cwd=TEST_ROOT) + except Exception: + print_exc() + for f in tmux_logs_iter(TEST_ROOT): + os.unlink(f) + + +def main(attempts=3): + vterm_path = os.path.join(TEST_ROOT, 'path') + + tmux_exe = os.path.join(vterm_path, 'tmux') + + socket_path = os.path.abspath('tmux-socket-{0}'.format(attempts)) + if os.path.exists(socket_path): + os.unlink(socket_path) + + env = get_env(vterm_path, TEST_ROOT, { + 'POWERLINE_THEME_OVERRIDES': ';'.join(( + key + '=' + json.dumps(val) + for key, val in ( + ('default.segments.right', [{ + 'type': 'string', + 'name': 's1', + 'highlight_groups': ['cwd'], + 'priority':50, + }]), + ('default.segments.left', [{ + 'type': 'string', + 'name': 's2', + 'highlight_groups': ['background'], + 'priority':20, + }]), + ('default.segment_data.s1.contents', 'S1 string here'), + ('default.segment_data.s2.contents', 'S2 string here'), + ) + )), + 'POWERLINE_TMUX_SOCKET_PATH': socket_path, + }) + + conf_path = os.path.abspath('powerline/bindings/tmux/powerline.conf') + conf_line = 'source "' + ( + conf_path.replace('\\', '\\\\').replace('"', '\\"')) + '"\n' + conf_file = os.path.realpath(os.path.join(TEST_ROOT, 'tmux.conf')) + with open(conf_file, 'w') as cf_fd: + cf_fd.write(conf_line) + + tmux_version = get_tmux_version(get_fallback_logger()) + + dim = MutableDimensions(rows=50, cols=200) + + def prepare_test_1(p): + sleep(5) + + def prepare_test_2(p): + dim.cols = 40 + p.resize(dim) + sleep(5) + + base_attrs = { + ((0, 0, 0), (243, 243, 243), 1, 0, 0): 'lead', + ((243, 243, 243), (11, 11, 11), 0, 0, 0): 'leadsep', + ((255, 255, 255), (11, 11, 11), 0, 0, 0): 'bg', + ((199, 199, 199), (88, 88, 88), 0, 0, 0): 'cwd', + ((88, 88, 88), (11, 11, 11), 0, 0, 0): 'cwdhsep', + ((0, 0, 0), (0, 224, 0), 0, 0, 0): 'defstl', + } + tests = ( + { + 'expected_result': get_expected_result( + tmux_version, + expected_result_old=( + '{lead: 0 }{leadsep: }{bg: S2 string here }' + '{4: 0 }{cwdhsep:| }{6:bash }' + '{bg: }{4: 1- }{cwdhsep:| }{6:bash }' + '{bg: }{7: }{8:2* | }{9:bash }{10: }' + '{bg:' + (' ' * 124) + '}' + '{cwdhsep: }{cwd: S1 string here }', updated(base_attrs, { + ((133, 133, 133), (11, 11, 11), 0, 0, 0): 4, + ((188, 188, 188), (11, 11, 11), 0, 0, 0): 6, + ((11, 11, 11), (0, 102, 153), 0, 0, 0): 7, + ((102, 204, 255), (0, 102, 153), 0, 0, 0): 8, + ((255, 255, 255), (0, 102, 153), 1, 0, 0): 9, + ((0, 102, 153), (11, 11, 11), 0, 0, 0): 10, + })), + expected_result_1_8=( + '{lead: 0 }{leadsep: }{bg: S2 string here }' + '{4: 0 }{cwdhsep:| }{6:bash }' + '{bg: }{4: 1- }{cwdhsep:| }{7:bash }' + '{bg: }{8: }{9:2* | }{10:bash }{7: }' + '{bg:' + (' ' * 124) + '}' + '{cwdhsep: }{cwd: S1 string here }', updated(base_attrs, { + ((133, 133, 133), (11, 11, 11), 0, 0, 0): 4, + ((188, 188, 188), (11, 11, 11), 0, 0, 0): 6, + ((0, 102, 153), (11, 11, 11), 0, 0, 0): 7, + ((11, 11, 11), (0, 102, 153), 0, 0, 0): 8, + ((102, 204, 255), (0, 102, 153), 0, 0, 0): 9, + ((255, 255, 255), (0, 102, 153), 1, 0, 0): 10, + })), + expected_result_2_0=( + '{lead: 0 }{leadsep: }{bg: S2 string here }' + '{4: 0 }{cwdhsep:| }{6:bash }' + '{bg: }{4: 1- }{cwdhsep:| }{7:bash }' + '{bg: }{8: }{9:2* | }{10:bash }{7: }' + '{bg:' + (' ' * 125) + '}' + '{cwdhsep: }{cwd: S1 string here }', updated(base_attrs, { + ((133, 133, 133), (11, 11, 11), 0, 0, 0): 4, + ((188, 188, 188), (11, 11, 11), 0, 0, 0): 6, + ((0, 102, 153), (11, 11, 11), 0, 0, 0): 7, + ((11, 11, 11), (0, 102, 153), 0, 0, 0): 8, + ((102, 204, 255), (0, 102, 153), 0, 0, 0): 9, + ((255, 255, 255), (0, 102, 153), 1, 0, 0): 10, + })), + ), + 'prep_cb': prepare_test_1, + 'row': dim.rows - 1, + }, { + 'expected_result': get_expected_result( + tmux_version, + expected_result_old=('{bg:' + (' ' * 40) + '}', base_attrs), + expected_result_1_7=( + '{lead: 0 }' + '{leadsep: }{bg: <}{4:h }{bg: }{5: }' + '{6:2* | }{7:bash }{8: }{bg: }{cwdhsep: }' + '{cwd: S1 string here }', updated(base_attrs, { + ((188, 188, 188), (11, 11, 11), 0, 0, 0): 4, + ((11, 11, 11), (0, 102, 153), 0, 0, 0): 5, + ((102, 204, 255), (0, 102, 153), 0, 0, 0): 6, + ((255, 255, 255), (0, 102, 153), 1, 0, 0): 7, + ((0, 102, 153), (11, 11, 11), 0, 0, 0): 8, + })), + expected_result_1_8=( + '{lead: 0 }' + '{leadsep: }{bg: <}{4:h }{bg: }{5: }' + '{6:2* | }{7:bash }{4: }{bg: }{cwdhsep: }' + '{cwd: S1 string here }', updated(base_attrs, { + ((0, 102, 153), (11, 11, 11), 0, 0, 0): 4, + ((11, 11, 11), (0, 102, 153), 0, 0, 0): 5, + ((102, 204, 255), (0, 102, 153), 0, 0, 0): 6, + ((255, 255, 255), (0, 102, 153), 1, 0, 0): 7, + })), + expected_result_2_0=( + '{lead: 0 }' + '{leadsep: }{bg:<}{4:ash }{bg: }{5: }' + '{6:2* | }{7:bash }{4: }{cwdhsep: }' + '{cwd: S1 string here }', updated(base_attrs, { + ((0, 102, 153), (11, 11, 11), 0, 0, 0): 4, + ((11, 11, 11), (0, 102, 153), 0, 0, 0): 5, + ((102, 204, 255), (0, 102, 153), 0, 0, 0): 6, + ((255, 255, 255), (0, 102, 153), 1, 0, 0): 7, + })), + ), + 'prep_cb': prepare_test_2, + 'row': dim.rows - 1, + } + ) + + args = [ + # Specify full path to tmux socket (testing tmux instance must not + # interfere with user one) + '-S', socket_path, + # Force 256-color mode + '-2', + # Request verbose logging just in case + '-v', + # Specify configuration file + '-f', conf_file, + # Run bash three times + 'new-session', 'bash --norc --noprofile -i', ';', + 'new-window', 'bash --norc --noprofile -i', ';', + 'new-window', 'bash --norc --noprofile -i', ';', + ] + + with PowerlineTestSuite('tmux') as suite: + return do_terminal_tests( + tests=tests, + cmd=tmux_exe, + dim=dim, + args=args, + env=env, + cwd=TEST_ROOT, + fin_cb=tmux_fin_cb, + last_attempt_cb=print_tmux_logs, + suite=suite, + ) + + +if __name__ == '__main__': + if main(): + raise SystemExit(0) + else: + raise SystemExit(1) diff --git a/tests/test_in_vterm/test_tmux.sh b/tests/test_in_vterm/test_tmux.sh new file mode 100755 index 0000000..062e02b --- /dev/null +++ b/tests/test_in_vterm/test_tmux.sh @@ -0,0 +1,44 @@ +#!/bin/bash +. tests/shlib/common.sh +. tests/shlib/vterm.sh + +enter_suite tmux final + +vterm_setup + +ln -s "$(which env)" "$TEST_ROOT/path" +ln -s "$(which cut)" "$TEST_ROOT/path" +ln -s "$ROOT/scripts/powerline-render" "$TEST_ROOT/path" +ln -s "$ROOT/scripts/powerline-config" "$TEST_ROOT/path" + +test_tmux() { + if test "$PYTHON_IMPLEMENTATION" = PyPy; then + # FIXME PyPy3 segfaults for some reason, PyPy does it as well, but + # occasionally. + return 0 + fi + if ! which "${POWERLINE_TMUX_EXE}" ; then + return 0 + fi + ln -sf "$(which "${POWERLINE_TMUX_EXE}")" "$TEST_ROOT/path/tmux" + f="$ROOT/tests/test_in_vterm/test_tmux.py" + if ! "${PYTHON}" "$f" ; then + local test_name="$("$POWERLINE_TMUX_EXE" -V 2>&1 | cut -d' ' -f2)" + fail "$test_name" F "Failed vterm test $f" + fi +} + +if test -z "$POWERLINE_TMUX_EXE" && test -d "$ROOT/tests/bot-ci/deps/tmux" +then + for tmux in "$ROOT"/tests/bot-ci/deps/tmux/tmux-*/tmux ; do + export POWERLINE_TMUX_EXE="$tmux" + test_tmux || true + done +else + export POWERLINE_TMUX_EXE="${POWERLINE_TMUX_EXE:-tmux}" + test_tmux || true +fi + +vterm_shutdown + +exit_suite diff --git a/tests/test_in_vterm/test_vim.py b/tests/test_in_vterm/test_vim.py new file mode 100755 index 0000000..0fbc319 --- /dev/null +++ b/tests/test_in_vterm/test_vim.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys + +from time import sleep +from subprocess import check_call +from glob import glob1 +from traceback import print_exc + +from powerline.lib.dict import updated + +from tests.modules.lib.terminal import (ExpectProcess, MutableDimensions, + do_terminal_tests, get_env) +from tests.modules import PowerlineTestSuite + + +TEST_ROOT = os.path.abspath(os.environ['TEST_ROOT']) + + +def main(attempts=3): + vterm_path = os.path.join(TEST_ROOT, 'path') + + vim_exe = os.path.join(vterm_path, 'vim') + + env = get_env(vterm_path, TEST_ROOT) + env['ROOT'] = os.path.abspath('.') + + dim = MutableDimensions(rows=50, cols=200) + + vimrc = os.path.join(TEST_ROOT, 'init.vim') + vimrc_contents = ''' + set laststatus=2 + set runtimepath=$ROOT/powerline/bindings/vim + ''' + with open(vimrc, 'w') as vd: + vd.write(vimrc_contents) + + base_attrs = { + (( 64, 64, 255), (0, 0, 0), 0, 0, 0): 'NT', # NonText + ((240, 240, 240), (0, 0, 0), 0, 0, 0): 'N', # Normal + } + + args = [ + '-u', vimrc, + '-i', 'NONE', + ] + + def feed(p): + p.send(':echo strtrans(eval(&statusline[2:]))\n') + + tests = ( + ) + + with PowerlineTestSuite('vim') as suite: + return do_terminal_tests( + tests=tests, + cmd=vim_exe, + dim=dim, + args=args, + env=env, + cwd=TEST_ROOT, + suite=suite, + ) + + +if __name__ == '__main__': + if main(): + raise SystemExit(0) + else: + raise SystemExit(1) diff --git a/tests/test_in_vterm/test_vim.sh b/tests/test_in_vterm/test_vim.sh new file mode 100755 index 0000000..a7e6168 --- /dev/null +++ b/tests/test_in_vterm/test_vim.sh @@ -0,0 +1,39 @@ +#!/bin/sh +. tests/shlib/common.sh +. tests/shlib/vterm.sh +. tests/shlib/vim.sh + +enter_suite vvim final + +vterm_setup + +test_vim() { + if test "$PYTHON_IMPLEMENTATION" != CPython ; then + # Can only link with cpython + return 0 + fi + if ! which "$POWERLINE_VIM_EXE" ; then + return 0 + fi + ln -sf "$(which "${POWERLINE_VIM_EXE}")" "$TEST_ROOT/path/vim" + f="$ROOT/tests/test_in_vterm/test_vim.py" + if ! "${PYTHON}" "$f" ; then + local test_name="$(LANG=C "$POWERLINE_VIM_EXE" --cmd 'echo version' --cmd qa 2>&1 | tail -n2)" + fail "$test_name" F "Failed vterm test $f" + fi +} + +if test -z "$POWERLINE_VIM_EXE" && test -d "$ROOT/tests/bot-ci/deps/vim" +then + for vim in "$OLD_VIM" "$NEW_VIM" ; do + export POWERLINE_VIM_EXE="$vim" + test_vim || true + done +else + export POWERLINE_VIM_EXE="${POWERLINE_VIM_EXE:-vim}" + test_vim || true +fi + +vterm_shutdown + +exit_suite diff --git a/tests/test_lint/test.sh b/tests/test_lint/test.sh new file mode 100755 index 0000000..03c2f8a --- /dev/null +++ b/tests/test_lint/test.sh @@ -0,0 +1,10 @@ +#!/bin/sh +. tests/shlib/common.sh + +enter_suite lint final + +if ! "$PYTHON" "$ROOT/scripts/powerline-lint" -p "$ROOT/powerline/config_files" ; then + fail "test" F "Running powerline-lint failed" +fi + +exit_suite diff --git a/tests/test_python/empty b/tests/test_python/empty new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/test_python/empty diff --git a/tests/test_python/test.sh b/tests/test_python/test.sh new file mode 100755 index 0000000..f042237 --- /dev/null +++ b/tests/test_python/test.sh @@ -0,0 +1,13 @@ +#!/bin/sh +. tests/shlib/common.sh + +enter_suite python final + +for file in "$ROOT"/tests/test_python/test_*.py ; do + test_name="${file##*/test_}" + if ! "$PYTHON" "$file" --verbose --catch ; then + fail "${test_name%.py}" F "Failed test(s) from $file" + fi +done + +exit_suite diff --git a/tests/test_python/test_cmdline.py b/tests/test_python/test_cmdline.py new file mode 100644 index 0000000..470a7b4 --- /dev/null +++ b/tests/test_python/test_cmdline.py @@ -0,0 +1,149 @@ +# vim:fileencoding=utf-8:noet + +'''Tests for shell.py parser''' + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys + +if sys.version_info < (3,): + from io import BytesIO as StrIO +else: + from io import StringIO as StrIO + +from powerline.commands.main import get_argparser, finish_args + +from tests.modules import TestCase +from tests.modules.lib import replace_attr + + +class TestParser(TestCase): + def test_main_err(self): + parser = get_argparser() + out = StrIO() + err = StrIO() + + def flush(): + out.truncate(0) + err.truncate(0) + + with replace_attr(sys, 'stdout', out, 'stderr', err): + for raising_args, raising_reg in [ + ([], 'too few arguments|the following arguments are required: ext'), + (['-r'], 'expected one argument'), + (['shell', '-r'], 'expected one argument'), + (['shell', '-w'], 'expected one argument'), + (['shell', '-c'], 'expected one argument'), + (['shell', '-t'], 'expected one argument'), + (['shell', '-p'], 'expected one argument'), + (['shell', '-R'], 'expected one argument'), + (['shell', '--renderer-module'], 'expected one argument'), + (['shell', '--width'], 'expected one argument'), + (['shell', '--last-exit-code'], 'expected one argument'), + (['shell', '--last-pipe-status'], 'expected one argument'), + (['shell', '--config-override'], 'expected one argument'), + (['shell', '--theme-override'], 'expected one argument'), + (['shell', '--config-path'], 'expected one argument'), + (['shell', '--renderer-arg'], 'expected one argument'), + (['shell', '--jobnum'], 'expected one argument'), + (['-r', '.zsh'], 'too few arguments|the following arguments are required: ext'), + (['shell', '--last-exit-code', 'i'], 'invalid int_or_sig value'), + (['shell', '--last-pipe-status', '1 i'], 'invalid <lambda> value'), + ]: + self.assertRaises(SystemExit, parser.parse_args, raising_args) + self.assertFalse(out.getvalue()) + self.assertRegexpMatches(err.getvalue(), raising_reg) + flush() + + def test_main_normal(self): + parser = get_argparser() + out = StrIO() + err = StrIO() + with replace_attr(sys, 'stdout', out, 'stderr', err): + for argv, expargs in [ + (['shell', 'left'], {'ext': ['shell'], 'side': 'left'}), + (['shell', 'left', '-r', '.zsh'], {'ext': ['shell'], 'renderer_module': '.zsh', 'side': 'left'}), + ([ + 'shell', + 'left', + '-r', '.zsh', + '--last-exit-code', '10', + '--last-pipe-status', '10 20 30', + '--jobnum=10', + '-w', '100', + '-c', 'common.term_truecolor=true', + '-c', 'common.spaces=4', + '-t', 'default.segment_data.hostname.before=H:', + '-p', '.', + '-p', '..', + '-R', 'smth={"abc":"def"}', + ], { + 'ext': ['shell'], + 'side': 'left', + 'renderer_module': '.zsh', + 'last_exit_code': 10, + 'last_pipe_status': [10, 20, 30], + 'jobnum': 10, + 'width': 100, + 'config_override': {'common': {'term_truecolor': True, 'spaces': 4}}, + 'theme_override': { + 'default': { + 'segment_data': { + 'hostname': { + 'before': 'H:' + } + } + } + }, + 'config_path': ['.', '..'], + 'renderer_arg': {'smth': {'abc': 'def'}}, + }), + (['shell', 'left', '-R', 'arg=true'], { + 'ext': ['shell'], + 'side': 'left', + 'renderer_arg': {'arg': True}, + }), + (['shell', 'left', '-R', 'arg=true', '-R', 'arg='], { + 'ext': ['shell'], + 'side': 'left', + 'renderer_arg': {}, + }), + (['shell', 'left', '-R', 'arg='], {'ext': ['shell'], 'renderer_arg': {}, 'side': 'left'}), + (['shell', 'left', '-t', 'default.segment_info={"hostname": {}}'], { + 'ext': ['shell'], + 'side': 'left', + 'theme_override': { + 'default': { + 'segment_info': { + 'hostname': {} + } + } + }, + }), + (['shell', 'left', '-c', 'common={ }'], { + 'ext': ['shell'], + 'side': 'left', + 'config_override': {'common': {}}, + }), + (['shell', 'left', '--last-pipe-status='], { + 'ext': ['shell'], + 'side': 'left', + 'last_pipe_status': [], + }), + ]: + args = parser.parse_args(argv) + finish_args(parser, {}, args) + for key, val in expargs.items(): + self.assertEqual(getattr(args, key), val) + for key, val in args.__dict__.items(): + if key not in expargs: + self.assertFalse(val, msg='key {0} is {1} while it should be something false'.format(key, val)) + self.assertFalse(err.getvalue() + out.getvalue(), msg='unexpected output: {0!r} {1!r}'.format( + err.getvalue(), + out.getvalue(), + )) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_config_merging.py b/tests/test_python/test_config_merging.py new file mode 100644 index 0000000..3f4fa2a --- /dev/null +++ b/tests/test_python/test_config_merging.py @@ -0,0 +1,270 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import json + +from subprocess import check_call +from operator import add +from shutil import rmtree + +from powerline.lib.dict import mergedicts_copy as mdc +from powerline import Powerline + +from tests.modules import TestCase +from tests.modules.lib.config_mock import select_renderer, UT + + +CONFIG_DIR = 'tests/config' + + +root_config = lambda: { + 'common': { + 'interval': None, + 'watcher': 'auto', + }, + 'ext': { + 'test': { + 'theme': 'default', + 'colorscheme': 'default', + }, + }, +} + + +colors_config = lambda: { + 'colors': { + 'c1': 1, + 'c2': 2, + }, + 'gradients': { + }, +} + + +colorscheme_config = lambda: { + 'groups': { + 'g': {'fg': 'c1', 'bg': 'c2', 'attrs': []}, + } +} + + +theme_config = lambda: { + 'segment_data': { + 's': { + 'before': 'b', + }, + }, + 'segments': { + 'left': [ + { + 'type': 'string', + 'name': 's', + 'contents': 't', + 'highlight_groups': ['g'], + }, + ], + 'right': [], + } +} + +top_theme_config = lambda: { + 'dividers': { + 'left': { + 'hard': '#>', + 'soft': '|>', + }, + 'right': { + 'hard': '<#', + 'soft': '<|', + }, + }, + 'spaces': 0, +} + + +main_tree = lambda: { + '1/config': root_config(), + '1/colors': colors_config(), + '1/colorschemes/default': colorscheme_config(), + '1/themes/test/default': theme_config(), + '1/themes/' + UT: top_theme_config(), + '1/themes/other1': mdc(top_theme_config(), { + 'dividers': { + 'left': { + 'hard': '!>', + } + } + }), + '1/themes/other2': mdc(top_theme_config(), { + 'dividers': { + 'left': { + 'hard': '>>', + } + } + }), +} + + +def mkdir_recursive(directory): + if os.path.isdir(directory): + return + mkdir_recursive(os.path.dirname(directory)) + os.mkdir(directory) + + +class TestPowerline(Powerline): + def get_config_paths(self): + return tuple(sorted([ + os.path.join(CONFIG_DIR, d) + for d in os.listdir(CONFIG_DIR) + ])) + + +class WithConfigTree(object): + __slots__ = ('tree', 'p', 'p_kwargs') + + def __init__(self, tree, p_kwargs={'run_once': True}): + self.tree = tree + self.p = None + self.p_kwargs = p_kwargs + + def __enter__(self, *args): + os.mkdir(CONFIG_DIR) + for k, v in self.tree.items(): + fname = os.path.join(CONFIG_DIR, k) + '.json' + mkdir_recursive(os.path.dirname(fname)) + with open(fname, 'w') as F: + json.dump(v, F) + select_renderer(simpler_renderer=True) + self.p = TestPowerline( + ext='test', + renderer_module='tests.modules.lib.config_mock', + **self.p_kwargs + ) + if os.environ.get('POWERLINE_RUN_LINT_DURING_TESTS'): + try: + check_call(['scripts/powerline-lint'] + reduce(add, ( + ['-p', d] for d in self.p.get_config_paths() + ))) + except: + self.__exit__() + raise + return self.p.__enter__(*args) + + def __exit__(self, *args): + try: + rmtree(CONFIG_DIR) + finally: + if self.p: + self.p.__exit__(*args) + + +class TestMerging(TestCase): + def assertRenderEqual(self, p, output, **kwargs): + self.assertEqual(p.render(**kwargs).replace(' ', ' '), output) + + def test_not_merged_config(self): + with WithConfigTree(main_tree()) as p: + self.assertRenderEqual(p, '{12} bt{2-}#>{--}') + + def test_root_config_merging(self): + with WithConfigTree(mdc(main_tree(), { + '2/config': { + 'common': { + 'default_top_theme': 'other1', + } + }, + })) as p: + self.assertRenderEqual(p, '{12} bt{2-}!>{--}') + with WithConfigTree(mdc(main_tree(), { + '2/config': { + 'common': { + 'default_top_theme': 'other1', + } + }, + '3/config': { + 'common': { + 'default_top_theme': 'other2', + } + }, + })) as p: + self.assertRenderEqual(p, '{12} bt{2-}>>{--}') + + def test_top_theme_merging(self): + with WithConfigTree(mdc(main_tree(), { + '2/themes/' + UT: { + 'spaces': 1, + }, + '3/themes/' + UT: { + 'dividers': { + 'left': { + 'hard': '>>', + } + } + }, + })) as p: + self.assertRenderEqual(p, '{12} bt {2-}>>{--}') + + def test_colors_config_merging(self): + with WithConfigTree(mdc(main_tree(), { + '2/colors': { + 'colors': { + 'c1': 3, + } + }, + })) as p: + self.assertRenderEqual(p, '{32} bt{2-}#>{--}') + with WithConfigTree(mdc(main_tree(), { + '2/colors': { + 'colors': { + 'c1': 3, + } + }, + '3/colors': { + 'colors': { + 'c1': 4, + } + }, + })) as p: + self.assertRenderEqual(p, '{42} bt{2-}#>{--}') + with WithConfigTree(mdc(main_tree(), { + '2/colors': { + 'colors': { + 'c1': 3, + } + }, + '3/colors': { + 'colors': { + 'c2': 4, + } + }, + })) as p: + self.assertRenderEqual(p, '{34} bt{4-}#>{--}') + + def test_colorschemes_merging(self): + with WithConfigTree(mdc(main_tree(), { + '2/colorschemes/default': { + 'groups': { + 'g': {'fg': 'c2', 'bg': 'c1', 'attrs': []}, + } + }, + })) as p: + self.assertRenderEqual(p, '{21} bt{1-}#>{--}') + + def test_theme_merging(self): + with WithConfigTree(mdc(main_tree(), { + '2/themes/test/default': { + 'segment_data': { + 's': { + 'after': 'a', + } + } + }, + })) as p: + self.assertRenderEqual(p, '{12} bta{2-}#>{--}') + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_config_reload.py b/tests/test_python/test_config_reload.py new file mode 100644 index 0000000..a418d49 --- /dev/null +++ b/tests/test_python/test_config_reload.py @@ -0,0 +1,319 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from time import sleep +from copy import deepcopy +from functools import wraps + +from tests.modules import TestCase +from tests.modules.lib.config_mock import get_powerline, add_watcher_events, UT + + +config = { + 'config': { + 'common': { + 'interval': 0, + 'watcher': 'test', + }, + 'ext': { + 'test': { + 'theme': 'default', + 'colorscheme': 'default', + }, + }, + }, + 'colors': { + 'colors': { + "col1": 1, + "col2": 2, + "col3": 3, + "col4": 4, + }, + 'gradients': { + }, + }, + 'colorschemes/test/default': { + 'groups': { + 'str1': {'fg': 'col1', 'bg': 'col2', 'attrs': ['bold']}, + 'str2': {'fg': 'col3', 'bg': 'col4', 'attrs': ['underline']}, + }, + }, + 'colorschemes/test/2': { + 'groups': { + 'str1': {'fg': 'col2', 'bg': 'col3', 'attrs': ['bold']}, + 'str2': {'fg': 'col1', 'bg': 'col4', 'attrs': ['underline']}, + }, + }, + 'themes/test/default': { + 'segments': { + "left": [ + { + "type": "string", + "contents": "s", + "highlight_groups": ["str1"], + }, + { + "type": "string", + "contents": "g", + "highlight_groups": ["str2"], + }, + ], + "right": [ + ], + }, + }, + 'themes/' + UT: { + 'dividers': { + "left": { + "hard": ">>", + "soft": ">", + }, + "right": { + "hard": "<<", + "soft": "<", + }, + }, + 'spaces': 0, + }, + 'themes/other': { + 'dividers': { + "left": { + "hard": ">>", + "soft": ">", + }, + "right": { + "hard": "<<", + "soft": "<", + }, + }, + 'spaces': 1, + }, + 'themes/test/2': { + 'segments': { + "left": [ + { + "type": "string", + "contents": "t", + "highlight_groups": ["str1"], + }, + { + "type": "string", + "contents": "b", + "highlight_groups": ["str2"], + }, + ], + "right": [ + ], + }, + }, +} + + +def with_new_config(func): + @wraps(func) + def f(self): + return func(self, deepcopy(config)) + return f + + +class TestConfigReload(TestCase): + def assertAccessEvents(self, p, *args): + events = set() + for event in args: + if ':' not in event: + events.add('check:' + event) + events.add('load:' + event) + else: + events.add(event) + self.assertEqual(set(p._pop_events()), events) + + @with_new_config + def test_noreload(self, config): + with get_powerline(config, run_once=True) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + config['config']['common']['spaces'] = 1 + add_watcher_events(p, 'config', wait=False, interval=0.05) + # When running once thread should not start + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p) + self.assertEqual(p.logger._pop_msgs(), []) + + @with_new_config + def test_reload_main(self, config): + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['config']['common']['default_top_theme'] = 'other' + add_watcher_events(p, 'config') + p.render() + self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'themes/other', 'check:themes/test/__main__', 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), []) + + config['config']['ext']['test']['theme'] = 'nonexistent' + add_watcher_events(p, 'config') + self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'check:themes/test/nonexistent', 'themes/other', 'check:themes/test/__main__') + # It should normally handle file missing error + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/test/__main__', + 'exception:test:powerline:Failed to load theme: themes/test/nonexistent', + 'exception:test:powerline:Failed to create renderer: themes/test/nonexistent' + ]) + + config['config']['ext']['test']['theme'] = 'default' + add_watcher_events(p, 'config') + self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'themes/test/default', 'themes/other', 'check:themes/test/__main__') + self.assertEqual(p.logger._pop_msgs(), []) + + config['config']['ext']['test']['colorscheme'] = 'nonexistent' + add_watcher_events(p, 'config') + self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'check:colorschemes/nonexistent', 'check:colorschemes/test/__main__', 'check:colorschemes/test/nonexistent') + # It should normally handle file missing error + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load colorscheme: colorschemes/nonexistent', + 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/__main__', + 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/nonexistent', + 'exception:test:powerline:Failed to create renderer: colorschemes/test/nonexistent' + ]) + + config['config']['ext']['test']['colorscheme'] = '2' + add_watcher_events(p, 'config') + self.assertEqual(p.render(), '<2 3 1> s <3 4 False>>><1 4 4>g <4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'check:colorschemes/2', 'check:colorschemes/test/__main__', 'colorschemes/test/2') + self.assertEqual(p.logger._pop_msgs(), []) + + config['config']['ext']['test']['theme'] = '2' + add_watcher_events(p, 'config') + self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'themes/test/2', 'themes/other', 'check:themes/test/__main__') + self.assertEqual(p.logger._pop_msgs(), []) + + self.assertEqual(p.renderer.local_themes, None) + config['config']['ext']['test']['local_themes'] = 'something' + add_watcher_events(p, 'config') + self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>><None None None>') + self.assertAccessEvents(p, 'config') + self.assertEqual(p.logger._pop_msgs(), []) + self.assertEqual(p.renderer.local_themes, 'something') + + @with_new_config + def test_reload_unexistent(self, config): + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['config']['ext']['test']['colorscheme'] = 'nonexistentraise' + add_watcher_events(p, 'config') + # It may appear that p.logger._pop_msgs() is called after given + # exception is added to the mesagges, but before config_loader + # exception was added (this one: + # “exception:test:config_loader:Error while running condition + # function for key colorschemes/test/nonexistentraise: + # fcf:colorschemes/test/nonexistentraise”). + # sleep(0.1) + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + # For colorschemes/{test/,}*raise find_config_file raises + # IOError, but it does not do so for check:colorschemes/test/__main__, + # so powerline is trying to load this, but not other + # colorschemes/* + self.assertAccessEvents(p, 'config', 'check:colorschemes/test/__main__', 'check:colorschemes/nonexistentraise', 'check:colorschemes/test/nonexistentraise') + self.assertIn('exception:test:powerline:Failed to create renderer: fcf:colorschemes/test/nonexistentraise', p.logger._pop_msgs()) + + config['colorschemes/nonexistentraise'] = {} + config['colorschemes/test/nonexistentraise'] = { + 'groups': { + 'str1': {'fg': 'col1', 'bg': 'col3', 'attrs': ['bold']}, + 'str2': {'fg': 'col2', 'bg': 'col4', 'attrs': ['underline']}, + }, + } + while not p._will_create_renderer(): + sleep(0.1) + self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><2 4 4>g<4 False False>>><None None None>') + # Same as above + self.assertAccessEvents(p, 'colorschemes/nonexistentraise', 'colorschemes/test/nonexistentraise', 'check:colorschemes/test/__main__') + self.assertEqual(p.logger._pop_msgs(), []) + + @with_new_config + def test_reload_colors(self, config): + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['colors']['colors']['col1'] = 5 + add_watcher_events(p, 'colors') + self.assertEqual(p.render(), '<5 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'colors') + self.assertEqual(p.logger._pop_msgs(), []) + + @with_new_config + def test_reload_colorscheme(self, config): + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['colorschemes/test/default']['groups']['str1']['bg'] = 'col3' + add_watcher_events(p, 'colorschemes/test/default') + self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default') + self.assertEqual(p.logger._pop_msgs(), []) + + @with_new_config + def test_reload_theme(self, config): + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' + add_watcher_events(p, 'themes/test/default') + self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + self.assertEqual(p.logger._pop_msgs(), []) + + @with_new_config + def test_reload_top_theme(self, config): + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['themes/' + UT]['dividers']['left']['hard'] = '|>' + add_watcher_events(p, 'themes/' + UT) + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>|><3 4 4>g<4 False False>|><None None None>') + self.assertAccessEvents(p, 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + self.assertEqual(p.logger._pop_msgs(), []) + + @with_new_config + def test_reload_theme_main(self, config): + config['config']['common']['interval'] = None + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' + add_watcher_events(p, 'themes/test/default', wait=False) + self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + self.assertEqual(p.logger._pop_msgs(), []) + self.assertTrue(p._watcher._calls) + + @with_new_config + def test_run_once_no_theme_reload(self, config): + config['config']['common']['interval'] = None + with get_powerline(config, run_once=True) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/' + UT, 'check:themes/test/__main__') + + config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' + add_watcher_events(p, 'themes/test/default', wait=False) + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>><None None None>') + self.assertAccessEvents(p) + self.assertEqual(p.logger._pop_msgs(), []) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_configuration.py b/tests/test_python/test_configuration.py new file mode 100644 index 0000000..aa9e844 --- /dev/null +++ b/tests/test_python/test_configuration.py @@ -0,0 +1,877 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os + +from functools import wraps +from copy import deepcopy + +import tests.modules.vim as vim_module + +from tests.modules import TestCase +from tests.modules.lib.config_mock import (get_powerline, get_powerline_raw, + swap_attributes, UT) +from tests.modules.lib import Args, replace_item + + +def highlighted_string(s, group, **kwargs): + ret = { + 'type': 'string', + 'contents': s, + 'highlight_groups': [group], + } + ret.update(kwargs) + return ret + + +config = { + 'config': { + 'common': { + 'interval': 0, + 'watcher': 'test', + }, + 'ext': { + 'test': { + 'theme': 'default', + 'colorscheme': 'default', + }, + 'vim': { + 'theme': 'default', + 'colorscheme': 'default', + }, + 'shell': { + 'theme': 'default', + 'colorscheme': 'default', + }, + 'wm': { + 'theme': 'default', + 'colorscheme': 'default', + }, + }, + }, + 'colors': { + 'colors': { + 'col1': 1, + 'col2': 2, + 'col3': 3, + 'col4': 4, + 'col5': 5, + 'col6': 6, + 'col7': 7, + 'col8': 8, + 'col9': 9, + 'col10': 10, + 'col11': 11, + 'col12': 12, + }, + 'gradients': { + }, + }, + 'colorschemes/test/__main__': { + 'groups': { + 'm1': 'g1', + 'm2': 'g3', + 'm3': {'fg': 'col11', 'bg': 'col12', 'attrs': []}, + } + }, + 'colorschemes/default': { + 'groups': { + 'g1': {'fg': 'col5', 'bg': 'col6', 'attrs': []}, + 'g2': {'fg': 'col7', 'bg': 'col8', 'attrs': []}, + 'g3': {'fg': 'col9', 'bg': 'col10', 'attrs': []}, + } + }, + 'colorschemes/test/default': { + 'groups': { + 'str1': {'fg': 'col1', 'bg': 'col2', 'attrs': ['bold']}, + 'str2': {'fg': 'col3', 'bg': 'col4', 'attrs': ['underline']}, + 'd1': 'g2', + 'd2': 'm2', + 'd3': 'm3', + }, + }, + 'colorschemes/vim/default': { + 'groups': { + 'environment': {'fg': 'col3', 'bg': 'col4', 'attrs': ['underline']}, + }, + }, + 'colorschemes/wm/default': { + 'groups': { + 'hl1': {'fg': 'col1', 'bg': 'col2', 'attrs': ['underline']}, + 'hl2': {'fg': 'col2', 'bg': 'col1', 'attrs': []}, + 'hl3': {'fg': 'col3', 'bg': 'col1', 'attrs': ['underline']}, + 'hl4': {'fg': 'col2', 'bg': 'col4', 'attrs': []}, + }, + }, + 'themes/test/default': { + 'segments': { + 'left': [ + highlighted_string('s', 'str1', width='auto'), + highlighted_string('g', 'str2'), + ], + 'right': [ + highlighted_string('f', 'str2', width='auto', align='r'), + ], + }, + }, + 'themes/' + UT: { + 'dividers': { + 'left': { + 'hard': '>>', + 'soft': '>', + }, + 'right': { + 'hard': '<<', + 'soft': '<', + }, + }, + 'spaces': 0, + }, + 'themes/test/__main__': { + 'dividers': { + 'right': { + 'soft': '|', + }, + }, + }, + 'themes/vim/default': { + 'segments': { + 'left': [ + { + 'function': 'powerline.segments.common.env.environment', + 'args': { + 'variable': 'TEST', + }, + }, + ], + }, + }, + 'themes/shell/default': { + 'default_module': 'powerline.segments.common', + 'segments': { + 'left': [ + highlighted_string('s', 'g1', width='auto'), + ], + }, + }, + 'themes/wm/default': { + 'default_module': 'powerline.segments.common', + 'segments': { + 'left': [ + highlighted_string('A', 'hl1'), + highlighted_string('B', 'hl2'), + ], + 'right': [ + highlighted_string('C', 'hl3'), + highlighted_string('D', 'hl4'), + ], + }, + }, +} + + +def with_new_config(func): + @wraps(func) + def f(self): + return func(self, deepcopy(config)) + return f + + +def add_args(func): + @wraps(func) + def f(self): + new_config = deepcopy(config) + with get_powerline(new_config, run_once=True, simpler_renderer=True) as p: + func(self, p, new_config) + return f + + +class TestRender(TestCase): + def assertRenderEqual(self, p, output, **kwargs): + self.assertEqual(p.render(**kwargs).replace(' ', ' '), output) + + def assertRenderLinesEqual(self, p, output, **kwargs): + self.assertEqual([l.replace(' ', ' ') for l in p.render_above_lines(**kwargs)], output) + + +class TestLines(TestRender): + @add_args + def test_without_above(self, p, config): + self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}|{344}f {--}') + self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', width=10) + # self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344} f {--}', width=11) + self.assertEqual(list(p.render_above_lines()), []) + + @with_new_config + def test_with_above(self, config): + old_segments = deepcopy(config['themes/test/default']['segments']) + config['themes/test/default']['segments']['above'] = [old_segments] + with get_powerline(config, run_once=True, simpler_renderer=True) as p: + self.assertRenderLinesEqual(p, [ + '{121} s{24}>>{344}g{34}>{34}|{344}f {--}', + ]) + self.assertRenderLinesEqual(p, [ + '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', + ], width=10) + + config['themes/test/default']['segments']['above'] = [old_segments] * 2 + with get_powerline(config, run_once=True, simpler_renderer=True) as p: + self.assertRenderLinesEqual(p, [ + '{121} s{24}>>{344}g{34}>{34}|{344}f {--}', + '{121} s{24}>>{344}g{34}>{34}|{344}f {--}', + ]) + self.assertRenderLinesEqual(p, [ + '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', + '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', + ], width=10) + + +class TestSegments(TestRender): + @add_args + def test_display(self, p, config): + config['themes/test/default']['segments']['left'][0]['display'] = False + config['themes/test/default']['segments']['left'][1]['display'] = True + config['themes/test/default']['segments']['right'][0]['display'] = False + self.assertRenderEqual(p, '{344} g{4-}>>{--}') + + +class TestColorschemesHierarchy(TestRender): + @add_args + def test_group_redirects(self, p, config): + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('a', 'd1', draw_hard_divider=False), + highlighted_string('b', 'd2', draw_hard_divider=False), + highlighted_string('c', 'd3', draw_hard_divider=False), + highlighted_string('A', 'm1', draw_hard_divider=False), + highlighted_string('B', 'm2', draw_hard_divider=False), + highlighted_string('C', 'm3', draw_hard_divider=False), + highlighted_string('1', 'g1', draw_hard_divider=False), + highlighted_string('2', 'g2', draw_hard_divider=False), + highlighted_string('3', 'g3', draw_hard_divider=False), + ], + 'right': [], + } + self.assertRenderEqual(p, '{78} a{910}b{1112}c{56}A{910}B{1112}C{56}1{78}2{910}3{--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_group_redirects_no_main(self, p, config): + del config['colorschemes/test/__main__'] + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('a', 'd1', draw_hard_divider=False), + highlighted_string('1', 'g1', draw_hard_divider=False), + highlighted_string('2', 'g2', draw_hard_divider=False), + highlighted_string('3', 'g3', draw_hard_divider=False), + ], + 'right': [], + } + self.assertRenderEqual(p, '{78} a{56}1{78}2{910}3{--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_group_redirects_no_top_default(self, p, config): + del config['colorschemes/default'] + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('c', 'd3', draw_soft_divider=False), + highlighted_string('C', 'm3', draw_hard_divider=False), + ], + 'right': [], + } + self.assertRenderEqual(p, '{1112} c{1112}C{--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_group_redirects_no_test_default(self, p, config): + del config['colorschemes/test/default'] + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('A', 'm1', draw_hard_divider=False), + highlighted_string('B', 'm2', draw_hard_divider=False), + highlighted_string('C', 'm3', draw_hard_divider=False), + highlighted_string('1', 'g1', draw_hard_divider=False), + highlighted_string('2', 'g2', draw_hard_divider=False), + highlighted_string('3', 'g3', draw_hard_divider=False), + ], + 'right': [], + } + self.assertRenderEqual(p, '{56} A{910}B{1112}C{56}1{78}2{910}3{--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_group_redirects_only_main(self, p, config): + del config['colorschemes/default'] + del config['colorschemes/test/default'] + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('C', 'm3', draw_hard_divider=False), + ], + 'right': [], + } + # Powerline is not able to work without default colorscheme + # somewhere, thus it will output error string + self.assertRenderEqual(p, 'colorschemes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load colorscheme: colorschemes/default', + 'exception:test:powerline:Failed to load colorscheme: colorschemes/test/default', + 'exception:test:powerline:Failed to create renderer: colorschemes/test/default', + 'exception:test:powerline:Failed to render: colorschemes/test/default', + ]) + + @add_args + def test_group_redirects_only_top_default(self, p, config): + del config['colorschemes/test/__main__'] + del config['colorschemes/test/default'] + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('1', 'g1', draw_hard_divider=False), + highlighted_string('2', 'g2', draw_hard_divider=False), + highlighted_string('3', 'g3', draw_hard_divider=False), + ], + 'right': [], + } + self.assertRenderEqual(p, '{56} 1{78}2{910}3{--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_group_redirects_only_test_default(self, p, config): + del config['colorschemes/default'] + del config['colorschemes/test/__main__'] + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('s', 'str1', draw_hard_divider=False), + ], + 'right': [], + } + self.assertRenderEqual(p, '{121} s{--}') + self.assertEqual(p.logger._pop_msgs(), []) + + +class TestThemeHierarchy(TestRender): + @add_args + def test_hierarchy(self, p, config): + self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}|{344}f {--}') + + @add_args + def test_no_main(self, p, config): + del config['themes/test/__main__'] + self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}<{344}f {--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_no_powerline(self, p, config): + config['themes/test/__main__']['dividers'] = config['themes/' + UT]['dividers'] + config['themes/test/__main__']['spaces'] = 1 + del config['themes/' + UT] + self.assertRenderEqual(p, '{121} s {24}>>{344}g {34}>{34}<{344} f {--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_no_default(self, p, config): + del config['themes/test/default'] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + @add_args + def test_only_default(self, p, config): + config['themes/test/default']['dividers'] = config['themes/' + UT]['dividers'] + config['themes/test/default']['spaces'] = 1 + del config['themes/test/__main__'] + del config['themes/' + UT] + self.assertRenderEqual(p, '{121} s {24}>>{344}g {34}>{34}<{344} f {--}') + + @add_args + def test_only_main(self, p, config): + del config['themes/test/default'] + del config['themes/' + UT] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/' + UT, + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + @add_args + def test_only_powerline(self, p, config): + del config['themes/test/default'] + del config['themes/test/__main__'] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/test/__main__', + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + @add_args + def test_nothing(self, p, config): + del config['themes/test/default'] + del config['themes/' + UT] + del config['themes/test/__main__'] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/' + UT, + 'exception:test:powerline:Failed to load theme: themes/test/__main__', + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + +class TestDisplayCondition(TestRender): + @add_args + def test_include_modes(self, p, config): + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('s1', 'g1', include_modes=['m1']), + highlighted_string('s2', 'g1', include_modes=['m1', 'm2']), + highlighted_string('s3', 'g1', include_modes=['m3']), + ] + } + self.assertRenderEqual(p, '{--}') + self.assertRenderEqual(p, '{56} s1{56}>{56}s2{6-}>>{--}', mode='m1') + self.assertRenderEqual(p, '{56} s2{6-}>>{--}', mode='m2') + self.assertRenderEqual(p, '{56} s3{6-}>>{--}', mode='m3') + + @add_args + def test_exclude_modes(self, p, config): + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('s1', 'g1', exclude_modes=['m1']), + highlighted_string('s2', 'g1', exclude_modes=['m1', 'm2']), + highlighted_string('s3', 'g1', exclude_modes=['m3']), + ] + } + self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}') + self.assertRenderEqual(p, '{56} s3{6-}>>{--}', mode='m1') + self.assertRenderEqual(p, '{56} s1{56}>{56}s3{6-}>>{--}', mode='m2') + self.assertRenderEqual(p, '{56} s1{56}>{56}s2{6-}>>{--}', mode='m3') + + @add_args + def test_exinclude_modes(self, p, config): + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('s1', 'g1', exclude_modes=['m1'], include_modes=['m2']), + highlighted_string('s2', 'g1', exclude_modes=['m1', 'm2'], include_modes=['m3']), + highlighted_string('s3', 'g1', exclude_modes=['m3'], include_modes=['m3']), + ] + } + self.assertRenderEqual(p, '{--}') + self.assertRenderEqual(p, '{--}', mode='m1') + self.assertRenderEqual(p, '{56} s1{6-}>>{--}', mode='m2') + self.assertRenderEqual(p, '{56} s2{6-}>>{--}', mode='m3') + + @add_args + def test_exinclude_function_nonexistent_module(self, p, config): + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('s1', 'g1', exclude_function='xxx_nonexistent_module.foo'), + highlighted_string('s2', 'g1', exclude_function='xxx_nonexistent_module.foo', include_function='xxx_nonexistent_module.bar'), + highlighted_string('s3', 'g1', include_function='xxx_nonexistent_module.bar'), + ] + } + self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}') + + @add_args + def test_exinclude_function(self, p, config): + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('s1', 'g1', exclude_function='mod.foo'), + highlighted_string('s2', 'g1', exclude_function='mod.foo', include_function='mod.bar'), + highlighted_string('s3', 'g1', include_function='mod.bar'), + ] + } + launched = set() + fool = [None] + barl = [None] + + def foo(*args, **kwargs): + launched.add('foo') + self.assertEqual(set(kwargs.keys()), set(('pl', 'segment_info', 'mode'))) + self.assertEqual(args, ()) + return fool[0] + + def bar(*args, **kwargs): + launched.add('bar') + self.assertEqual(set(kwargs.keys()), set(('pl', 'segment_info', 'mode'))) + self.assertEqual(args, ()) + return barl[0] + + with replace_item(sys.modules, 'mod', Args(foo=foo, bar=bar)): + fool[0] = True + barl[0] = True + self.assertRenderEqual(p, '{56} s3{6-}>>{--}') + self.assertEqual(launched, set(('foo', 'bar'))) + + fool[0] = False + barl[0] = True + self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}') + self.assertEqual(launched, set(('foo', 'bar'))) + + fool[0] = False + barl[0] = False + self.assertRenderEqual(p, '{56} s1{6-}>>{--}') + self.assertEqual(launched, set(('foo', 'bar'))) + + fool[0] = True + barl[0] = False + self.assertRenderEqual(p, '{--}') + self.assertEqual(launched, set(('foo', 'bar'))) + + @add_args + def test_exinclude_modes_override_functions(self, p, config): + config['themes/test/default']['segments'] = { + 'left': [ + highlighted_string('s1', 'g1', exclude_function='mod.foo', exclude_modes=['m2']), + highlighted_string('s2', 'g1', exclude_function='mod.foo', include_modes=['m2']), + highlighted_string('s3', 'g1', include_function='mod.foo', exclude_modes=['m2']), + highlighted_string('s4', 'g1', include_function='mod.foo', include_modes=['m2']), + ] + } + fool = [None] + + def foo(*args, **kwargs): + return fool[0] + + with replace_item(sys.modules, 'mod', Args(foo=foo)): + fool[0] = True + self.assertRenderEqual(p, '{56} s4{6-}>>{--}', mode='m2') + self.assertRenderEqual(p, '{56} s3{56}>{56}s4{6-}>>{--}', mode='m1') + + fool[0] = False + self.assertRenderEqual(p, '{56} s2{56}>{56}s4{6-}>>{--}', mode='m2') + self.assertRenderEqual(p, '{56} s1{6-}>>{--}', mode='m1') + + +class TestOuterPadding(TestRender): + @add_args + def test_outer_padding_left(self, p, config): + config['themes/' + UT]['outer_padding'] = 5 + self.assertRenderEqual(p, '{121} s{24}>>{344}g{4-}>>{--}', side='left') + + @add_args + def test_outer_padding_right(self, p, config): + config['themes/' + UT]['outer_padding'] = 5 + self.assertRenderEqual(p, '{4-}<<{344}f {--}', side='right') + + @add_args + def test_outer_padding_ten(self, p, config): + config['themes/' + UT]['outer_padding'] = 10 + self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344} f {--}', width=30) + + @add_args + def test_outer_padding_zero(self, p, config): + config['themes/' + UT]['outer_padding'] = 0 + self.assertRenderEqual(p, '{121}s {24}>>{344}g{34}>{34}|{344} f{--}', width=30) + + +class TestSegmentAttributes(TestRender): + @add_args + def test_no_attributes(self, p, config): + def m1(divider=',', **kwargs): + return divider.join(kwargs.keys()) + divider + config['themes/test/default']['segments'] = { + 'left': [ + { + 'function': 'bar.m1' + } + ] + } + with replace_item(sys.modules, 'bar', Args(m1=m1)): + self.assertRenderEqual(p, '{56} pl,{6-}>>{--}') + + @add_args + def test_segment_datas(self, p, config): + def m1(divider=',', **kwargs): + return divider.join(kwargs.keys()) + divider + m1.powerline_segment_datas = { + UT: { + 'args': { + 'divider': ';' + } + }, + 'ascii': { + 'args': { + 'divider': '--' + } + } + } + config['themes/test/default']['segments'] = { + 'left': [ + { + 'function': 'bar.m1' + } + ] + } + with replace_item(sys.modules, 'bar', Args(m1=m1)): + self.assertRenderEqual(p, '{56} pl;{6-}>>{--}') + + @add_args + def test_expand(self, p, config): + def m1(divider=',', **kwargs): + return divider.join(kwargs.keys()) + divider + + def expand(pl, amount, segment, **kwargs): + return ('-' * amount) + segment['contents'] + + m1.expand = expand + config['themes/test/default']['segments'] = { + 'left': [ + { + 'function': 'bar.m1', + 'width': 'auto' + } + ] + } + with replace_item(sys.modules, 'bar', Args(m1=m1)): + self.assertRenderEqual(p, '{56} ----pl,{6-}>>{--}', width=10) + + @add_args + def test_truncate(self, p, config): + def m1(divider=',', **kwargs): + return divider.join(kwargs.keys()) + divider + + def truncate(pl, amount, segment, **kwargs): + return segment['contents'][:-amount] + + m1.truncate = truncate + config['themes/test/default']['segments'] = { + 'left': [ + { + 'function': 'bar.m1' + } + ] + } + with replace_item(sys.modules, 'bar', Args(m1=m1)): + self.assertRenderEqual(p, '{56} p{6-}>>{--}', width=4) + + +class TestSegmentData(TestRender): + @add_args + def test_segment_data(self, p, config): + def m1(**kwargs): + return 'S' + + def m2(**kwargs): + return 'S' + sys.modules['bar'] = Args(m1=m1, m2=m2) + config['themes/' + UT]['segment_data'] = { + 'm1': { + 'before': '1' + }, + 'bar.m2': { + 'before': '2' + }, + 'n': { + 'before': '3' + }, + 'm2': { + 'before': '4' + }, + } + config['themes/test/default']['segments'] = { + 'left': [ + { + 'function': 'bar.m1' + }, + { + 'function': 'bar.m1', + 'name': 'n' + }, + { + 'function': 'bar.m2', + 'name': 'n' + }, + { + 'function': 'bar.m2' + } + ] + } + self.assertRenderEqual(p, '{56} 1S{56}>{56}3S{610}>>{910}3S{910}>{910}2S{10-}>>{--}') + + +class TestShellEscapes(TestCase): + @with_new_config + def test_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0;38;5;5;48;5;6m\xa0s\x1b[0;38;5;6;49;22m>>\x1b[0m') + + @with_new_config + def test_tmux_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['additional_escapes'] = 'tmux' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0;38;5;5;48;5;6m\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0;38;5;6;49;22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_screen_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['additional_escapes'] = 'screen' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0;38;5;5;48;5;6m\x1b\\\xa0s\x1bP\x1b\x1b[0;38;5;6;49;22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_fbterm_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0m\x1b[1;5}\x1b[2;6}\xa0s\x1b[0m\x1b[1;6}\x1b[49m\x1b[22m>>\x1b[0m') + + @with_new_config + def test_fbterm_tmux_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + config['config']['common']['additional_escapes'] = 'tmux' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0m\x1b\x1b[1;5}\x1b\x1b[2;6}\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0m\x1b\x1b[1;6}\x1b\x1b[49m\x1b\x1b[22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_fbterm_screen_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + config['config']['common']['additional_escapes'] = 'screen' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0m\x1b\x1b[1;5}\x1b\x1b[2;6}\x1b\\\xa0s\x1bP\x1b\x1b[0m\x1b\x1b[1;6}\x1b\x1b[49m\x1b\x1b[22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_term_truecolor_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_truecolor'] = True + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0;38;2;192;0;192;48;2;0;128;128m\xa0s\x1b[0;38;2;0;128;128;49;22m>>\x1b[0m') + + @with_new_config + def test_term_truecolor_fbterm_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + config['config']['common']['term_truecolor'] = True + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0m\x1b[1;5}\x1b[2;6}\xa0s\x1b[0m\x1b[1;6}\x1b[49m\x1b[22m>>\x1b[0m') + + @with_new_config + def test_term_truecolor_tmux_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_truecolor'] = True + config['config']['common']['additional_escapes'] = 'tmux' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0;38;2;192;0;192;48;2;0;128;128m\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0;38;2;0;128;128;49;22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_term_truecolor_screen_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_truecolor'] = True + config['config']['common']['additional_escapes'] = 'screen' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0;38;2;192;0;192;48;2;0;128;128m\x1b\\\xa0s\x1bP\x1b\x1b[0;38;2;0;128;128;49;22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\') + + +class TestVim(TestCase): + def test_environ_update(self): + # Regression test: test that segment obtains environment from vim, not + # from os.environ. + with vim_module._with('globals', powerline_config_paths=['/']): + from powerline.vim import VimPowerline + import powerline as powerline_module + with swap_attributes(config, powerline_module): + with vim_module._with('environ', TEST='abc'): + with get_powerline_raw(config, VimPowerline) as powerline: + window = vim_module.current.window + window_id = 1 + winnr = window.number + 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' + 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): + # Regression test: VimPowerline.add_local_theme did not work properly. + from powerline.vim import VimPowerline + import powerline as powerline_module + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, VimPowerline, replace_gcp=True) as powerline: + powerline.add_local_theme('tests.modules.matchers.always_true', { + 'segment_data': { + 'foo': { + 'contents': '“bar”' + } + }, + 'segments': { + 'left': [ + { + 'type': 'string', + 'name': 'foo', + 'highlight_groups': ['g1'] + } + ] + } + }) + window = vim_module.current.window + window_id = 1 + winnr = window.number + 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 + def setUpClass(cls): + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path'))) + + @classmethod + def tearDownClass(cls): + sys.path.pop(0) + + +class TestLemonbar(TestRender): + def test_lemonbar(self): + import powerline as powerline_module + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline: + self.assertRenderEqual( + powerline, + '%{l}%{F#ffc00000}%{B#ff008000}%{+u} A%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}B%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}' + ) + + @with_new_config + def test_lemonbar_escape(self, config): + import powerline as powerline_module + config['themes/wm/default']['segments']['left'] = ( + highlighted_string('%{asd}', 'hl1'), + highlighted_string('10% %', 'hl2'), + ) + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline: + self.assertRenderEqual( + powerline, + '%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{}{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%%{} %%{}%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}' + ) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_lib.py b/tests/test_python/test_lib.py new file mode 100644 index 0000000..6dd6190 --- /dev/null +++ b/tests/test_python/test_lib.py @@ -0,0 +1,733 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import threading +import os +import sys +import re +import shutil +import unicodedata + +from time import sleep +from subprocess import call, PIPE + +from powerline.lib import add_divider_highlight_group +from powerline.lib.dict import mergedicts, REMOVE_THIS_KEY +from powerline.lib.humanize_bytes import humanize_bytes +from powerline.lib.vcs import guess, get_fallback_create_watcher +from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment +from powerline.lib.monotonic import monotonic +from powerline.lib.vcs.git import git_directory +from powerline.lib.shell import run_cmd + +import powerline.lib.unicode as plu + +from tests.modules.lib import Pl, replace_attr +from tests.modules import TestCase, SkipTest + + +try: + __import__('bzrlib') +except ImportError: + use_bzr = False +else: + use_bzr = True + +try: + __import__('hglib') +except ImportError: + use_mercurial = False +else: + use_mercurial = True + + +GIT_REPO = 'git_repo' +HG_REPO = 'hg_repo' +BZR_REPO = 'bzr_repo' + + +def thread_number(): + return len(threading.enumerate()) + + +class TestShell(TestCase): + def test_run_cmd(self): + pl = Pl() + self.assertEqual(run_cmd(pl, ['xxx_nonexistent_command_xxx']), None) + self.assertEqual(len(pl.exceptions), 1) + pl = Pl() + self.assertEqual(run_cmd(pl, ['echo', ' test ']), 'test') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=True), 'test') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=False), ' test \n') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['cat'], stdin='test'), 'test') + self.assertFalse(pl) + self.assertEqual(run_cmd(pl, ['sh', '-c', 'cat >&2'], stdin='test'), '') + self.assertFalse(pl) + + +class TestThreaded(TestCase): + def test_threaded_segment(self): + log = [] + pl = Pl() + updates = [(None,)] + lock = threading.Lock() + event = threading.Event() + block_event = threading.Event() + + class TestSegment(ThreadedSegment): + interval = 10 + + def set_state(self, **kwargs): + event.clear() + log.append(('set_state', kwargs)) + return super(TestSegment, self).set_state(**kwargs) + + def update(self, update_value): + block_event.wait() + event.set() + # Make sleep first to prevent some race conditions + log.append(('update', update_value)) + with lock: + ret = updates[0] + if isinstance(ret, Exception): + raise ret + else: + return ret[0] + + def render(self, update, **kwargs): + log.append(('render', update, kwargs)) + if isinstance(update, Exception): + raise update + else: + return update + + # Non-threaded tests + segment = TestSegment() + block_event.set() + updates[0] = (None,) + self.assertEqual(segment(pl=pl), None) + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('set_state', {}), + ('update', None), + ('render', None, {'pl': pl, 'update_first': True}), + ]) + log[:] = () + + segment = TestSegment() + block_event.set() + updates[0] = ('abc',) + self.assertEqual(segment(pl=pl), 'abc') + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('set_state', {}), + ('update', None), + ('render', 'abc', {'pl': pl, 'update_first': True}), + ]) + log[:] = () + + segment = TestSegment() + block_event.set() + updates[0] = ('abc',) + self.assertEqual(segment(pl=pl, update_first=False), 'abc') + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('set_state', {}), + ('update', None), + ('render', 'abc', {'pl': pl, 'update_first': False}), + ]) + log[:] = () + + segment = TestSegment() + block_event.set() + updates[0] = ValueError('abc') + self.assertEqual(segment(pl=pl), None) + self.assertEqual(thread_number(), 1) + self.assertEqual(len(pl.exceptions), 1) + self.assertEqual(log, [ + ('set_state', {}), + ('update', None), + ]) + log[:] = () + pl.exceptions[:] = () + + segment = TestSegment() + block_event.set() + updates[0] = (TypeError('def'),) + self.assertRaises(TypeError, segment, pl=pl) + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('set_state', {}), + ('update', None), + ('render', updates[0][0], {'pl': pl, 'update_first': True}), + ]) + log[:] = () + + # Threaded tests + segment = TestSegment() + block_event.clear() + kwargs = {'pl': pl, 'update_first': False, 'other': 1} + with lock: + updates[0] = ('abc',) + segment.startup(**kwargs) + ret = segment(**kwargs) + self.assertEqual(thread_number(), 2) + block_event.set() + event.wait() + segment.shutdown_event.set() + segment.thread.join() + self.assertEqual(ret, None) + self.assertEqual(log, [ + ('set_state', {'update_first': False, 'other': 1}), + ('render', None, {'pl': pl, 'update_first': False, 'other': 1}), + ('update', None), + ]) + log[:] = () + + segment = TestSegment() + block_event.set() + kwargs = {'pl': pl, 'update_first': True, 'other': 1} + with lock: + updates[0] = ('def',) + segment.startup(**kwargs) + ret = segment(**kwargs) + self.assertEqual(thread_number(), 2) + segment.shutdown_event.set() + segment.thread.join() + self.assertEqual(ret, 'def') + self.assertEqual(log, [ + ('set_state', {'update_first': True, 'other': 1}), + ('update', None), + ('render', 'def', {'pl': pl, 'update_first': True, 'other': 1}), + ]) + log[:] = () + + segment = TestSegment() + block_event.set() + kwargs = {'pl': pl, 'update_first': True, 'interval': 0.2} + with lock: + updates[0] = ('abc',) + segment.startup(**kwargs) + start = monotonic() + ret1 = segment(**kwargs) + with lock: + updates[0] = ('def',) + self.assertEqual(thread_number(), 2) + sleep(0.5) + ret2 = segment(**kwargs) + segment.shutdown_event.set() + segment.thread.join() + end = monotonic() + duration = end - start + self.assertEqual(ret1, 'abc') + self.assertEqual(ret2, 'def') + self.assertEqual(log[:5], [ + ('set_state', {'update_first': True, 'interval': 0.2}), + ('update', None), + ('render', 'abc', {'pl': pl, 'update_first': True, 'interval': 0.2}), + ('update', 'abc'), + ('update', 'def'), + ]) + num_runs = len([e for e in log if e[0] == 'update']) + self.assertAlmostEqual(duration / 0.2, num_runs, delta=1) + log[:] = () + + segment = TestSegment() + block_event.set() + kwargs = {'pl': pl, 'update_first': True, 'interval': 0.2} + with lock: + updates[0] = ('ghi',) + segment.startup(**kwargs) + start = monotonic() + ret1 = segment(**kwargs) + with lock: + updates[0] = TypeError('jkl') + self.assertEqual(thread_number(), 2) + sleep(0.5) + ret2 = segment(**kwargs) + segment.shutdown_event.set() + segment.thread.join() + end = monotonic() + duration = end - start + self.assertEqual(ret1, 'ghi') + self.assertEqual(ret2, None) + self.assertEqual(log[:5], [ + ('set_state', {'update_first': True, 'interval': 0.2}), + ('update', None), + ('render', 'ghi', {'pl': pl, 'update_first': True, 'interval': 0.2}), + ('update', 'ghi'), + ('update', 'ghi'), + ]) + num_runs = len([e for e in log if e[0] == 'update']) + self.assertAlmostEqual(duration / 0.2, num_runs, delta=1) + self.assertEqual(num_runs - 1, len(pl.exceptions)) + log[:] = () + + def test_kw_threaded_segment(self): + log = [] + pl = Pl() + event = threading.Event() + + class TestSegment(KwThreadedSegment): + interval = 10 + + @staticmethod + def key(_key=(None,), **kwargs): + log.append(('key', _key, kwargs)) + return _key + + def compute_state(self, key): + event.set() + sleep(0.1) + log.append(('compute_state', key)) + ret = key + if isinstance(ret, Exception): + raise ret + else: + return ret[0] + + def render_one(self, state, **kwargs): + log.append(('render_one', state, kwargs)) + if isinstance(state, Exception): + raise state + else: + return state + + # Non-threaded tests + segment = TestSegment() + event.clear() + self.assertEqual(segment(pl=pl), None) + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('key', (None,), {'pl': pl}), + ('compute_state', (None,)), + ('render_one', None, {'pl': pl}), + ]) + log[:] = () + + segment = TestSegment() + kwargs = {'pl': pl, '_key': ('abc',), 'update_first': False} + event.clear() + self.assertEqual(segment(**kwargs), 'abc') + kwargs.update(_key=('def',)) + self.assertEqual(segment(**kwargs), 'def') + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('key', ('abc',), {'pl': pl}), + ('compute_state', ('abc',)), + ('render_one', 'abc', {'pl': pl, '_key': ('abc',)}), + ('key', ('def',), {'pl': pl}), + ('compute_state', ('def',)), + ('render_one', 'def', {'pl': pl, '_key': ('def',)}), + ]) + log[:] = () + + segment = TestSegment() + kwargs = {'pl': pl, '_key': ValueError('xyz'), 'update_first': False} + event.clear() + self.assertEqual(segment(**kwargs), None) + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('key', kwargs['_key'], {'pl': pl}), + ('compute_state', kwargs['_key']), + ]) + log[:] = () + + segment = TestSegment() + kwargs = {'pl': pl, '_key': (ValueError('abc'),), 'update_first': False} + event.clear() + self.assertRaises(ValueError, segment, **kwargs) + self.assertEqual(thread_number(), 1) + self.assertEqual(log, [ + ('key', kwargs['_key'], {'pl': pl}), + ('compute_state', kwargs['_key']), + ('render_one', kwargs['_key'][0], {'pl': pl, '_key': kwargs['_key']}), + ]) + log[:] = () + + # Threaded tests + segment = TestSegment() + kwargs = {'pl': pl, 'update_first': False, '_key': ('_abc',)} + event.clear() + segment.startup(**kwargs) + ret = segment(**kwargs) + self.assertEqual(thread_number(), 2) + segment.shutdown_event.set() + segment.thread.join() + self.assertEqual(ret, None) + self.assertEqual(log[:2], [ + ('key', kwargs['_key'], {'pl': pl}), + ('render_one', None, {'pl': pl, '_key': kwargs['_key']}), + ]) + self.assertLessEqual(len(log), 3) + if len(log) > 2: + self.assertEqual(log[2], ('compute_state', kwargs['_key'])) + log[:] = () + + segment = TestSegment() + kwargs = {'pl': pl, 'update_first': True, '_key': ('_abc',)} + event.clear() + segment.startup(**kwargs) + ret1 = segment(**kwargs) + kwargs.update(_key=('_def',)) + ret2 = segment(**kwargs) + self.assertEqual(thread_number(), 2) + segment.shutdown_event.set() + segment.thread.join() + self.assertEqual(ret1, '_abc') + self.assertEqual(ret2, '_def') + self.assertEqual(log, [ + ('key', ('_abc',), {'pl': pl}), + ('compute_state', ('_abc',)), + ('render_one', '_abc', {'pl': pl, '_key': ('_abc',)}), + ('key', ('_def',), {'pl': pl}), + ('compute_state', ('_def',)), + ('render_one', '_def', {'pl': pl, '_key': ('_def',)}), + ]) + log[:] = () + + +class TestLib(TestCase): + def test_mergedicts(self): + d = {} + mergedicts(d, {'abc': {'def': 'ghi'}}) + self.assertEqual(d, {'abc': {'def': 'ghi'}}) + mergedicts(d, {'abc': {'def': {'ghi': 'jkl'}}}) + self.assertEqual(d, {'abc': {'def': {'ghi': 'jkl'}}}) + mergedicts(d, {}) + self.assertEqual(d, {'abc': {'def': {'ghi': 'jkl'}}}) + mergedicts(d, {'abc': {'mno': 'pqr'}}) + self.assertEqual(d, {'abc': {'def': {'ghi': 'jkl'}, 'mno': 'pqr'}}) + mergedicts(d, {'abc': {'def': REMOVE_THIS_KEY}}) + self.assertEqual(d, {'abc': {'mno': 'pqr'}}) + + def test_add_divider_highlight_group(self): + def decorated_function_name(**kwargs): + return str(kwargs) + func = add_divider_highlight_group('hl_group')(decorated_function_name) + self.assertEqual(func.__name__, 'decorated_function_name') + self.assertEqual(func(kw={}), [{'contents': repr({str('kw'): {}}), 'divider_highlight_group': 'hl_group'}]) + + def test_humanize_bytes(self): + self.assertEqual(humanize_bytes(0), '0 B') + self.assertEqual(humanize_bytes(1), '1 B') + self.assertEqual(humanize_bytes(1, suffix='bit'), '1 bit') + self.assertEqual(humanize_bytes(1000, si_prefix=True), '1 kB') + self.assertEqual(humanize_bytes(1024, si_prefix=True), '1 kB') + self.assertEqual(humanize_bytes(1000000000, si_prefix=True), '1.00 GB') + self.assertEqual(humanize_bytes(1000000000, si_prefix=False), '953.7 MiB') + + +width_data = { + 'N': 1, # Neutral + 'Na': 1, # Narrow + 'A': 1, # Ambigious + 'H': 1, # Half-width + 'W': 2, # Wide + 'F': 2, # Fullwidth +} + + +class TestUnicode(TestCase): + def assertStringsIdentical(self, s1, s2): + self.assertTrue(type(s1) is type(s2), msg='string types differ') + self.assertEqual(s1, s2) + + def test_unicode(self): + self.assertTrue(type('abc') is plu.unicode) + + def test_unichr(self): + self.assertStringsIdentical('\U0010FFFF', plu.unichr(0x10FFFF)) + self.assertStringsIdentical('\uFFFF', plu.unichr(0xFFFF)) + self.assertStringsIdentical('\x20', plu.unichr(0x20)) + + def test_u(self): + self.assertStringsIdentical('Test', plu.u('Test')) + self.assertStringsIdentical('Test', plu.u(b'Test')) + self.assertStringsIdentical('«»', plu.u(b'\xC2\xAB\xC2\xBB')) + self.assertRaises(UnicodeDecodeError, plu.u, b'\xFF') + + def test_tointiter(self): + self.assertEqual([1, 2, 3], list(plu.tointiter(b'\x01\x02\x03'))) + + def test_decode_error(self): + self.assertStringsIdentical('<FF>', b'\xFF'.decode('utf-8', 'powerline_decode_error')) + self.assertStringsIdentical('abc', b'abc'.decode('utf-8', 'powerline_decode_error')) + + def test_register_strwidth_error(self): + ename = plu.register_strwidth_error(lambda s: 3) + self.assertStringsIdentical(b'???', 'A'.encode('latin1', ename)) + self.assertStringsIdentical(b'abc', 'abc'.encode('latin1', ename)) + + def test_out_u(self): + self.assertStringsIdentical('abc', plu.out_u('abc')) + self.assertStringsIdentical('abc', plu.out_u(b'abc')) + self.assertRaises(TypeError, plu.out_u, None) + + def test_safe_unicode(self): + self.assertStringsIdentical('abc', plu.safe_unicode('abc')) + self.assertStringsIdentical('abc', plu.safe_unicode(b'abc')) + self.assertStringsIdentical('«»', plu.safe_unicode(b'\xc2\xab\xc2\xbb')) + with replace_attr(plu, 'get_preferred_output_encoding', lambda: 'latin1'): + self.assertStringsIdentical('ÿ', plu.safe_unicode(b'\xFF')) + self.assertStringsIdentical('None', plu.safe_unicode(None)) + + class FailingStr(object): + def __str__(self): + raise NotImplementedError('Fail!') + + self.assertStringsIdentical('Fail!', plu.safe_unicode(FailingStr())) + + def test_FailedUnicode(self): + self.assertTrue(isinstance(plu.FailedUnicode('abc'), plu.unicode)) + self.assertEqual('abc', plu.FailedUnicode('abc')) + + def test_string(self): + self.assertStringsIdentical(str('abc'), plu.string('abc')) + self.assertStringsIdentical(str('abc'), plu.string(b'abc')) + + def test_surrogate_pair_to_character(self): + self.assertEqual(0x1F48E, plu.surrogate_pair_to_character(0xD83D, 0xDC8E)) + + def test_strwidth_ucs_4(self): + self.assertEqual(4, plu.strwidth_ucs_4(width_data, 'abcd')) + self.assertEqual(4, plu.strwidth_ucs_4(width_data, 'AB')) + if sys.maxunicode < 0x10FFFF: + raise SkipTest('Can only test strwidth_ucs_4 in UCS-4 Pythons') + + self.assertEqual(1, plu.strwidth_ucs_4(width_data, '\U0001F063')) + + def test_strwidth_ucs_2(self): + self.assertEqual(4, plu.strwidth_ucs_2(width_data, 'abcd')) + self.assertEqual(4, plu.strwidth_ucs_2(width_data, 'AB')) + if not sys.maxunicode < 0x10FFFF: + raise SkipTest('Can only test strwidth_ucs_2 in UCS-2 Pythons') + self.assertEqual(1, plu.strwidth_ucs_2(width_data, '\ud83c\udc30')) + + +class TestVCS(TestCase): + def do_branch_rename_test(self, repo, q): + st = monotonic() + while monotonic() - st < 1: + # Give inotify time to deliver events + ans = repo.branch() + if hasattr(q, '__call__'): + if q(ans): + break + else: + if ans == q: + break + sleep(0.01) + if hasattr(q, '__call__'): + self.assertTrue(q(ans)) + else: + self.assertEqual(ans, q) + + def test_git(self): + create_watcher = get_fallback_create_watcher() + repo = guess(path=GIT_REPO, create_watcher=create_watcher) + self.assertNotEqual(repo, None) + self.assertEqual(repo.branch(), 'master') + self.assertEqual(repo.status(), None) + self.assertEqual(repo.status('file'), None) + with open(os.path.join(GIT_REPO, 'file'), 'w') as f: + f.write('abc') + f.flush() + self.assertEqual(repo.status(), ' U') + self.assertEqual(repo.status('file'), '??') + call(['git', 'add', '.'], cwd=GIT_REPO) + self.assertEqual(repo.status(), ' I ') + self.assertEqual(repo.status('file'), 'A ') + f.write('def') + f.flush() + self.assertEqual(repo.status(), 'DI ') + self.assertEqual(repo.status('file'), 'AM') + os.remove(os.path.join(GIT_REPO, 'file')) + # Test changing branch + self.assertEqual(repo.branch(), 'master') + try: + call(['git', 'branch', 'branch1'], cwd=GIT_REPO) + call(['git', 'checkout', '-q', 'branch1'], cwd=GIT_REPO) + self.do_branch_rename_test(repo, 'branch1') + call(['git', 'branch', 'branch2'], cwd=GIT_REPO) + call(['git', 'checkout', '-q', 'branch2'], cwd=GIT_REPO) + self.do_branch_rename_test(repo, 'branch2') + call(['git', 'checkout', '-q', '--detach', 'branch1'], cwd=GIT_REPO) + self.do_branch_rename_test(repo, lambda b: re.match(r'^[a-f0-9]+$', b)) + finally: + call(['git', 'checkout', '-q', 'master'], cwd=GIT_REPO) + # Test stashing + self.assertEqual(repo.stash(), 0) + + def stash_save(): + with open(os.path.join(GIT_REPO, 'file'), 'w') as f: + f.write('abc') + return call(['git', 'stash', '-u'], cwd=GIT_REPO, stdout=PIPE) + + def stash_drop(): + return call(['git', 'stash', 'drop'], cwd=GIT_REPO, stdout=PIPE) + + def stash_list(): + return call(['git', 'stash', 'list'], cwd=GIT_REPO, stdout=PIPE) + + try: + stash_save() + self.assertEqual(repo.stash(), 1) + stash_save() + self.assertEqual(repo.stash(), 2) + stash_drop() + self.assertEqual(repo.stash(), 1) + stash_drop() + self.assertEqual(repo.stash(), 0) + finally: + while stash_list(): + stash_drop() + + def test_git_sym(self): + create_watcher = get_fallback_create_watcher() + dotgit = os.path.join(GIT_REPO, '.git') + spacegit = os.path.join(GIT_REPO, ' .git ') + os.rename(dotgit, spacegit) + try: + with open(dotgit, 'w') as F: + F.write('gitdir: .git \n') + gitdir = git_directory(GIT_REPO) + self.assertTrue(os.path.isdir(gitdir)) + self.assertEqual(gitdir, os.path.abspath(spacegit)) + repo = guess(path=GIT_REPO, create_watcher=create_watcher) + self.assertEqual(repo.branch(), 'master') + finally: + os.remove(dotgit) + os.rename(spacegit, dotgit) + + def test_mercurial(self): + if not use_mercurial: + raise SkipTest('Mercurial is not available') + create_watcher = get_fallback_create_watcher() + repo = guess(path=HG_REPO, create_watcher=create_watcher) + self.assertNotEqual(repo, None) + self.assertEqual(repo.branch(), 'default') + self.assertEqual(repo.status(), None) + with open(os.path.join(HG_REPO, 'file'), 'w') as f: + f.write('abc') + f.flush() + self.assertEqual(repo.status(), ' U') + self.assertEqual(repo.status('file'), 'U') + call(['hg', 'add', '.'], cwd=HG_REPO, stdout=PIPE) + self.assertEqual(repo.status(), 'D ') + self.assertEqual(repo.status('file'), 'A') + os.remove(os.path.join(HG_REPO, 'file')) + + def test_bzr(self): + if not use_bzr: + raise SkipTest('Bazaar is not available') + create_watcher = get_fallback_create_watcher() + repo = guess(path=BZR_REPO, create_watcher=create_watcher) + self.assertNotEqual(repo, None, 'No bzr repo found. Do you have bzr installed?') + self.assertEqual(repo.branch(), 'test_powerline') + self.assertEqual(repo.status(), None) + with open(os.path.join(BZR_REPO, 'file'), 'w') as f: + f.write('abc') + self.assertEqual(repo.status(), ' U') + self.assertEqual(repo.status('file'), '? ') + call(['bzr', 'add', '-q', '.'], cwd=BZR_REPO, stdout=PIPE) + self.assertEqual(repo.status(), 'D ') + self.assertEqual(repo.status('file'), '+N') + call(['bzr', 'commit', '-q', '-m', 'initial commit'], cwd=BZR_REPO) + self.assertEqual(repo.status(), None) + with open(os.path.join(BZR_REPO, 'file'), 'w') as f: + f.write('def') + self.assertEqual(repo.status(), 'D ') + self.assertEqual(repo.status('file'), ' M') + self.assertEqual(repo.status('notexist'), None) + with open(os.path.join(BZR_REPO, 'ignored'), 'w') as f: + f.write('abc') + self.assertEqual(repo.status('ignored'), '? ') + # Test changing the .bzrignore file should update status + with open(os.path.join(BZR_REPO, '.bzrignore'), 'w') as f: + f.write('ignored') + self.assertEqual(repo.status('ignored'), None) + # Test changing the dirstate file should invalidate the cache for + # all files in the repo + with open(os.path.join(BZR_REPO, 'file2'), 'w') as f: + f.write('abc') + call(['bzr', 'add', 'file2'], cwd=BZR_REPO, stdout=PIPE) + call(['bzr', 'commit', '-q', '-m', 'file2 added'], cwd=BZR_REPO) + with open(os.path.join(BZR_REPO, 'file'), 'a') as f: + f.write('hello') + with open(os.path.join(BZR_REPO, 'file2'), 'a') as f: + f.write('hello') + self.assertEqual(repo.status('file'), ' M') + self.assertEqual(repo.status('file2'), ' M') + call(['bzr', 'commit', '-q', '-m', 'multi'], cwd=BZR_REPO) + self.assertEqual(repo.status('file'), None) + self.assertEqual(repo.status('file2'), None) + + # Test changing branch + call(['bzr', 'nick', 'branch1'], cwd=BZR_REPO, stdout=PIPE, stderr=PIPE) + self.do_branch_rename_test(repo, 'branch1') + + # Test branch name/status changes when swapping repos + for x in ('b1', 'b2'): + d = os.path.join(BZR_REPO, x) + os.mkdir(d) + call(['bzr', 'init', '-q'], cwd=d) + call(['bzr', 'nick', '-q', x], cwd=d) + repo = guess(path=d, create_watcher=create_watcher) + self.assertEqual(repo.branch(), x) + self.assertFalse(repo.status()) + if x == 'b1': + open(os.path.join(d, 'dirty'), 'w').close() + self.assertTrue(repo.status()) + os.rename(os.path.join(BZR_REPO, 'b1'), os.path.join(BZR_REPO, 'b')) + os.rename(os.path.join(BZR_REPO, 'b2'), os.path.join(BZR_REPO, 'b1')) + os.rename(os.path.join(BZR_REPO, 'b'), os.path.join(BZR_REPO, 'b2')) + for x, y in (('b1', 'b2'), ('b2', 'b1')): + d = os.path.join(BZR_REPO, x) + repo = guess(path=d, create_watcher=create_watcher) + self.do_branch_rename_test(repo, y) + if x == 'b1': + self.assertFalse(repo.status()) + else: + self.assertTrue(repo.status()) + + @classmethod + def setUpClass(cls): + cls.powerline_old_cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.dirname(__file__))) + call(['git', 'init', '--quiet', GIT_REPO]) + assert os.path.isdir(GIT_REPO) + call(['git', 'config', '--local', 'user.name', 'Foo'], cwd=GIT_REPO) + call(['git', 'config', '--local', 'user.email', 'bar@example.org'], cwd=GIT_REPO) + call(['git', 'commit', '--allow-empty', '--message', 'Initial commit', '--quiet'], cwd=GIT_REPO) + if use_mercurial: + cls.powerline_old_HGRCPATH = os.environ.get('HGRCPATH') + os.environ['HGRCPATH'] = '' + call(['hg', 'init', HG_REPO]) + with open(os.path.join(HG_REPO, '.hg', 'hgrc'), 'w') as hgrc: + hgrc.write('[ui]\n') + hgrc.write('username = Foo <bar@example.org>\n') + if use_bzr: + call(['bzr', 'init', '--quiet', BZR_REPO]) + call(['bzr', 'config', 'email=Foo <bar@example.org>'], cwd=BZR_REPO) + call(['bzr', 'config', 'nickname=test_powerline'], cwd=BZR_REPO) + call(['bzr', 'config', 'create_signatures=0'], cwd=BZR_REPO) + + @classmethod + def tearDownClass(cls): + for repo_dir in [GIT_REPO] + ([HG_REPO] if use_mercurial else []) + ([BZR_REPO] if use_bzr else []): + shutil.rmtree(repo_dir) + if use_mercurial: + if cls.powerline_old_HGRCPATH is None: + os.environ.pop('HGRCPATH') + else: + os.environ['HGRCPATH'] = cls.powerline_old_HGRCPATH + os.chdir(cls.powerline_old_cwd) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_lib_config.py b/tests/test_python/test_lib_config.py new file mode 100644 index 0000000..053462a --- /dev/null +++ b/tests/test_python/test_lib_config.py @@ -0,0 +1,52 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os + +from powerline.lib.config import ConfigLoader + +from tests.modules import TestCase +from tests.modules.lib.fsconfig import FSTree + + +FILE_ROOT = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'cfglib') + + +class LoadedList(list): + def pop_all(self): + try: + return self[:] + finally: + self[:] = () + + +loaded = LoadedList() + + +def on_load(key): + loaded.append(key) + + +def check_file(path): + if os.path.exists(path): + return path + else: + raise IOError + + +class TestLoaderCondition(TestCase): + def test_update_missing(self): + loader = ConfigLoader(run_once=True) + fpath = os.path.join(FILE_ROOT, 'file.json') + self.assertRaises(IOError, loader.load, fpath) + loader.register_missing(check_file, on_load, fpath) + loader.update() # This line must not raise IOError + with FSTree({'file': {'test': 1}}, root=FILE_ROOT): + loader.update() + self.assertEqual(loader.load(fpath), {'test': 1}) + self.assertEqual(loaded.pop_all(), [fpath]) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_listers.py b/tests/test_python/test_listers.py new file mode 100644 index 0000000..a33f033 --- /dev/null +++ b/tests/test_python/test_listers.py @@ -0,0 +1,227 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import powerline.listers.i3wm as i3wm + +from tests.modules.lib import Args, replace_attr, Pl +from tests.modules import TestCase + + +class TestI3WM(TestCase): + @staticmethod + def get_workspaces(): + return iter([ + {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, + {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, + {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, + {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, + ]) + + @staticmethod + def get_outputs(pl): + return iter([ + {'name': 'LVDS1'}, + {'name': 'HDMI1'}, + {'name': 'DVI01'}, + ]) + + def test_output_lister(self): + pl = Pl() + with replace_attr(i3wm, 'get_connected_xrandr_outputs', self.get_outputs): + self.assertEqual( + list(i3wm.output_lister(pl=pl, segment_info={'a': 1})), + [ + ({'a': 1, 'output': 'LVDS1'}, {'draw_inner_divider': None}), + ({'a': 1, 'output': 'HDMI1'}, {'draw_inner_divider': None}), + ({'a': 1, 'output': 'DVI01'}, {'draw_inner_divider': None}), + ] + ) + + def test_workspace_lister(self): + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): + self.assertEqual( + list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1})), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'HDMI1', + 'workspace': { + 'name': '3: w3', + 'focused': False, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'DVI01', + 'workspace': { + 'name': '4: w4', + 'focused': True, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1}, output='LVDS1')), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister( + pl=pl, + segment_info={'a': 1, 'output': 'LVDS1'} + )), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister( + pl=pl, + segment_info={'a': 1, 'output': 'LVDS1'}, + output=False + )), + [ + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '1: w1', + 'focused': False, + 'urgent': False, + 'visible': False + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'LVDS1', + 'workspace': { + 'name': '2: w2', + 'focused': False, + 'urgent': False, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'HDMI1', + 'workspace': { + 'name': '3: w3', + 'focused': False, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'DVI01', + 'workspace': { + 'name': '4: w4', + 'focused': True, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + self.assertEqual( + list(i3wm.workspace_lister( + pl=pl, + segment_info={'a': 1}, + only_show=['focused', 'urgent'] + )), + [ + ({ + 'a': 1, + 'output': 'HDMI1', + 'workspace': { + 'name': '3: w3', + 'focused': False, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ({ + 'a': 1, + 'output': 'DVI01', + 'workspace': { + 'name': '4: w4', + 'focused': True, + 'urgent': True, + 'visible': True + } + }, {'draw_inner_divider': None}), + ] + ) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_logging.py b/tests/test_python/test_logging.py new file mode 100644 index 0000000..d7cfe4a --- /dev/null +++ b/tests/test_python/test_logging.py @@ -0,0 +1,467 @@ +# vim:fileencoding=utf-8:noet + +'''Tests for various logging features''' + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import re +import codecs +import os + +from io import StringIO +from shutil import rmtree + +from powerline import finish_common_config, create_logger + +from tests.modules import TestCase +from tests.modules.lib import replace_attr + + +TIMESTAMP_RE = r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3}' + + +class TestRE(TestCase): + def assertMatches(self, text, regexp): + self.assertTrue( + re.match(regexp, text), + '{0!r} did not match {1!r}'.format(text, regexp), + ) + + +def close_handlers(logger): + for handler in logger.handlers: + handler.close() + + +class TestHandlers(TestRE): + def test_stderr_handler_is_default(self): + out = StringIO() + err = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(out.getvalue(), '') + + def test_stream_override(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_explicit_none(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [None]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_explicit_stream_handler(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', [[]]]]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertEqual(stream.getvalue(), '') + self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(out.getvalue(), '') + + def test_explicit_stream_handler_implicit_stream(self): + out = StringIO() + err = StringIO() + stream = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', []]]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_file_handler(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_file_handler' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': file_name}) + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_file_handler_create_dir(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_file_handler_create_dir/file' + + self.assertFalse(os.path.isdir(os.path.dirname(file_name))) + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': file_name}) + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + self.assertTrue(os.path.isdir(os.path.dirname(file_name))) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + rmtree(os.path.dirname(file_name)) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_multiple_files(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name_1 = 'test_logging-test_multiple_files-1' + file_name_2 = file_name_1[:-1] + '2' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2]}) + try: + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + for file_name in (file_name_1, file_name_2): + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name_1) + finally: + os.unlink(file_name_2) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_multiple_files_and_stream(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name_1 = 'test_logging-test_multiple_files_and_stream-1' + file_name_2 = file_name_1[:-1] + '2' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2, None]}) + try: + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + for file_name in (file_name_1, file_name_2): + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name_1) + finally: + os.unlink(file_name_2) + self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_handler_args(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_handler_args' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['RotatingFileHandler', [[file_name]]] + ]}) + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + close_handlers(logger) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_handler_args_kwargs(self): + out = StringIO() + err = StringIO() + stream = StringIO() + file_name = 'test_logging-test_handler_args_kwargs' + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['RotatingFileHandler', [[file_name], {'maxBytes': 1, 'backupCount': 1}]] + ]}) + try: + try: + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.error('Foo') + pl.error('Bar') + close_handlers(logger) + with codecs.open(file_name, encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$') + with codecs.open(file_name + '.1', encoding='utf-8') as fp: + self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$') + finally: + os.unlink(file_name + '.1') + finally: + os.unlink(file_name) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_logger_level(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + stream2 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'WARNING'], + ['logging.StreamHandler', [[stream2]], 'ERROR'], + ]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertMatches(stream1.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:Foo\n' + + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$' + )) + self.assertMatches(stream2.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_logger_level_not_overriding_default(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'DEBUG'], + ]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.debug('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertMatches(stream1.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_top_log_level(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'DEBUG'], + ], 'log_level': 'DEBUG'}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.debug('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertMatches(stream1.getvalue(), ( + '^' + TIMESTAMP_RE + ':DEBUG:__unknown__:Foo\n' + + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$' + )) + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_logger_format(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'], + ]}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + def test_top_log_format(self): + out = StringIO() + err = StringIO() + stream = StringIO() + stream1 = StringIO() + stream2 = StringIO() + + with replace_attr(sys, 'stdout', out, 'stderr', err): + common_config = finish_common_config('utf-8', {'log_file': [ + ['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'], + ['logging.StreamHandler', [[stream2]], 'WARNING'], + ], 'log_format': 'BAR'}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('Foo') + pl.error('Bar') + close_handlers(logger) + self.assertEqual(stream2.getvalue(), 'BAR\nBAR\n') + self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n') + self.assertEqual(stream.getvalue(), '') + self.assertEqual(err.getvalue(), '') + self.assertEqual(out.getvalue(), '') + + +class TestPowerlineLogger(TestRE): + def test_args_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('foo {0}', 'Test') + pl.warn('bar {0!r}', 'Test') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$' + )) + + def test_prefix_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.prefix = '1' + pl.warn('foo') + pl.prefix = '2' + pl.warn('bar') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:1:foo\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:2:bar\n$' + )) + + def test_kwargs_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('foo {arg}', arg='Test') + pl.warn('bar {arg!r}', arg='Test') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$' + )) + + def test_args_kwargs_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.warn('foo {0!r} {arg}', 'Test0', arg='Test') + pl.warn('bar {0} {arg!r}', 'Test0', arg='Test') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo u?\'Test0\' Test\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:bar Test0 u?\'Test\'\n$' + )) + + def test_exception_formatting(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + try: + raise ValueError('foo') + except ValueError: + pl.exception('Message') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Message\n' + + 'Traceback \\(most recent call last\\):\n' + + '(?: File ".*?", line \\d+, in \\w+\n [^\n]*\n)+' + + 'ValueError: foo\n$' + )) + + def test_levels(self): + stream = StringIO() + + common_config = finish_common_config('utf-8', {'log_level': 'DEBUG'}) + logger, pl, get_module_attr = create_logger(common_config, stream=stream) + pl.debug('1') + pl.info('2') + pl.warn('3') + pl.error('4') + pl.critical('5') + close_handlers(logger) + self.assertMatches(stream.getvalue(), ( + '^' + TIMESTAMP_RE + ':DEBUG:__unknown__:1\n' + + TIMESTAMP_RE + ':INFO:__unknown__:2\n' + + TIMESTAMP_RE + ':WARNING:__unknown__:3\n' + + TIMESTAMP_RE + ':ERROR:__unknown__:4\n' + + TIMESTAMP_RE + ':CRITICAL:__unknown__:5\n$' + )) + + +old_cwd = None + + +def setUpModule(): + global old_cwd + global __file__ + old_cwd = os.getcwd() + __file__ = os.path.abspath(__file__) + os.chdir(os.path.dirname(os.path.dirname(__file__))) + + +def tearDownModule(): + global old_cwd + os.chdir(old_cwd) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_provided_config_files.py b/tests/test_python/test_provided_config_files.py new file mode 100644 index 0000000..fd8b16e --- /dev/null +++ b/tests/test_python/test_provided_config_files.py @@ -0,0 +1,201 @@ +# vim:fileencoding=utf-8:noet + +'''Dynamic configuration files tests.''' + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os +import json +import logging + +import tests.modules.vim as vim_module + +from tests.modules.lib import Args, urllib_read, replace_attr +from tests.modules import TestCase + +from powerline import NotInterceptedError +from powerline.segments.common import wthr + + +VBLOCK = chr(ord('V') - 0x40) +SBLOCK = chr(ord('S') - 0x40) + + +class FailingLogger(logging.Logger): + def exception(self, *args, **kwargs): + super(FailingLogger, self).exception(*args, **kwargs) + raise NotInterceptedError('Unexpected exception occurred') + + +def get_logger(stream=None): + log_format = '%(asctime)s:%(levelname)s:%(message)s' + formatter = logging.Formatter(log_format) + + level = logging.WARNING + handler = logging.StreamHandler(stream) + handler.setLevel(level) + handler.setFormatter(formatter) + + logger = FailingLogger('powerline') + logger.setLevel(level) + logger.addHandler(handler) + return logger + + +class TestVimConfig(TestCase): + def test_vim(self): + from powerline.vim import VimPowerline + cfg_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'powerline', 'config_files') + buffers = ( + (('bufoptions',), {'buftype': 'help'}), + (('bufname', '[Command Line]'), {}), + (('bufoptions',), {'buftype': 'quickfix'}), + (('bufname', 'NERD_tree_1'), {}), + (('bufname', '__Gundo__'), {}), + (('bufname', '__Gundo_Preview__'), {}), + # No Command-T tests here: requires +ruby or emulation + # No tabline here: tablines are tested separately + ) + with open(os.path.join(cfg_path, 'config.json'), 'r') as f: + local_themes_raw = json.load(f)['ext']['vim']['local_themes'] + # Don’t run tests on external/plugin segments + local_themes = dict((k, v) for (k, v) in local_themes_raw.items()) + # See end of the buffers definition above for `- 2` + self.assertEqual(len(buffers), len(local_themes) - 2) + outputs = {} + i = 0 + + with vim_module._with('split'): + with VimPowerline(logger=get_logger()) as powerline: + def check_output(mode, args, kwargs): + if mode == 'nc': + window = vim_module.windows[0] + window_id = 2 + else: + vim_module._start_mode(mode) + window = vim_module.current.window + window_id = 1 + winnr = window.number + out = powerline.render(window, window_id, winnr) + if out in outputs: + self.fail('Duplicate in set #{0} ({1}) for mode {2!r} (previously defined in set #{3} ({4!r}) for mode {5!r})'.format(i, (args, kwargs), mode, *outputs[out])) + outputs[out] = (i, (args, kwargs), mode) + + with vim_module._with('bufname', '/tmp/foo.txt'): + out = powerline.render(vim_module.current.window, 1, vim_module.current.window.number, is_tabline=True) + outputs[out] = (-1, (None, None), 'tab') + with vim_module._with('globals', powerline_config_paths=[cfg_path]): + exclude = set(('no', 'v', 'V', VBLOCK, 's', 'S', SBLOCK, 'R', 'Rv', 'c', 'cv', 'ce', 'r', 'rm', 'r?', '!')) + try: + for mode in ['n', 'nc', 'no', 'v', 'V', VBLOCK, 's', 'S', SBLOCK, 'i', 'R', 'Rv', 'c', 'cv', 'ce', 'r', 'rm', 'r?', '!']: + check_output(mode, None, None) + for args, kwargs in buffers: + i += 1 + if mode in exclude: + continue + with vim_module._with(*args, **kwargs): + check_output(mode, args, kwargs) + finally: + vim_module._start_mode('n') + + @classmethod + def setUpClass(cls): + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path'))) + + @classmethod + def tearDownClass(cls): + sys.path.pop(0) + + +class TestConfig(TestCase): + def test_tmux(self): + from powerline.segments import common + from imp import reload + reload(common) + from powerline.shell import ShellPowerline + with replace_attr(common, 'urllib_read', urllib_read): + with ShellPowerline(Args(ext=['tmux']), logger=get_logger(), run_once=False) as powerline: + powerline.render() + with ShellPowerline(Args(ext=['tmux']), logger=get_logger(), run_once=False) as powerline: + powerline.render() + + def test_zsh(self): + from powerline.shell import ShellPowerline + args = Args(last_pipe_status=[1, 0], jobnum=0, ext=['shell'], renderer_module='.zsh') + segment_info = {'args': args} + with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline: + powerline.render(segment_info=segment_info) + with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline: + powerline.render(segment_info=segment_info) + segment_info['local_theme'] = 'select' + with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline: + powerline.render(segment_info=segment_info) + segment_info['local_theme'] = 'continuation' + segment_info['parser_state'] = 'if cmdsubst' + with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline: + powerline.render(segment_info=segment_info) + + def test_bash(self): + from powerline.shell import ShellPowerline + args = Args(last_exit_code=1, last_pipe_status=[], jobnum=0, ext=['shell'], renderer_module='.bash', config_override={'ext': {'shell': {'theme': 'default_leftonly'}}}) + with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline: + powerline.render(segment_info={'args': args}) + with ShellPowerline(args, logger=get_logger(), run_once=False) as powerline: + powerline.render(segment_info={'args': args}) + + def test_ipython(self): + from powerline.ipython import IPythonPowerline + + class IpyPowerline(IPythonPowerline): + config_paths = None + config_overrides = None + theme_overrides = {} + + segment_info = Args(prompt_count=1) + + with IpyPowerline(logger=get_logger(), renderer_module='.pre_5') as powerline: + for prompt_type in ['in', 'in2']: + powerline.render(is_prompt=True, matcher_info=prompt_type, segment_info=segment_info) + powerline.render(is_prompt=True, matcher_info=prompt_type, segment_info=segment_info) + with IpyPowerline(logger=get_logger(), renderer_module='.pre_5') as powerline: + for prompt_type in ['out', 'rewrite']: + powerline.render(is_prompt=False, matcher_info=prompt_type, segment_info=segment_info) + powerline.render(is_prompt=False, matcher_info=prompt_type, segment_info=segment_info) + + def test_wm(self): + from powerline.segments import common + from imp import reload + reload(common) + from powerline import Powerline + with replace_attr(wthr, 'urllib_read', urllib_read): + Powerline(logger=get_logger(), ext='wm', renderer_module='pango_markup', run_once=True).render() + reload(common) + + +old_cwd = None +saved_get_config_paths = None + + +def setUpModule(): + global old_cwd + global saved_get_config_paths + import powerline + saved_get_config_paths = powerline.get_config_paths + path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'powerline', 'config_files') + powerline.get_config_paths = lambda: [path] + old_cwd = os.getcwd() + + +def tearDownModule(): + global old_cwd + global saved_get_config_paths + import powerline + powerline.get_config_paths = saved_get_config_paths + os.chdir(old_cwd) + old_cwd = None + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_segments.py b/tests/test_python/test_segments.py new file mode 100644 index 0000000..3f09470 --- /dev/null +++ b/tests/test_python/test_segments.py @@ -0,0 +1,1711 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os + +from functools import partial +from collections import namedtuple +from time import sleep +from platform import python_implementation + +from powerline.segments import shell, tmux, pdb, i3wm +from powerline.lib.vcs import get_fallback_create_watcher +from powerline.lib.unicode import out_u + +import tests.modules.vim as vim_module + +from tests.modules.lib import (Args, urllib_read, replace_attr, new_module, + replace_module_module, replace_env, Pl) +from tests.modules import TestCase, SkipTest + + +def get_dummy_guess(**kwargs): + if 'directory' in kwargs: + def guess(path, create_watcher): + return Args(branch=lambda: out_u(os.path.basename(path)), **kwargs) + else: + def guess(path, create_watcher): + return Args(branch=lambda: out_u(os.path.basename(path)), directory=path, **kwargs) + return guess + + +class TestShell(TestCase): + def test_last_status(self): + pl = Pl() + segment_info = {'args': Args(last_exit_code=10)} + self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), [ + {'contents': '10', 'highlight_groups': ['exit_fail']} + ]) + segment_info['args'].last_exit_code = 0 + self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), None) + segment_info['args'].last_exit_code = None + self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), None) + segment_info['args'].last_exit_code = 'sigsegv' + self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), [ + {'contents': 'sigsegv', 'highlight_groups': ['exit_fail']} + ]) + segment_info['args'].last_exit_code = 'sigsegv+core' + self.assertEqual(shell.last_status(pl=pl, segment_info=segment_info), [ + {'contents': 'sigsegv+core', 'highlight_groups': ['exit_fail']} + ]) + + def test_last_pipe_status(self): + pl = Pl() + segment_info = {'args': Args(last_pipe_status=[], last_exit_code=0)} + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None) + segment_info['args'].last_pipe_status = [0, 0, 0] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None) + segment_info['args'].last_pipe_status = [0, 0] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None) + segment_info['args'].last_pipe_status = [0] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None) + segment_info['args'].last_pipe_status = [0, 2, 0] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + ]) + segment_info['args'].last_pipe_status = [2, 0, 0] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + ]) + segment_info['args'].last_pipe_status = [0, 0, 2] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + ]) + segment_info['args'].last_pipe_status = [2] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '2', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + ]) + segment_info['args'].last_pipe_status = [0, 'sigsegv', 'sigsegv+core'] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + {'contents': 'sigsegv', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + {'contents': 'sigsegv+core', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True} + ]) + segment_info['args'].last_pipe_status = [0, 'sigsegv', 0] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + {'contents': 'sigsegv', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True} + ]) + segment_info['args'].last_pipe_status = [0, 'sigsegv+core', 0] + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True}, + {'contents': 'sigsegv+core', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + {'contents': '0', 'highlight_groups': ['exit_success'], 'draw_inner_divider': True} + ]) + segment_info['args'].last_pipe_status = [] + segment_info['args'].last_exit_code = 5 + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '5', 'highlight_groups': ['exit_fail'], 'draw_inner_divider': True}, + ]) + + def test_jobnum(self): + pl = Pl() + segment_info = {'args': Args(jobnum=0)} + self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info), None) + self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=False), None) + self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=True), '0') + segment_info = {'args': Args(jobnum=1)} + self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info), '1') + self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=False), '1') + self.assertEqual(shell.jobnum(pl=pl, segment_info=segment_info, show_zero=True), '1') + + def test_continuation(self): + pl = Pl() + self.assertEqual(shell.continuation(pl=pl, segment_info={}), [{ + 'contents': '', + 'width': 'auto', + 'highlight_groups': ['continuation:current', 'continuation'], + }]) + segment_info = {'parser_state': 'if cmdsubst'} + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info), [ + { + 'contents': 'if', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation:current', 'continuation'], + 'width': 'auto', + 'align': 'l', + }, + ]) + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, right_align=True), [ + { + 'contents': 'if', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation:current', 'continuation'], + 'width': 'auto', + 'align': 'r', + }, + ]) + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=False), [ + { + 'contents': 'if', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation'], + }, + { + 'contents': 'cmdsubst', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation:current', 'continuation'], + 'width': 'auto', + 'align': 'l', + }, + ]) + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=False, right_align=True), [ + { + 'contents': 'if', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation'], + 'width': 'auto', + 'align': 'r', + }, + { + 'contents': 'cmdsubst', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation:current', 'continuation'], + }, + ]) + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=True, right_align=True), [ + { + 'contents': 'if', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation:current', 'continuation'], + 'width': 'auto', + 'align': 'r', + }, + ]) + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=True, right_align=True, renames={'if': 'IF'}), [ + { + 'contents': 'IF', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation:current', 'continuation'], + 'width': 'auto', + 'align': 'r', + }, + ]) + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info, omit_cmdsubst=True, right_align=True, renames={'if': None}), [ + { + 'contents': '', + 'highlight_groups': ['continuation:current', 'continuation'], + 'width': 'auto', + 'align': 'r', + }, + ]) + segment_info = {'parser_state': 'then then then cmdsubst'} + self.assertEqual(shell.continuation(pl=pl, segment_info=segment_info), [ + { + 'contents': 'then', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation'], + }, + { + 'contents': 'then', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation'], + }, + { + 'contents': 'then', + 'draw_inner_divider': True, + 'highlight_groups': ['continuation:current', 'continuation'], + 'width': 'auto', + 'align': 'l', + }, + ]) + + def test_cwd(self): + new_os = new_module('os', path=os.path, sep='/') + pl = Pl() + cwd = [None] + + def getcwd(): + wd = cwd[0] + if isinstance(wd, Exception): + raise wd + else: + return wd + + segment_info = {'getcwd': getcwd, 'home': None} + with replace_attr(shell, 'os', new_os): + cwd[0] = '/abc/def/ghi/foo/bar' + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'abc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'def', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + segment_info['home'] = '/abc/def/ghi' + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + segment_info.update(shortened_path='~foo/ghi') + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info), [ + {'contents': '~foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_shortened_path=False), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + segment_info.pop('shortened_path') + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3, shorten_home=False), [ + {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1), [ + {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis='---'), [ + {'contents': '---', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis=None), [ + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True), [ + {'contents': '.../', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis='---'), [ + {'contents': '---/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis=None), [ + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'fo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2, use_path_separator=True), [ + {'contents': '~/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'fo/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + cwd[0] = '/etc' + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + cwd[0] = '/' + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + ose = OSError() + ose.errno = 2 + cwd[0] = ose + self.assertEqual(shell.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [ + {'contents': '[not found]', 'divider_highlight_group': 'cwd:divider', 'highlight_groups': ['cwd:current_folder', 'cwd'], 'draw_inner_divider': True} + ]) + cwd[0] = OSError() + self.assertRaises(OSError, shell.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2) + cwd[0] = ValueError() + self.assertRaises(ValueError, shell.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2) + + +class TestTmux(TestCase): + def test_attached_clients(self): + def get_tmux_output(pl, cmd, *args): + if cmd == 'list-panes': + return 'session_name\n' + elif cmd == 'list-clients': + return '/dev/pts/2: 0 [191x51 xterm-256color] (utf8)\n/dev/pts/3: 0 [191x51 xterm-256color] (utf8)' + + pl = Pl() + with replace_attr(tmux, 'get_tmux_output', get_tmux_output): + self.assertEqual(tmux.attached_clients(pl=pl), '2') + self.assertEqual(tmux.attached_clients(pl=pl, minimum=3), None) + + +class TestCommon(TestCase): + @classmethod + def setUpClass(cls): + module = __import__(str('powerline.segments.common.{0}'.format(cls.module_name))) + cls.module = getattr(module.segments.common, str(cls.module_name)) + + +class TestNet(TestCommon): + module_name = 'net' + + def test_hostname(self): + pl = Pl() + with replace_env('SSH_CLIENT', '192.168.0.12 40921 22') as segment_info: + with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc'): + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc') + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True), 'abc') + with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc.mydomain'): + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc.mydomain') + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, exclude_domain=True), 'abc') + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True), 'abc.mydomain') + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True, exclude_domain=True), 'abc') + segment_info['environ'].pop('SSH_CLIENT') + with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc'): + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc') + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True), None) + with replace_module_module(self.module, 'socket', gethostname=lambda: 'abc.mydomain'): + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info), 'abc.mydomain') + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, exclude_domain=True), 'abc') + self.assertEqual(self.module.hostname(pl=pl, segment_info=segment_info, only_if_ssh=True, exclude_domain=True), None) + + def test_external_ip(self): + pl = Pl() + with replace_attr(self.module, 'urllib_read', urllib_read): + self.assertEqual(self.module.external_ip(pl=pl), [{'contents': '127.0.0.1', 'divider_highlight_group': 'background:divider'}]) + + def test_internal_ip(self): + try: + import netifaces + except ImportError: + raise SkipTest('netifaces module is not available') + pl = Pl() + addr = { + 'enp2s0': { + netifaces.AF_INET: [{'addr': '192.168.100.200'}], + netifaces.AF_INET6: [{'addr': 'feff::5446:5eff:fe5a:7777%enp2s0'}] + }, + 'lo': { + netifaces.AF_INET: [{'addr': '127.0.0.1'}], + netifaces.AF_INET6: [{'addr': '::1'}] + }, + 'teredo': { + netifaces.AF_INET6: [{'addr': 'feff::5446:5eff:fe5a:7777'}] + }, + } + interfaces = ['lo', 'enp2s0', 'teredo'] + with replace_module_module( + self.module, 'netifaces', + interfaces=(lambda: interfaces), + ifaddresses=(lambda interface: addr[interface]), + AF_INET=netifaces.AF_INET, + AF_INET6=netifaces.AF_INET6, + ): + self.assertEqual(self.module.internal_ip(pl=pl), '192.168.100.200') + self.assertEqual(self.module.internal_ip(pl=pl, interface='auto'), '192.168.100.200') + self.assertEqual(self.module.internal_ip(pl=pl, interface='lo'), '127.0.0.1') + self.assertEqual(self.module.internal_ip(pl=pl, interface='teredo'), None) + self.assertEqual(self.module.internal_ip(pl=pl, ipv=4), '192.168.100.200') + self.assertEqual(self.module.internal_ip(pl=pl, interface='auto', ipv=4), '192.168.100.200') + self.assertEqual(self.module.internal_ip(pl=pl, interface='lo', ipv=4), '127.0.0.1') + self.assertEqual(self.module.internal_ip(pl=pl, interface='teredo', ipv=4), None) + self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), 'feff::5446:5eff:fe5a:7777%enp2s0') + self.assertEqual(self.module.internal_ip(pl=pl, interface='auto', ipv=6), 'feff::5446:5eff:fe5a:7777%enp2s0') + self.assertEqual(self.module.internal_ip(pl=pl, interface='lo', ipv=6), '::1') + self.assertEqual(self.module.internal_ip(pl=pl, interface='teredo', ipv=6), 'feff::5446:5eff:fe5a:7777') + interfaces[1:2] = () + self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), 'feff::5446:5eff:fe5a:7777') + interfaces[1:2] = () + self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), '::1') + interfaces[:] = () + self.assertEqual(self.module.internal_ip(pl=pl, ipv=6), None) + + gateways = { + 'default': { + netifaces.AF_INET: ('192.168.100.1', 'enp2s0'), + netifaces.AF_INET6: ('feff::5446:5eff:fe5a:0001', 'enp2s0') + } + } + + with replace_module_module( + self.module, 'netifaces', + interfaces=(lambda: interfaces), + ifaddresses=(lambda interface: addr[interface]), + gateways=(lambda: gateways), + AF_INET=netifaces.AF_INET, + AF_INET6=netifaces.AF_INET6, + ): + # default gateway has specified address family + self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=4), '192.168.100.200') + self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=6), 'feff::5446:5eff:fe5a:7777%enp2s0') + # default gateway doesn't have specified address family + gateways['default'] = {} + self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=4), None) + self.assertEqual(self.module.internal_ip(pl=pl, interface='default_gateway', ipv=6), None) + + def test_network_load(self): + def gb(interface): + return None + + f = [gb] + + def _get_bytes(interface): + return f[0](interface) + + pl = Pl() + + with replace_attr(self.module, '_get_bytes', _get_bytes): + self.module.network_load.startup(pl=pl) + try: + self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), None) + sleep(self.module.network_load.interval) + self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), None) + while 'prev' not in self.module.network_load.interfaces.get('eth0', {}): + sleep(0.1) + self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), None) + + l = [0, 0] + + def gb2(interface): + l[0] += 1200 + l[1] += 2400 + return tuple(l) + f[0] = gb2 + + while not self.module.network_load.interfaces.get('eth0', {}).get('prev', (None, None))[1]: + sleep(0.1) + self.assertEqual(self.module.network_load(pl=pl, interface='eth0'), [ + {'divider_highlight_group': 'network_load:divider', 'contents': 'DL 1 KiB/s', 'highlight_groups': ['network_load_recv', 'network_load']}, + {'divider_highlight_group': 'network_load:divider', 'contents': 'UL 2 KiB/s', 'highlight_groups': ['network_load_sent', 'network_load']}, + ]) + self.assertEqual(self.module.network_load(pl=pl, interface='eth0', recv_format='r {value}', sent_format='s {value}'), [ + {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 KiB/s', 'highlight_groups': ['network_load_recv', 'network_load']}, + {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 KiB/s', 'highlight_groups': ['network_load_sent', 'network_load']}, + ]) + self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', suffix='bps', interface='eth0'), [ + {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 Kibps', 'highlight_groups': ['network_load_recv', 'network_load']}, + {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 Kibps', 'highlight_groups': ['network_load_sent', 'network_load']}, + ]) + self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', si_prefix=True, interface='eth0'), [ + {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 kB/s', 'highlight_groups': ['network_load_recv', 'network_load']}, + {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 kB/s', 'highlight_groups': ['network_load_sent', 'network_load']}, + ]) + self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', recv_max=0, interface='eth0'), [ + {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 KiB/s', 'highlight_groups': ['network_load_recv_gradient', 'network_load_gradient', 'network_load_recv', 'network_load'], 'gradient_level': 100}, + {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 KiB/s', 'highlight_groups': ['network_load_sent', 'network_load']}, + ]) + + class ApproxEqual(object): + def __eq__(self, i): + return abs(i - 50.0) < 1 + + self.assertEqual(self.module.network_load(pl=pl, recv_format='r {value}', sent_format='s {value}', sent_max=4800, interface='eth0'), [ + {'divider_highlight_group': 'network_load:divider', 'contents': 'r 1 KiB/s', 'highlight_groups': ['network_load_recv', 'network_load']}, + {'divider_highlight_group': 'network_load:divider', 'contents': 's 2 KiB/s', 'highlight_groups': ['network_load_sent_gradient', 'network_load_gradient', 'network_load_sent', 'network_load'], 'gradient_level': ApproxEqual()}, + ]) + finally: + self.module.network_load.shutdown() + + +class TestEnv(TestCommon): + module_name = 'env' + + def test_user(self): + new_os = new_module('os', getpid=lambda: 1) + + class Process(object): + def __init__(self, pid): + pass + + def username(self): + return 'def@DOMAIN.COM' + + if hasattr(self.module, 'psutil') and not callable(self.module.psutil.Process.username): + username = property(username) + + segment_info = {'environ': {}} + + def user(*args, **kwargs): + return self.module.user(pl=pl, segment_info=segment_info, *args, **kwargs) + + struct_passwd = namedtuple('struct_passwd', ('pw_name',)) + new_psutil = new_module('psutil', Process=Process) + new_pwd = new_module('pwd', getpwuid=lambda uid: struct_passwd(pw_name='def@DOMAIN.COM')) + new_getpass = new_module('getpass', getuser=lambda: 'def@DOMAIN.COM') + pl = Pl() + with replace_attr(self.module, 'pwd', new_pwd): + with replace_attr(self.module, 'getpass', new_getpass): + with replace_attr(self.module, 'os', new_os): + with replace_attr(self.module, 'psutil', new_psutil): + with replace_attr(self.module, '_geteuid', lambda: 5): + self.assertEqual(user(), [ + {'contents': 'def@DOMAIN.COM', 'highlight_groups': ['user']} + ]) + self.assertEqual(user(hide_user='abc'), [ + {'contents': 'def@DOMAIN.COM', 'highlight_groups': ['user']} + ]) + self.assertEqual(user(hide_domain=False), [ + {'contents': 'def@DOMAIN.COM', 'highlight_groups': ['user']} + ]) + self.assertEqual(user(hide_user='def@DOMAIN.COM'), None) + self.assertEqual(user(hide_domain=True), [ + {'contents': 'def', 'highlight_groups': ['user']} + ]) + with replace_attr(self.module, '_geteuid', lambda: 0): + self.assertEqual(user(), [ + {'contents': 'def', 'highlight_groups': ['superuser', 'user']} + ]) + + def test_cwd(self): + new_os = new_module('os', path=os.path, sep='/') + pl = Pl() + cwd = [None] + + def getcwd(): + wd = cwd[0] + if isinstance(wd, Exception): + raise wd + else: + return wd + + segment_info = {'getcwd': getcwd, 'home': None} + with replace_attr(self.module, 'os', new_os): + cwd[0] = '/abc/def/ghi/foo/bar' + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'abc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'def', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + segment_info['home'] = '/abc/def/ghi' + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3, shorten_home=False), [ + {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1), [ + {'contents': '...', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis='---'), [ + {'contents': '---', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, ellipsis=None), [ + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True), [ + {'contents': '.../', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis='---'), [ + {'contents': '---/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True, ellipsis=None), [ + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'fo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2, use_path_separator=True), [ + {'contents': '~/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'fo/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']} + ]) + cwd[0] = '/etc' + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'etc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + cwd[0] = '/' + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=False), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, use_path_separator=True), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_groups': ['cwd:current_folder', 'cwd']}, + ]) + ose = OSError() + ose.errno = 2 + cwd[0] = ose + self.assertEqual(self.module.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [ + {'contents': '[not found]', 'divider_highlight_group': 'cwd:divider', 'highlight_groups': ['cwd:current_folder', 'cwd'], 'draw_inner_divider': True} + ]) + cwd[0] = OSError() + self.assertRaises(OSError, self.module.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2) + cwd[0] = ValueError() + self.assertRaises(ValueError, self.module.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2) + + def test_virtualenv(self): + pl = Pl() + with replace_env('VIRTUAL_ENV', '/abc/def/ghi') as segment_info: + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'ghi') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), 'ghi') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + + segment_info['environ'].pop('VIRTUAL_ENV') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + + with replace_env('CONDA_DEFAULT_ENV', 'foo') as segment_info: + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'foo') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), 'foo') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + + segment_info['environ'].pop('CONDA_DEFAULT_ENV') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + + with replace_env('CONDA_DEFAULT_ENV', 'foo', environ={'VIRTUAL_ENV': '/sbc/def/ghi'}) as segment_info: + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'ghi') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), 'ghi') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), 'foo') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + + segment_info['environ'].pop('CONDA_DEFAULT_ENV') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'ghi') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), 'ghi') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + + def test_environment(self): + pl = Pl() + variable = 'FOO' + value = 'bar' + with replace_env(variable, value) as segment_info: + self.assertEqual(self.module.environment(pl=pl, segment_info=segment_info, variable=variable), value) + segment_info['environ'].pop(variable) + self.assertEqual(self.module.environment(pl=pl, segment_info=segment_info, variable=variable), None) + + +class TestVcs(TestCommon): + module_name = 'vcs' + + def test_branch(self): + pl = Pl() + create_watcher = get_fallback_create_watcher() + segment_info = {'getcwd': os.getcwd} + branch = partial(self.module.branch, pl=pl, create_watcher=create_watcher) + with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: None, directory='/tmp/tests')): + with replace_attr(self.module, 'tree_status', lambda repo, pl: None): + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{ + 'highlight_groups': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{ + 'contents': 'tests', + 'highlight_groups': ['branch_clean', 'branch'], + 'divider_highlight_group': None + }]) + with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: 'D ', directory='/tmp/tests')): + with replace_attr(self.module, 'tree_status', lambda repo, pl: 'D '): + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{ + 'highlight_groups': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{ + 'contents': 'tests', + 'highlight_groups': ['branch_dirty', 'branch'], + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [{ + 'highlight_groups': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + with replace_attr(self.module, 'guess', lambda path, create_watcher: None): + self.assertEqual(branch(segment_info=segment_info, status_colors=False), None) + with replace_attr(self.module, 'guess', get_dummy_guess(status=lambda: 'U')): + with replace_attr(self.module, 'tree_status', lambda repo, pl: 'U'): + self.assertEqual(branch(segment_info=segment_info, status_colors=False, ignore_statuses=['U']), [{ + 'highlight_groups': ['branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['DU']), [{ + 'highlight_groups': ['branch_dirty', 'branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [{ + 'highlight_groups': ['branch_dirty', 'branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['U']), [{ + 'highlight_groups': ['branch_clean', 'branch'], + 'contents': 'tests', + 'divider_highlight_group': None + }]) + + def test_stash(self): + pl = Pl() + create_watcher = get_fallback_create_watcher() + stash = partial(self.module.stash, pl=pl, create_watcher=create_watcher, segment_info={'getcwd': os.getcwd}) + + def forge_stash(n): + return replace_attr(self.module, 'guess', get_dummy_guess(stash=lambda: n, directory='/tmp/tests')) + + with forge_stash(0): + self.assertEqual(stash(), None) + with forge_stash(1): + self.assertEqual(stash(), [{ + 'highlight_groups': ['stash'], + 'contents': '1', + 'divider_highlight_group': None + }]) + with forge_stash(2): + self.assertEqual(stash(), [{ + 'highlight_groups': ['stash'], + 'contents': '2', + 'divider_highlight_group': None + }]) + + +class TestTime(TestCommon): + module_name = 'time' + + def test_date(self): + pl = Pl() + with replace_attr(self.module, 'datetime', Args(now=lambda: Args(strftime=lambda fmt: fmt))): + self.assertEqual(self.module.date(pl=pl), [{'contents': '%Y-%m-%d', 'highlight_groups': ['date'], 'divider_highlight_group': None}]) + self.assertEqual(self.module.date(pl=pl, format='%H:%M', istime=True), [{'contents': '%H:%M', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}]) + unicode_date = self.module.date(pl=pl, format='\u231a', istime=True) + expected_unicode_date = [{'contents': '\u231a', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}] + if python_implementation() == 'PyPy' and sys.version_info >= (3,): + if unicode_date != expected_unicode_date: + raise SkipTest('Dates do not match, see https://bitbucket.org/pypy/pypy/issues/2161/pypy3-strftime-does-not-accept-unicode') + self.assertEqual(unicode_date, expected_unicode_date) + + def test_fuzzy_time(self): + time = Args(hour=0, minute=45) + pl = Pl() + with replace_attr(self.module, 'datetime', Args(now=lambda: time)): + self.assertEqual(self.module.fuzzy_time(pl=pl), 'quarter to one') + time.hour = 23 + time.minute = 59 + self.assertEqual(self.module.fuzzy_time(pl=pl), 'round about midnight') + time.minute = 33 + self.assertEqual(self.module.fuzzy_time(pl=pl), 'twenty-five to twelve') + time.minute = 60 + self.assertEqual(self.module.fuzzy_time(pl=pl), 'twelve o\'clock') + time.minute = 33 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twenty-five to twelve') + time.minute = 60 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twelve o\'clock') + time.minute = 33 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twenty‐five to twelve') + time.minute = 60 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twelve o’clock') + + +class TestSys(TestCommon): + module_name = 'sys' + + def test_uptime(self): + pl = Pl() + with replace_attr(self.module, '_get_uptime', lambda: 259200): + self.assertEqual(self.module.uptime(pl=pl), [{'contents': '3d', 'divider_highlight_group': 'background:divider'}]) + with replace_attr(self.module, '_get_uptime', lambda: 93784): + self.assertEqual(self.module.uptime(pl=pl), [{'contents': '1d 2h 3m', 'divider_highlight_group': 'background:divider'}]) + self.assertEqual(self.module.uptime(pl=pl, shorten_len=4), [{'contents': '1d 2h 3m 4s', 'divider_highlight_group': 'background:divider'}]) + with replace_attr(self.module, '_get_uptime', lambda: 65536): + self.assertEqual(self.module.uptime(pl=pl), [{'contents': '18h 12m 16s', 'divider_highlight_group': 'background:divider'}]) + self.assertEqual(self.module.uptime(pl=pl, shorten_len=2), [{'contents': '18h 12m', 'divider_highlight_group': 'background:divider'}]) + self.assertEqual(self.module.uptime(pl=pl, shorten_len=1), [{'contents': '18h', 'divider_highlight_group': 'background:divider'}]) + + def _get_uptime(): + raise NotImplementedError + + with replace_attr(self.module, '_get_uptime', _get_uptime): + self.assertEqual(self.module.uptime(pl=pl), None) + + def test_system_load(self): + pl = Pl() + with replace_module_module(self.module, 'os', getloadavg=lambda: (7.5, 3.5, 1.5)): + with replace_attr(self.module, '_cpu_count', lambda: 2): + self.assertEqual(self.module.system_load(pl=pl), [ + {'contents': '7.5 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + {'contents': '3.5 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}, + {'contents': '1.5', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 0} + ]) + self.assertEqual(self.module.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1), [ + {'contents': '8 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + {'contents': '4 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + {'contents': '2', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0} + ]) + self.assertEqual(self.module.system_load(pl=pl, short=True), [ + {'contents': '7.5', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + ]) + self.assertEqual(self.module.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1, short=True), [ + {'contents': '8', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + ]) + + def test_cpu_load_percent(self): + try: + __import__('psutil') + except ImportError as e: + raise SkipTest('Failed to import psutil: {0}'.format(e)) + pl = Pl() + with replace_module_module(self.module, 'psutil', cpu_percent=lambda **kwargs: 52.3): + self.assertEqual(self.module.cpu_load_percent(pl=pl), [{ + 'contents': '52%', + 'gradient_level': 52.3, + 'highlight_groups': ['cpu_load_percent_gradient', 'cpu_load_percent'], + }]) + self.assertEqual(self.module.cpu_load_percent(pl=pl, format='{0:.1f}%'), [{ + 'contents': '52.3%', + 'gradient_level': 52.3, + 'highlight_groups': ['cpu_load_percent_gradient', 'cpu_load_percent'], + }]) + + +class TestWthr(TestCommon): + module_name = 'wthr' + + def test_weather(self): + pl = Pl() + with replace_attr(self.module, 'urllib_read', urllib_read): + self.assertEqual(self.module.weather(pl=pl), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + ]) + self.assertEqual(self.module.weather(pl=pl, temp_coldest=0, temp_hottest=100), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 14.0} + ]) + self.assertEqual(self.module.weather(pl=pl, temp_coldest=-100, temp_hottest=-50), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 100} + ]) + self.assertEqual(self.module.weather(pl=pl, icons={'blustery': 'o'}), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'o '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + ]) + self.assertEqual(self.module.weather(pl=pl, icons={'windy': 'x'}), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'x '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + ]) + self.assertEqual(self.module.weather(pl=pl, unit='F'), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '57°F', 'gradient_level': 62.857142857142854} + ]) + self.assertEqual(self.module.weather(pl=pl, unit='K'), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '287K', 'gradient_level': 62.857142857142854} + ]) + self.assertEqual(self.module.weather(pl=pl, temp_format='{temp:.1e}C'), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '1.4e+01C', 'gradient_level': 62.857142857142854} + ]) + with replace_attr(self.module, 'urllib_read', urllib_read): + self.module.weather.startup(pl=pl, location_query='Meppen,06,DE') + self.assertEqual(self.module.weather(pl=pl), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + ]) + self.assertEqual(self.module.weather(pl=pl, location_query='Moscow,RU'), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_fair_night', 'weather_condition_night', 'weather_conditions', 'weather'], 'contents': 'NIGHT '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '9°C', 'gradient_level': 55.714285714285715} + ]) + self.module.weather.shutdown() + + +class TestI3WM(TestCase): + @staticmethod + def get_workspaces(): + return iter([ + {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, + {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, + {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, + {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, + ]) + + def test_workspaces(self): + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): + segment_info = {} + + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [ + {'contents': '1: w1', 'highlight_groups': ['workspace']}, + {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=None), [ + {'contents': '1: w1', 'highlight_groups': ['workspace']}, + {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent']), [ + {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible']), [ + {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [ + {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, + {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent'], output='DVI01'), [ + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [ + {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3, output='LVDS1'), [ + {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, + ]) + segment_info['output'] = 'LVDS1' + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [ + {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [ + {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, + ]) + + def test_workspace(self): + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): + segment_info = {} + + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='1: w1'), [ + {'contents': '1: w1', 'highlight_groups': ['workspace']}, + ]) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='3: w3', strip=True), [ + {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='9: w9'), None) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info), [ + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + segment_info['workspace'] = next(self.get_workspaces()) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='4: w4'), [ + {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + ]) + self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [ + {'contents': 'w1', 'highlight_groups': ['workspace']}, + ]) + + def test_mode(self): + pl = Pl() + self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}), None) + self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}), 'test') + self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}, names={'default': 'test'}), 'test') + self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}, names={'default': 'test', 'test': 't'}), 't') + + def test_scratchpad(self): + class Conn(object): + def get_tree(self): + return self + + def descendents(self): + nodes_unfocused = [Args(focused = False)] + nodes_focused = [Args(focused = True)] + + workspace_scratch = lambda: Args(name='__i3_scratch') + workspace_noscratch = lambda: Args(name='2: www') + return [ + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused), + Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused), + ] + + pl = Pl() + with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()): + self.assertEqual(i3wm.scratchpad(pl=pl), [ + {'contents': 'O', 'highlight_groups': ['scratchpad']}, + {'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']}, + {'contents': 'O', 'highlight_groups': ['scratchpad']}, + {'contents': 'X', 'highlight_groups': ['scratchpad:visible', 'scratchpad']}, + {'contents': 'O', 'highlight_groups': ['scratchpad:focused', 'scratchpad']}, + {'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']}, + ]) + self.assertEqual(i3wm.scratchpad(pl=pl, icons={'changed': '-', 'fresh': 'o'}), [ + {'contents': 'o', 'highlight_groups': ['scratchpad']}, + {'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']}, + {'contents': 'o', 'highlight_groups': ['scratchpad']}, + {'contents': '-', 'highlight_groups': ['scratchpad:visible', 'scratchpad']}, + {'contents': 'o', 'highlight_groups': ['scratchpad:focused', 'scratchpad']}, + {'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']}, + ]) + + +class TestMail(TestCommon): + module_name = 'mail' + + def test_email_imap_alert(self): + # TODO + pass + + +class TestPlayers(TestCommon): + module_name = 'players' + + def test_now_playing(self): + # TODO + pass + + +class TestBat(TestCommon): + module_name = 'bat' + + def test_battery(self): + pl = Pl() + + def _get_battery_status(pl): + return 86, False + + with replace_attr(self.module, '_get_battery_status', _get_battery_status): + self.assertEqual(self.module.battery(pl=pl), [{ + 'contents': ' 86%', + 'highlight_groups': ['battery_gradient', 'battery'], + 'gradient_level': 14, + }]) + self.assertEqual(self.module.battery(pl=pl, format='{capacity:.2f}'), [{ + 'contents': '0.86', + 'highlight_groups': ['battery_gradient', 'battery'], + 'gradient_level': 14, + }]) + self.assertEqual(self.module.battery(pl=pl, steps=7), [{ + 'contents': ' 86%', + 'highlight_groups': ['battery_gradient', 'battery'], + 'gradient_level': 14, + }]) + self.assertEqual(self.module.battery(pl=pl, gamify=True), [ + { + 'contents': ' ', + 'draw_inner_divider': False, + 'highlight_groups': ['battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'], + 'gradient_level': 0 + }, + { + 'contents': 'OOOO', + 'draw_inner_divider': False, + 'highlight_groups': ['battery_full', 'battery_gradient', 'battery'], + 'gradient_level': 0 + }, + { + 'contents': 'O', + 'draw_inner_divider': False, + 'highlight_groups': ['battery_empty', 'battery_gradient', 'battery'], + 'gradient_level': 100 + } + ]) + self.assertEqual(self.module.battery(pl=pl, gamify=True, full_heart='+', empty_heart='-', steps='10'), [ + { + 'contents': ' ', + 'draw_inner_divider': False, + 'highlight_groups': ['battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'], + 'gradient_level': 0 + }, + { + 'contents': '++++++++', + 'draw_inner_divider': False, + 'highlight_groups': ['battery_full', 'battery_gradient', 'battery'], + 'gradient_level': 0 + }, + { + 'contents': '--', + 'draw_inner_divider': False, + 'highlight_groups': ['battery_empty', 'battery_gradient', 'battery'], + 'gradient_level': 100 + } + ]) + + def test_battery_with_ac_online(self): + pl = Pl() + + def _get_battery_status(pl): + return 86, True + + with replace_attr(self.module, '_get_battery_status', _get_battery_status): + self.assertEqual(self.module.battery(pl=pl, online='C', offline=' '), [ + { + 'contents': 'C 86%', + 'highlight_groups': ['battery_gradient', 'battery'], + 'gradient_level': 14, + }]) + + def test_battery_with_ac_offline(self): + pl = Pl() + + def _get_battery_status(pl): + return 86, False + + with replace_attr(self.module, '_get_battery_status', _get_battery_status): + self.assertEqual(self.module.battery(pl=pl, online='C', offline=' '), [ + { + 'contents': ' 86%', + 'highlight_groups': ['battery_gradient', 'battery'], + 'gradient_level': 14, + }]) + + +class TestVim(TestCase): + def test_mode(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'NORMAL') + self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info, override={'i': 'INS'}), 'NORMAL') + self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info, override={'n': 'NORM'}), 'NORM') + with vim_module._with('mode', 'i') as segment_info: + self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'INSERT') + with vim_module._with('mode', 'i\0') as segment_info: + self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'INSERT') + with vim_module._with('mode', chr(ord('V') - 0x40)) as segment_info: + self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info), 'V-BLCK') + self.assertEqual(self.vim.mode(pl=pl, segment_info=segment_info, override={'^V': 'VBLK'}), 'VBLK') + + def test_visual_range(self): + pl = Pl() + vr = partial(self.vim.visual_range, pl=pl) + vim_module.current.window.cursor = [0, 0] + try: + with vim_module._with('mode', 'i') as segment_info: + self.assertEqual(vr(segment_info=segment_info), '') + with vim_module._with('mode', '^V') as segment_info: + self.assertEqual(vr(segment_info=segment_info), '1 x 1') + with vim_module._with('vpos', line=5, col=5, off=0): + self.assertEqual(vr(segment_info=segment_info), '5 x 5') + with vim_module._with('vpos', line=5, col=4, off=0): + self.assertEqual(vr(segment_info=segment_info), '5 x 4') + with vim_module._with('mode', '^S') as segment_info: + self.assertEqual(vr(segment_info=segment_info), '1 x 1') + with vim_module._with('vpos', line=5, col=5, off=0): + self.assertEqual(vr(segment_info=segment_info), '5 x 5') + with vim_module._with('vpos', line=5, col=4, off=0): + self.assertEqual(vr(segment_info=segment_info), '5 x 4') + with vim_module._with('mode', 'V') as segment_info: + self.assertEqual(vr(segment_info=segment_info), 'L:1') + with vim_module._with('vpos', line=5, col=5, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + with vim_module._with('vpos', line=5, col=4, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + with vim_module._with('mode', 'S') as segment_info: + self.assertEqual(vr(segment_info=segment_info), 'L:1') + with vim_module._with('vpos', line=5, col=5, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + with vim_module._with('vpos', line=5, col=4, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + with vim_module._with('mode', 'v') as segment_info: + self.assertEqual(vr(segment_info=segment_info), 'C:1') + with vim_module._with('vpos', line=5, col=5, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + with vim_module._with('vpos', line=5, col=4, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + with vim_module._with('mode', 's') as segment_info: + self.assertEqual(vr(segment_info=segment_info), 'C:1') + with vim_module._with('vpos', line=5, col=5, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + with vim_module._with('vpos', line=5, col=4, off=0): + self.assertEqual(vr(segment_info=segment_info), 'L:5') + finally: + vim_module._close(1) + + def test_modified_indicator(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info), None) + segment_info['buffer'][0] = 'abc' + try: + self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info), '+') + self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info, text='-'), '-') + finally: + vim_module._bw(segment_info['bufnr']) + + def test_paste_indicator(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.paste_indicator(pl=pl, segment_info=segment_info), None) + with vim_module._with('options', paste=1): + self.assertEqual(self.vim.paste_indicator(pl=pl, segment_info=segment_info), 'PASTE') + self.assertEqual(self.vim.paste_indicator(pl=pl, segment_info=segment_info, text='P'), 'P') + + def test_readonly_indicator(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.readonly_indicator(pl=pl, segment_info=segment_info), None) + with vim_module._with('bufoptions', readonly=1): + self.assertEqual(self.vim.readonly_indicator(pl=pl, segment_info=segment_info), 'RO') + self.assertEqual(self.vim.readonly_indicator(pl=pl, segment_info=segment_info, text='L'), 'L') + + def test_file_scheme(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.file_scheme(pl=pl, segment_info=segment_info), None) + with vim_module._with('buffer', '/tmp/’’/abc') as segment_info: + self.assertEqual(self.vim.file_scheme(pl=pl, segment_info=segment_info), None) + with vim_module._with('buffer', 'zipfile:/tmp/abc.zip::abc/abc.vim') as segment_info: + self.assertEqual(self.vim.file_scheme(pl=pl, segment_info=segment_info), 'zipfile') + + def test_file_directory(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), None) + with replace_env('HOME', '/home/foo', os.environ): + with vim_module._with('buffer', '/tmp/’’/abc') as segment_info: + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/’’/') + with vim_module._with('buffer', b'/tmp/\xFF\xFF/abc') as segment_info: + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/<ff><ff>/') + with vim_module._with('buffer', '/tmp/abc') as segment_info: + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/') + os.environ['HOME'] = '/tmp' + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '~/') + with vim_module._with('buffer', 'zipfile:/tmp/abc.zip::abc/abc.vim') as segment_info: + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=False), 'zipfile:/tmp/abc.zip::abc/') + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=True), '/tmp/abc.zip::abc/') + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/abc.zip::abc/') + os.environ['HOME'] = '/tmp' + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=False), 'zipfile:/tmp/abc.zip::abc/') + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info, remove_scheme=True), '/tmp/abc.zip::abc/') + self.assertEqual(self.vim.file_directory(pl=pl, segment_info=segment_info), '/tmp/abc.zip::abc/') + + def test_file_name(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), None) + self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info, display_no_file=True), [ + {'contents': '[No file]', 'highlight_groups': ['file_name_no_file', 'file_name']} + ]) + self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info, display_no_file=True, no_file_text='X'), [ + {'contents': 'X', 'highlight_groups': ['file_name_no_file', 'file_name']} + ]) + with vim_module._with('buffer', '/tmp/abc') as segment_info: + self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), 'abc') + with vim_module._with('buffer', '/tmp/’’') as segment_info: + self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), '’’') + with vim_module._with('buffer', b'/tmp/\xFF\xFF') as segment_info: + self.assertEqual(self.vim.file_name(pl=pl, segment_info=segment_info), '<ff><ff>') + + def test_file_size(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.file_size(pl=pl, segment_info=segment_info), '0 B') + with vim_module._with( + 'buffer', + os.path.join( + os.path.dirname(os.path.dirname(__file__)), 'empty') + ) as segment_info: + self.assertEqual(self.vim.file_size(pl=pl, segment_info=segment_info), '0 B') + + def test_file_opts(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.file_format(pl=pl, segment_info=segment_info), [ + {'divider_highlight_group': 'background:divider', 'contents': 'unix'} + ]) + self.assertEqual(self.vim.file_encoding(pl=pl, segment_info=segment_info), [ + {'divider_highlight_group': 'background:divider', 'contents': 'utf-8'} + ]) + self.assertEqual(self.vim.file_type(pl=pl, segment_info=segment_info), None) + with vim_module._with('bufoptions', filetype='python'): + self.assertEqual(self.vim.file_type(pl=pl, segment_info=segment_info), [ + {'divider_highlight_group': 'background:divider', 'contents': 'python'} + ]) + + def test_window_title(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.window_title(pl=pl, segment_info=segment_info), None) + with vim_module._with('wvars', quickfix_title='Abc'): + self.assertEqual(self.vim.window_title(pl=pl, segment_info=segment_info), 'Abc') + + def test_line_percent(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + segment_info['buffer'][0:-1] = [str(i) for i in range(100)] + try: + self.assertEqual(self.vim.line_percent(pl=pl, segment_info=segment_info), '1') + vim_module._set_cursor(50, 0) + self.assertEqual(self.vim.line_percent(pl=pl, segment_info=segment_info), '50') + self.assertEqual(self.vim.line_percent(pl=pl, segment_info=segment_info, gradient=True), [ + {'contents': '50', 'highlight_groups': ['line_percent_gradient', 'line_percent'], 'gradient_level': 50 * 100.0 / 101} + ]) + finally: + vim_module._bw(segment_info['bufnr']) + + def test_line_count(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + segment_info['buffer'][0:-1] = [str(i) for i in range(99)] + try: + self.assertEqual(self.vim.line_count(pl=pl, segment_info=segment_info), '100') + vim_module._set_cursor(50, 0) + self.assertEqual(self.vim.line_count(pl=pl, segment_info=segment_info), '100') + finally: + vim_module._bw(segment_info['bufnr']) + + def test_position(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + try: + segment_info['buffer'][0:-1] = [str(i) for i in range(99)] + vim_module._set_cursor(49, 0) + self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info), '50%') + self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, gradient=True), [ + {'contents': '50%', 'highlight_groups': ['position_gradient', 'position'], 'gradient_level': 50.0} + ]) + vim_module._set_cursor(0, 0) + self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info), 'Top') + vim_module._set_cursor(97, 0) + self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, position_strings={'top': 'Comienzo', 'bottom': 'Final', 'all': 'Todo'}), 'Final') + segment_info['buffer'][0:-1] = [str(i) for i in range(2)] + vim_module._set_cursor(0, 0) + self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, position_strings={'top': 'Comienzo', 'bottom': 'Final', 'all': 'Todo'}), 'Todo') + self.assertEqual(self.vim.position(pl=pl, segment_info=segment_info, gradient=True), [ + {'contents': 'All', 'highlight_groups': ['position_gradient', 'position'], 'gradient_level': 0.0} + ]) + finally: + vim_module._bw(segment_info['bufnr']) + + def test_cursor_current(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.line_current(pl=pl, segment_info=segment_info), '1') + self.assertEqual(self.vim.col_current(pl=pl, segment_info=segment_info), '1') + self.assertEqual(self.vim.virtcol_current(pl=pl, segment_info=segment_info), [{ + 'highlight_groups': ['virtcol_current_gradient', 'virtcol_current', 'col_current'], 'contents': '1', 'gradient_level': 100.0 / 80, + }]) + self.assertEqual(self.vim.virtcol_current(pl=pl, segment_info=segment_info, gradient=False), [{ + 'highlight_groups': ['virtcol_current', 'col_current'], 'contents': '1', + }]) + + def test_modified_buffers(self): + pl = Pl() + self.assertEqual(self.vim.modified_buffers(pl=pl), None) + + def test_branch(self): + pl = Pl() + create_watcher = get_fallback_create_watcher() + branch = partial(self.vim.branch, pl=pl, create_watcher=create_watcher) + with vim_module._with('buffer', '/foo') as segment_info: + with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: None)): + with replace_attr(self.vcs, 'tree_status', lambda repo, pl: None): + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_clean', 'branch'], 'contents': 'foo'} + ]) + with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: 'DU')): + with replace_attr(self.vcs, 'tree_status', lambda repo, pl: 'DU'): + self.assertEqual(branch(segment_info=segment_info, status_colors=False), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_dirty', 'branch'], 'contents': 'foo'} + ]) + with replace_attr(self.vcs, 'guess', get_dummy_guess(status=lambda: 'U')): + with replace_attr(self.vcs, 'tree_status', lambda repo, pl: 'U'): + self.assertEqual(branch(segment_info=segment_info, status_colors=False, ignore_statuses=['U']), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['DU']), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_dirty', 'branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_dirty', 'branch'], 'contents': 'foo'} + ]) + self.assertEqual(branch(segment_info=segment_info, status_colors=True, ignore_statuses=['U']), [ + {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_clean', 'branch'], 'contents': 'foo'} + ]) + + def test_stash(self): + pl = Pl() + create_watcher = get_fallback_create_watcher() + with vim_module._with('buffer', '/foo') as segment_info: + stash = partial(self.vim.stash, pl=pl, create_watcher=create_watcher, segment_info=segment_info) + + def forge_stash(n): + return replace_attr(self.vcs, 'guess', get_dummy_guess(stash=lambda: n)) + + with forge_stash(0): + self.assertEqual(stash(), None) + with forge_stash(1): + self.assertEqual(stash(), [{ + 'divider_highlight_group': 'stash:divider', + 'highlight_groups': ['stash'], + 'contents': '1' + }]) + with forge_stash(2): + self.assertEqual(stash(), [{ + 'divider_highlight_group': 'stash:divider', + 'highlight_groups': ['stash'], + 'contents': '2' + }]) + + def test_file_vcs_status(self): + pl = Pl() + create_watcher = get_fallback_create_watcher() + file_vcs_status = partial(self.vim.file_vcs_status, pl=pl, create_watcher=create_watcher) + with vim_module._with('buffer', '/foo') as segment_info: + with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda file: 'M')): + self.assertEqual(file_vcs_status(segment_info=segment_info), [ + {'highlight_groups': ['file_vcs_status_M', 'file_vcs_status'], 'contents': 'M'} + ]) + with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda file: None)): + self.assertEqual(file_vcs_status(segment_info=segment_info), None) + with vim_module._with('buffer', '/bar') as segment_info: + with vim_module._with('bufoptions', buftype='nofile'): + with replace_attr(self.vim, 'guess', get_dummy_guess(status=lambda file: 'M')): + self.assertEqual(file_vcs_status(segment_info=segment_info), None) + + def test_trailing_whitespace(self): + pl = Pl() + with vim_module._with('buffer', 'tws') as segment_info: + trailing_whitespace = partial(self.vim.trailing_whitespace, pl=pl, segment_info=segment_info) + self.assertEqual(trailing_whitespace(), None) + self.assertEqual(trailing_whitespace(), None) + vim_module.current.buffer[0] = ' ' + self.assertEqual(trailing_whitespace(), [{ + 'highlight_groups': ['trailing_whitespace', 'warning'], + 'contents': '1', + }]) + self.assertEqual(trailing_whitespace(), [{ + 'highlight_groups': ['trailing_whitespace', 'warning'], + 'contents': '1', + }]) + vim_module.current.buffer[0] = '' + self.assertEqual(trailing_whitespace(), None) + self.assertEqual(trailing_whitespace(), None) + + def test_tabnr(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.tabnr(pl=pl, segment_info=segment_info, show_current=True), '1') + self.assertEqual(self.vim.tabnr(pl=pl, segment_info=segment_info, show_current=False), None) + + def test_tab(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.tab(pl=pl, segment_info=segment_info), [{ + 'contents': None, + 'literal_contents': (0, '%1T'), + }]) + self.assertEqual(self.vim.tab(pl=pl, segment_info=segment_info, end=True), [{ + 'contents': None, + 'literal_contents': (0, '%T'), + }]) + + def test_bufnr(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.bufnr(pl=pl, segment_info=segment_info, show_current=True), str(segment_info['bufnr'])) + self.assertEqual(self.vim.bufnr(pl=pl, segment_info=segment_info, show_current=False), None) + + def test_winnr(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.winnr(pl=pl, segment_info=segment_info, show_current=True), str(segment_info['winnr'])) + self.assertEqual(self.vim.winnr(pl=pl, segment_info=segment_info, show_current=False), None) + + def test_segment_info(self): + pl = Pl() + with vim_module._with('tabpage'): + with vim_module._with('buffer', '1') as segment_info: + self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), None) + vim_module.current.buffer[0] = ' ' + self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), [{ + 'contents': '+', + 'highlight_groups': ['tab_modified_indicator', 'modified_indicator'], + }]) + vim_module._undo() + self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), None) + old_buffer = vim_module.current.buffer + vim_module._new('2') + segment_info = vim_module._get_segment_info() + self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), None) + old_buffer[0] = ' ' + self.assertEqual(self.vim.modified_indicator(pl=pl, segment_info=segment_info), None) + self.assertEqual(self.vim.tab_modified_indicator(pl=pl, segment_info=segment_info), [{ + 'contents': '+', + 'highlight_groups': ['tab_modified_indicator', 'modified_indicator'], + }]) + + def test_csv_col_current(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + + def csv_col_current(**kwargs): + self.vim.csv_cache and self.vim.csv_cache.clear() + return self.vim.csv_col_current(pl=pl, segment_info=segment_info, **kwargs) + + buffer = segment_info['buffer'] + try: + self.assertEqual(csv_col_current(), None) + buffer.options['filetype'] = 'csv' + self.assertEqual(csv_col_current(), None) + buffer[:] = ['1;2;3', '4;5;6'] + vim_module._set_cursor(1, 1) + self.assertEqual(csv_col_current(), [{ + 'contents': '1', 'highlight_groups': ['csv:column_number', 'csv'] + }]) + vim_module._set_cursor(2, 3) + self.assertEqual(csv_col_current(), [{ + 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv'] + }]) + vim_module._set_cursor(2, 3) + self.assertEqual(csv_col_current(display_name=True), [{ + 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv'] + }, { + 'contents': ' (2)', 'highlight_groups': ['csv:column_name', 'csv'] + }]) + buffer[:0] = ['Foo;Bar;Baz'] + vim_module._set_cursor(2, 3) + self.assertEqual(csv_col_current(), [{ + 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv'] + }, { + 'contents': ' (Bar)', 'highlight_groups': ['csv:column_name', 'csv'] + }]) + if sys.version_info < (2, 7): + raise SkipTest('csv module in Python-2.6 does not handle multiline csv files well') + buffer[len(buffer):] = ['1;"bc', 'def', 'ghi', 'jkl";3'] + vim_module._set_cursor(5, 1) + self.assertEqual(csv_col_current(), [{ + 'contents': '2', 'highlight_groups': ['csv:column_number', 'csv'] + }, { + 'contents': ' (Bar)', 'highlight_groups': ['csv:column_name', 'csv'] + }]) + vim_module._set_cursor(7, 6) + self.assertEqual(csv_col_current(), [{ + 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv'] + }, { + 'contents': ' (Baz)', 'highlight_groups': ['csv:column_name', 'csv'] + }]) + self.assertEqual(csv_col_current(name_format=' ({column_name:.1})'), [{ + 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv'] + }, { + 'contents': ' (B)', 'highlight_groups': ['csv:column_name', 'csv'] + }]) + self.assertEqual(csv_col_current(display_name=True, name_format=' ({column_name:.1})'), [{ + 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv'] + }, { + 'contents': ' (B)', 'highlight_groups': ['csv:column_name', 'csv'] + }]) + self.assertEqual(csv_col_current(display_name=False, name_format=' ({column_name:.1})'), [{ + 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv'] + }]) + self.assertEqual(csv_col_current(display_name=False), [{ + 'contents': '3', 'highlight_groups': ['csv:column_number', 'csv'] + }]) + finally: + vim_module._bw(segment_info['bufnr']) + + @classmethod + def setUpClass(cls): + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path'))) + from powerline.segments import vim + cls.vim = vim + from powerline.segments.common import vcs + cls.vcs = vcs + + @classmethod + def tearDownClass(cls): + sys.path.pop(0) + + +class TestPDB(TestCase): + def test_current_line(self): + pl = Pl() + self.assertEqual(pdb.current_line(pl=pl, segment_info={'curframe': Args(f_lineno=10)}), '10') + + def test_current_file(self): + pl = Pl() + cf = lambda **kwargs: pdb.current_file( + pl=pl, + segment_info={'curframe': Args(f_code=Args(co_filename='/tmp/abc.py'))}, + **kwargs + ) + self.assertEqual(cf(), 'abc.py') + self.assertEqual(cf(basename=True), 'abc.py') + self.assertEqual(cf(basename=False), '/tmp/abc.py') + + def test_current_code_name(self): + pl = Pl() + ccn = lambda **kwargs: pdb.current_code_name( + pl=pl, + segment_info={'curframe': Args(f_code=Args(co_name='<module>'))}, + **kwargs + ) + self.assertEqual(ccn(), '<module>') + + def test_current_context(self): + pl = Pl() + cc = lambda **kwargs: pdb.current_context( + pl=pl, + segment_info={'curframe': Args(f_code=Args(co_name='<module>', co_filename='/tmp/abc.py'))}, + **kwargs + ) + self.assertEqual(cc(), 'abc.py') + + def test_stack_depth(self): + pl = Pl() + sd = lambda **kwargs: pdb.stack_depth( + pl=pl, + segment_info={'pdb': Args(stack=[1, 2, 3]), 'initial_stack_length': 1}, + **kwargs + ) + self.assertEqual(sd(), '2') + self.assertEqual(sd(full_stack=False), '2') + self.assertEqual(sd(full_stack=True), '3') + + +old_cwd = None + + +def setUpModule(): + global old_cwd + global __file__ + old_cwd = os.getcwd() + __file__ = os.path.abspath(__file__) + os.chdir(os.path.dirname(os.path.dirname(__file__))) + + +def tearDownModule(): + global old_cwd + os.chdir(old_cwd) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_selectors.py b/tests/test_python/test_selectors.py new file mode 100644 index 0000000..74ace8d --- /dev/null +++ b/tests/test_python/test_selectors.py @@ -0,0 +1,36 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import sys + +from functools import partial + +import tests.modules.vim as vim_module + +from tests.modules.lib import Pl +from tests.modules import TestCase + + +class TestVim(TestCase): + def test_single_tab(self): + pl = Pl() + single_tab = partial(self.vim.single_tab, pl=pl, segment_info=None, mode=None) + with vim_module._with('tabpage'): + self.assertEqual(single_tab(), False) + self.assertEqual(single_tab(), True) + + @classmethod + def setUpClass(cls): + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vim_sys_path'))) + from powerline.selectors import vim + cls.vim = vim + + @classmethod + def tearDownClass(cls): + sys.path.pop(0) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_python/test_watcher.py b/tests/test_python/test_watcher.py new file mode 100644 index 0000000..a246d0b --- /dev/null +++ b/tests/test_python/test_watcher.py @@ -0,0 +1,245 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import shutil +import os + +from time import sleep +from functools import partial +from errno import ENOENT + +from powerline.lib.watcher import create_file_watcher, create_tree_watcher, INotifyError +from powerline.lib.watcher.uv import UvNotFound +from powerline import get_fallback_logger +from powerline.lib.monotonic import monotonic + +from tests.modules import TestCase, SkipTest + + +INOTIFY_DIR = 'inotify' + os.path.basename(os.environ.get('PYTHON', '')) + + +def clear_dir(dir): + for root, dirs, files in list(os.walk(dir, topdown=False)): + for f in files: + os.remove(os.path.join(root, f)) + for d in dirs: + os.rmdir(os.path.join(root, d)) + + +def set_watcher_tests(l): + byte_tests = (('bytes', True), ('unicode', False)) + + for btn, use_bytes in byte_tests: + def test_inotify_file_watcher(self, use_bytes=use_bytes): + try: + w = create_file_watcher(pl=get_fallback_logger(), watcher_type='inotify') + except INotifyError: + raise SkipTest('This test is not suitable for a stat based file watcher') + self.do_test_file_watcher(w, use_bytes) + + def test_uv_file_watcher(self, use_bytes=use_bytes): + raise SkipTest('Uv watcher tests are not stable') + try: + w = create_file_watcher(pl=get_fallback_logger(), watcher_type='uv') + except UvNotFound: + raise SkipTest('Pyuv is not available') + self.do_test_file_watcher(w, use_bytes) + + def test_inotify_tree_watcher(self, use_bytes=use_bytes): + try: + tw = create_tree_watcher(get_fallback_logger(), watcher_type='inotify') + except INotifyError: + raise SkipTest('INotify is not available') + self.do_test_tree_watcher(tw, use_bytes) + + def test_uv_tree_watcher(self, use_bytes=use_bytes): + raise SkipTest('Uv watcher tests are not stable') + try: + tw = create_tree_watcher(get_fallback_logger(), 'uv') + except UvNotFound: + raise SkipTest('Pyuv is not available') + self.do_test_tree_watcher(tw, use_bytes) + + def test_inotify_file_watcher_is_watching(self, use_bytes=use_bytes): + try: + w = create_file_watcher(pl=get_fallback_logger(), watcher_type='inotify') + except INotifyError: + raise SkipTest('INotify is not available') + self.do_test_file_watcher_is_watching(w, use_bytes) + + def test_stat_file_watcher_is_watching(self, use_bytes=use_bytes): + w = create_file_watcher(pl=get_fallback_logger(), watcher_type='stat') + self.do_test_file_watcher_is_watching(w, use_bytes) + + def test_uv_file_watcher_is_watching(self, use_bytes=use_bytes): + try: + w = create_file_watcher(pl=get_fallback_logger(), watcher_type='uv') + except UvNotFound: + raise SkipTest('Pyuv is not available') + self.do_test_file_watcher_is_watching(w, use_bytes) + + for wt in ('uv', 'inotify'): + l['test_{0}_file_watcher_{1}'.format(wt, btn)] = locals()['test_{0}_file_watcher'.format(wt)] + l['test_{0}_tree_watcher_{1}'.format(wt, btn)] = locals()['test_{0}_tree_watcher'.format(wt)] + l['test_{0}_file_watcher_is_watching_{1}'.format(wt, btn)] = ( + locals()['test_{0}_file_watcher_is_watching'.format(wt)]) + l['test_{0}_file_watcher_is_watching_{1}'.format('stat', btn)] = ( + locals()['test_{0}_file_watcher_is_watching'.format('stat')]) + + +class TestFilesystemWatchers(TestCase): + def do_test_for_change(self, watcher, path): + st = monotonic() + while monotonic() - st < 1: + if watcher(path): + return + sleep(0.1) + self.fail('The change to {0} was not detected'.format(path)) + + def do_test_file_watcher(self, w, use_bytes=False): + try: + f1, f2, f3 = map(lambda x: os.path.join(INOTIFY_DIR, 'file%d' % x), (1, 2, 3)) + ne = os.path.join(INOTIFY_DIR, 'notexists') + if use_bytes: + f1 = f1.encode('utf-8') + f2 = f2.encode('utf-8') + f3 = f3.encode('utf-8') + ne = ne.encode('utf-8') + with open(f1, 'wb'): + with open(f2, 'wb'): + with open(f3, 'wb'): + pass + self.assertRaises(OSError, w, ne) + self.assertTrue(w(f1)) + self.assertTrue(w(f2)) + os.utime(f1, None), os.utime(f2, None) + self.do_test_for_change(w, f1) + self.do_test_for_change(w, f2) + # Repeat once + os.utime(f1, None), os.utime(f2, None) + self.do_test_for_change(w, f1) + self.do_test_for_change(w, f2) + # Check that no false changes are reported + self.assertFalse(w(f1), 'Spurious change detected') + self.assertFalse(w(f2), 'Spurious change detected') + # Check that open the file with 'w' triggers a change + with open(f1, 'wb'): + with open(f2, 'wb'): + pass + self.do_test_for_change(w, f1) + self.do_test_for_change(w, f2) + # Check that writing to a file with 'a' triggers a change + with open(f1, 'ab') as f: + f.write(b'1') + self.do_test_for_change(w, f1) + # Check that deleting a file registers as a change + os.unlink(f1) + self.do_test_for_change(w, f1) + # Test that changing the inode of a file does not cause it to stop + # being watched + os.rename(f3, f2) + self.do_test_for_change(w, f2) + self.assertFalse(w(f2), 'Spurious change detected') + os.utime(f2, None) + self.do_test_for_change(w, f2) + finally: + clear_dir(INOTIFY_DIR) + + def do_test_tree_watcher(self, tw, use_bytes=False): + try: + inotify_dir = INOTIFY_DIR + subdir = os.path.join(inotify_dir, 'subdir') + t1 = os.path.join(inotify_dir, 'tree1') + ts1 = os.path.join(subdir, 'tree1') + suffix = '1' + f = os.path.join(subdir, 'f') + if use_bytes: + inotify_dir = inotify_dir.encode('utf-8') + subdir = subdir.encode('utf-8') + t1 = t1.encode('utf-8') + ts1 = ts1.encode('utf-8') + suffix = suffix.encode('utf-8') + f = f.encode('utf-8') + os.mkdir(subdir) + try: + if tw.watch(inotify_dir).is_dummy: + raise SkipTest('No tree watcher available') + except UvNotFound: + raise SkipTest('Pyuv is not available') + except INotifyError: + raise SkipTest('INotify is not available') + self.assertTrue(tw(inotify_dir)) + self.assertFalse(tw(inotify_dir)) + changed = partial(self.do_test_for_change, tw, inotify_dir) + open(t1, 'w').close() + changed() + open(ts1, 'w').close() + changed() + os.unlink(ts1) + changed() + os.rmdir(subdir) + changed() + os.mkdir(subdir) + changed() + os.rename(subdir, subdir + suffix) + changed() + shutil.rmtree(subdir + suffix) + changed() + os.mkdir(subdir) + open(f, 'w').close() + changed() + with open(f, 'a') as s: + s.write(' ') + changed() + os.rename(f, f + suffix) + changed() + finally: + clear_dir(inotify_dir) + + def do_test_file_watcher_is_watching(self, w, use_bytes=False): + try: + f1, f2, f3 = map(lambda x: os.path.join(INOTIFY_DIR, 'file%d' % x), (1, 2, 3)) + ne = os.path.join(INOTIFY_DIR, 'notexists') + if use_bytes: + f1 = f1.encode('utf-8') + f2 = f2.encode('utf-8') + f3 = f3.encode('utf-8') + ne = ne.encode('utf-8') + with open(f1, 'wb'): + with open(f2, 'wb'): + with open(f3, 'wb'): + pass + self.assertRaises(OSError, w, ne) + try: + w(ne) + except OSError as e: + self.assertEqual(e.errno, ENOENT) + self.assertTrue(w(f1)) + self.assertFalse(w.is_watching(ne)) + self.assertTrue(w.is_watching(f1)) + self.assertFalse(w.is_watching(f2)) + finally: + clear_dir(INOTIFY_DIR) + + set_watcher_tests(locals()) + + +old_cwd = None + + +def setUpModule(): + global old_cwd + old_cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.dirname(__file__))) + os.mkdir(INOTIFY_DIR) + + +def tearDownModule(): + shutil.rmtree(INOTIFY_DIR) + os.chdir(old_cwd) + + +if __name__ == '__main__': + from tests.modules import main + main() diff --git a/tests/test_shells/bgscript.sh b/tests/test_shells/bgscript.sh new file mode 100755 index 0000000..71886e6 --- /dev/null +++ b/tests/test_shells/bgscript.sh @@ -0,0 +1,5 @@ +#!/bin/sh +echo $$ > pid +while true ; do + sleep 0.1s +done diff --git a/tests/test_shells/inputs/bash b/tests/test_shells/inputs/bash new file mode 100644 index 0000000..1b68b6f --- /dev/null +++ b/tests/test_shells/inputs/bash @@ -0,0 +1,69 @@ +set_theme_option() { + export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2" +} +set_theme() { + export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" +} +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +ABOVE_LEFT='[{ + "left": [ + { + "function": "powerline.segments.common.env.environment", + "args": {"variable": "DISPLAYED_ENV_VAR"} + } + ] +}]' +ABOVE_FULL='[{ + "left": [ + { + "type": "string", + "name": "background", + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + { + "function": "powerline.segments.common.env.environment", + "args": {"variable": "DISPLAYED_ENV_VAR"} + } + ] +}]' +set_theme default_leftonly +export VIRTUAL_ENV= +source powerline/bindings/bash/powerline.sh +cd "$TEST_ROOT"/3rd +cd .git +cd .. +VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +VIRTUAL_ENV= +bgscript.sh & waitpid.sh +false +kill `cat pid` ; sleep 1s +set_theme_option default_leftonly.segment_data.hostname.display false +set_theme_option default_leftonly.segment_data.user.display false +echo ' +abc +def +' +cd "$DIR1" +cd ../"$DIR2" +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode!»' +(exit 42)|(exit 43) +set_theme_option default_leftonly.segments.above "$ABOVE_LEFT" +export DISPLAYED_ENV_VAR=foo +unset DISPLAYED_ENV_VAR +set_theme_option default_leftonly.segments.above "$ABOVE_FULL" +export DISPLAYED_ENV_VAR=foo +unset DISPLAYED_ENV_VAR +set_theme_option default_leftonly.segments.above +set_theme_option default_leftonly.dividers.left.hard \$ABC +false +true is the last line +exit diff --git a/tests/test_shells/inputs/busybox b/tests/test_shells/inputs/busybox new file mode 100644 index 0000000..5d1495a --- /dev/null +++ b/tests/test_shells/inputs/busybox @@ -0,0 +1,37 @@ +set_theme_option() { + export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2" +} +set_theme() { + export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" +} +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +set_theme default_leftonly +. powerline/bindings/shell/powerline.sh +export VIRTUAL_ENV= +cd "$TEST_ROOT"/3rd +cd .git +cd .. +VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +VIRTUAL_ENV= +bgscript.sh & waitpid.sh +false +kill `cat pid` ; sleep 1s +set_theme_option default_leftonly.segment_data.hostname.display false +set_theme_option default_leftonly.segment_data.user.display false +echo ' +abc +def +' +cd "$DIR1" +cd ../"$DIR2" +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode!»' +set_theme_option default_leftonly.dividers.left.hard \$ABC +false +true is the last line +exit diff --git a/tests/test_shells/inputs/dash b/tests/test_shells/inputs/dash new file mode 100644 index 0000000..5d1495a --- /dev/null +++ b/tests/test_shells/inputs/dash @@ -0,0 +1,37 @@ +set_theme_option() { + export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2" +} +set_theme() { + export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" +} +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +set_theme default_leftonly +. powerline/bindings/shell/powerline.sh +export VIRTUAL_ENV= +cd "$TEST_ROOT"/3rd +cd .git +cd .. +VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +VIRTUAL_ENV= +bgscript.sh & waitpid.sh +false +kill `cat pid` ; sleep 1s +set_theme_option default_leftonly.segment_data.hostname.display false +set_theme_option default_leftonly.segment_data.user.display false +echo ' +abc +def +' +cd "$DIR1" +cd ../"$DIR2" +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode!»' +set_theme_option default_leftonly.dividers.left.hard \$ABC +false +true is the last line +exit diff --git a/tests/test_shells/inputs/fish b/tests/test_shells/inputs/fish new file mode 100644 index 0000000..9a20613 --- /dev/null +++ b/tests/test_shells/inputs/fish @@ -0,0 +1,69 @@ +function set_theme_option + set -g -x POWERLINE_THEME_OVERRIDES "$POWERLINE_THEME_OVERRIDES;$argv[1]=$argv[2]" +end +function set_theme + set -g -x POWERLINE_CONFIG_OVERRIDES "ext.shell.theme=$argv" +end +set -g -x POWERLINE_ +set -g ABOVE_LEFT '[{ + "left": [ + { + "function": "powerline.segments.common.env.environment", + "args": {"variable": "DISPLAYED_ENV_VAR"} + } + ] +}]' +set -g ABOVE_FULL '[{ + "left": [ + { + "type": "string", + "name": "background", + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + { + "function": "powerline.segments.common.env.environment", + "args": {"variable": "DISPLAYED_ENV_VAR"} + } + ] +}]' +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +set_theme default_leftonly +set fish_function_path "$PWD/powerline/bindings/fish" $fish_function_path +while jobs | grep fish_update_completions + sleep 1 +end +powerline-setup +setenv VIRTUAL_ENV +cd "$TEST_ROOT"/3rd +cd .git +cd .. +setenv VIRTUAL_ENV "$HOME/.virtenvs/some-virtual-environment" +setenv VIRTUAL_ENV +bgscript.sh & waitpid.sh +false +kill (cat pid) ; sleep 1s +cd "$DIR1" +cd ../"$DIR2" +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode!»' +set_theme default +set_theme_option default.segments.above "$ABOVE_LEFT" +set -g -x DISPLAYED_ENV_VAR foo +set -g -x -e DISPLAYED_ENV_VAR +set_theme_option default.segments.above "$ABOVE_FULL" +set -g -x DISPLAYED_ENV_VAR foo +set -g -x -e DISPLAYED_ENV_VAR +set_theme_option default.segments.above '' +set -g fish_key_bindings fish_vi_key_bindings +ii +false +true is the last line +exit diff --git a/tests/test_shells/inputs/ipython b/tests/test_shells/inputs/ipython new file mode 100644 index 0000000..257cba6 --- /dev/null +++ b/tests/test_shells/inputs/ipython @@ -0,0 +1,7 @@ +print ('cd ' + '"$TEST_ROOT"/3rd') # Start of the test marker +bool 42 +bool 44 +class Test(object): +pass + +exit diff --git a/tests/test_shells/inputs/mksh b/tests/test_shells/inputs/mksh new file mode 100644 index 0000000..ca45783 --- /dev/null +++ b/tests/test_shells/inputs/mksh @@ -0,0 +1,38 @@ +set_theme_option() { + export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2" +} +set_theme() { + export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" +} +set_theme default_leftonly +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +. powerline/bindings/shell/powerline.sh +export VIRTUAL_ENV= +cd "$TEST_ROOT"/3rd +cd .git +cd .. +VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +VIRTUAL_ENV= +bgscript.sh & waitpid.sh +false +kill `cat pid` ; sleep 1 +set_theme_option default_leftonly.segment_data.hostname.display false +set_theme_option default_leftonly.segment_data.user.display false +echo -n +echo ' +abc +def +' +cd "$DIR1" +cd ../"$DIR2" +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode!»' +set_theme_option default_leftonly.dividers.left.hard \$ABC +false +true is the last line +exit diff --git a/tests/test_shells/inputs/pdb b/tests/test_shells/inputs/pdb new file mode 100644 index 0000000..e9ac498 --- /dev/null +++ b/tests/test_shells/inputs/pdb @@ -0,0 +1,89 @@ +s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_shells/inputs/rc b/tests/test_shells/inputs/rc new file mode 100644 index 0000000..c88bcf9 --- /dev/null +++ b/tests/test_shells/inputs/rc @@ -0,0 +1,33 @@ +fn set_theme_option { + POWERLINE_THEME_OVERRIDES = $POWERLINE_THEME_OVERRIDES';'$1'='$2 +} +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +POWERLINE_CONFIG_OVERRIDES = 'ext.shell.theme=default_leftonly' +. powerline/bindings/rc/powerline.rc +VIRTUAL_ENV = () +cd $TEST_ROOT/3rd +true cd "$TEST_ROOT"/3rd # Test start marker +cd .git +cd .. +VIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment' +VIRTUAL_ENV = () +bgscript.sh & waitpid.sh +false +kill `{cat pid} ; sleep 1s +cd $DIR1 +cd ../$DIR2 +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode!»' +false +set_theme_option default_leftonly.segment_data.hostname.display false +set_theme_option default_leftonly.segment_data.user.display false +echo `{ + echo Continuation! +} +true is the last line +exit diff --git a/tests/test_shells/inputs/tcsh b/tests/test_shells/inputs/tcsh new file mode 100644 index 0000000..c7d722a --- /dev/null +++ b/tests/test_shells/inputs/tcsh @@ -0,0 +1,24 @@ +setenv POWERLINE_THEME_OVERRIDES "default_leftonly.segment_data.hostname.args.only_if_ssh=false" +setenv POWERLINE_CONFIG_OVERRIDES "ext.shell.theme=default_leftonly" +source powerline/bindings/tcsh/powerline.tcsh +unsetenv VIRTUAL_ENV +cd "$TEST_ROOT"/3rd +cd .git +cd .. +setenv VIRTUAL_ENV "/home/foo/.virtenvs/some-virtual-environment" +unsetenv VIRTUAL_ENV +bgscript.sh & waitpid.sh +false # Warning: currently tcsh bindings do not support job count +kill `cat pid` ; sleep 1s +cd $DIR1:q +cd ../$DIR2:q +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode\!»' +false +true is the last line +exit diff --git a/tests/test_shells/inputs/zsh b/tests/test_shells/inputs/zsh new file mode 100644 index 0000000..811684e --- /dev/null +++ b/tests/test_shells/inputs/zsh @@ -0,0 +1,90 @@ +unset HOME +unsetopt promptsp notransientrprompt +setopt interactivecomments +setopt autonamedirs +setopt warncreateglobal +function set_theme_option() { + export POWERLINE_THEME_OVERRIDES="${POWERLINE_THEME_OVERRIDES};$1=$2" + powerline-reload-config +} +function set_theme() { + export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" + powerline-reload-config +} +if test -n "$POWERLINE_NO_ZSH_ZPYTHON" ; then + powerline-reload-config(): +fi +source powerline/bindings/zsh/powerline.zsh +set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false +set_theme_option default.segment_data.hostname.args.only_if_ssh false +ABOVE_LEFT='[{ + "left": [ + { + "function": "powerline.segments.common.env.environment", + "args": {"variable": "DISPLAYED_ENV_VAR"} + } + ] +}]' +ABOVE_FULL='[{ + "left": [ + { + "type": "string", + "name": "background", + "draw_hard_divider": false, + "width": "auto" + } + ], + "right": [ + { + "function": "powerline.segments.common.env.environment", + "args": {"variable": "DISPLAYED_ENV_VAR"} + } + ] +}]' +set_theme default_leftonly +export VIRTUAL_ENV= +cd "$TEST_ROOT"/3rd +cd .git +cd .. +VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment" +VIRTUAL_ENV= +bgscript.sh & waitpid.sh +false +kill `cat pid` ; sleep 1s +cd "$DIR1" +cd ../"$DIR2" +cd ../'\[\]' +cd ../'%%' +cd ../'#[bold]' +cd ../'(echo)' +cd ../'$(echo)' +cd ../'`echo`' +cd ../'«Unicode!»' +cd .. +bindkey -v ; set_theme default + + +echo abc +false +set_theme_option default.segment_data.hostname.display false +set_theme_option default.segment_data.user.display false +select abc in def ghi jkl +do + echo $abc + break +done +1 +cd . +cd . +set_theme_option default.segments.above "$ABOVE_LEFT" +export DISPLAYED_ENV_VAR=foo +unset DISPLAYED_ENV_VAR +set_theme_option default.segments.above "$ABOVE_FULL" +export DISPLAYED_ENV_VAR=foo +unset DISPLAYED_ENV_VAR +set_theme_option default.segments.above +hash -d foo=$PWD:h ; cd . +set_theme_option default.dividers.left.hard \$ABC +true +true is the last line +exit diff --git a/tests/test_shells/ipython_home/profile_default/ipython_config.py b/tests/test_shells/ipython_home/profile_default/ipython_config.py new file mode 100644 index 0000000..6d4a262 --- /dev/null +++ b/tests/test_shells/ipython_home/profile_default/ipython_config.py @@ -0,0 +1,19 @@ +import os +c = get_config() +c.InteractiveShellApp.extensions = ['powerline.bindings.ipython.post_0_11'] +c.TerminalInteractiveShell.autocall = 1 +c.Powerline.config_paths = [os.path.abspath('powerline/config_files')] +c.Powerline.theme_overrides = { + 'in': { + 'segment_data': { + 'virtualenv': { + 'display': False + } + } + } +} +c.Powerline.config_overrides = { + 'common': { + 'default_top_theme': 'ascii' + } +} diff --git a/tests/test_shells/outputs/bash.daemon.ok b/tests/test_shells/outputs/bash.daemon.ok new file mode 100644 index 0000000..89907c8 --- /dev/null +++ b/tests/test_shells/outputs/bash.daemon.ok @@ -0,0 +1,42 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[1] PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +[1]+ Terminated bgscript.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m(exit 42)|(exit 43) +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;48;5;52;22m [0;38;5;231;48;5;52m42 [0;38;5;231;48;5;52;22m [0;38;5;231;48;5;52m43 [0;38;5;52;49;22m [0mset_theme_option default_leftonly.segments.above "$ABOVE_LEFT" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;22m foo [0;38;5;22;49;22m [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segments.above "$ABOVE_FULL" +[0;38;5;231;48;5;233m [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;233m [0;38;5;22;48;5;233;22m [0;38;5;231;48;5;22m foo [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;231;48;5;233m [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segments.above +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/bash.nodaemon.ok b/tests/test_shells/outputs/bash.nodaemon.ok new file mode 100644 index 0000000..c65dcc1 --- /dev/null +++ b/tests/test_shells/outputs/bash.nodaemon.ok @@ -0,0 +1,42 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[1] PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +[1]+ Terminated bgscript.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m(exit 42)|(exit 43) +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;48;5;52;22m [0;38;5;231;48;5;52m42 [0;38;5;231;48;5;52;22m [0;38;5;231;48;5;52m43 [0;38;5;52;49;22m [0mset_theme_option default_leftonly.segments.above "$ABOVE_LEFT" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;22m foo [0;38;5;22;49;22m [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segments.above "$ABOVE_FULL" +[0;38;5;231;48;5;233m [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;233m [0;38;5;22;48;5;233;22m [0;38;5;231;48;5;22m foo [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;231;48;5;233m [0m +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segments.above +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/busybox.daemon.ok b/tests/test_shells/outputs/busybox.daemon.ok new file mode 100644 index 0000000..446d88e --- /dev/null +++ b/tests/test_shells/outputs/busybox.daemon.ok @@ -0,0 +1,29 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +[1]+ Terminated bgscript.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/busybox.nodaemon.ok b/tests/test_shells/outputs/busybox.nodaemon.ok new file mode 100644 index 0000000..afda9a5 --- /dev/null +++ b/tests/test_shells/outputs/busybox.nodaemon.ok @@ -0,0 +1,29 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +[1]+ Terminated bgscript.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/dash.daemon.ok b/tests/test_shells/outputs/dash.daemon.ok new file mode 100644 index 0000000..71ca500 --- /dev/null +++ b/tests/test_shells/outputs/dash.daemon.ok @@ -0,0 +1,28 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +set_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0m[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/dash.nodaemon.ok b/tests/test_shells/outputs/dash.nodaemon.ok new file mode 100644 index 0000000..c289cd2 --- /dev/null +++ b/tests/test_shells/outputs/dash.nodaemon.ok @@ -0,0 +1,28 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +set_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0m[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/fish.ok b/tests/test_shells/outputs/fish.ok new file mode 100644 index 0000000..4d208bb --- /dev/null +++ b/tests/test_shells/outputs/fish.ok @@ -0,0 +1,52 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;231;48;5;22m foo [0;38;5;22;49;22m [0m +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;231;48;5;233m [0m +[0;38;5;231;48;5;233m [0;38;5;22;48;5;233;22m [0;38;5;231;48;5;22m foo [0m +[0;38;5;231;48;5;233m [0m +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;22;48;5;148;1m DEFAULT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;22;48;5;148;1m DEFAULT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m +[0;38;5;236;49;22m [0;38;5;250;48;5;236m BRANCH [0m diff --git a/tests/test_shells/outputs/ipython.ok b/tests/test_shells/outputs/ipython.ok new file mode 100644 index 0000000..166604d --- /dev/null +++ b/tests/test_shells/outputs/ipython.ok @@ -0,0 +1,14 @@ + +[0;38;5;250;48;5;240m In [[0;38;5;231;48;5;240m2[0;38;5;250;48;5;240m] [0;38;5;240;49;22m [0mbool 42 +[0;38;5;250;48;5;240m [0;38;5;231;48;5;240m2[0;38;5;250;48;5;240m> [0;38;5;240;49;22m [0mbool(42) +[0;38;5;250;48;5;240m Out[[0;38;5;231;48;5;240m2[0;38;5;250;48;5;240m] [0;38;5;240;49;22m [0mTrue + +[0;38;5;250;48;5;240m In [[0;38;5;231;48;5;240m3[0;38;5;250;48;5;240m] [0;38;5;240;49;22m [0mbool 44 +[0;38;5;250;48;5;240m [0;38;5;231;48;5;240m3[0;38;5;250;48;5;240m> [0;38;5;240;49;22m [0mbool(44) +[0;38;5;250;48;5;240m Out[[0;38;5;231;48;5;240m3[0;38;5;250;48;5;240m] [0;38;5;240;49;22m [0mTrue + +[0;38;5;250;48;5;240m In [[0;38;5;231;48;5;240m4[0;38;5;250;48;5;240m] [0;38;5;240;49;22m [0mclass Test(object): +[0;38;5;250;48;5;240m [0;38;5;240;49;22m [0m pass +[0;38;5;250;48;5;240m [0;38;5;240;49;22m [0m + +[0;38;5;250;48;5;240m In [[0;38;5;231;48;5;240m5[0;38;5;250;48;5;240m] [0;38;5;240;49;22m [0mexit diff --git a/tests/test_shells/outputs/mksh.daemon.ok b/tests/test_shells/outputs/mksh.daemon.ok new file mode 100644 index 0000000..264dff8 --- /dev/null +++ b/tests/test_shells/outputs/mksh.daemon.ok @@ -0,0 +1,32 @@ + +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[1] PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1 +[1] + Terminated bash -c ... +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho -n +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/mksh.nodaemon.ok b/tests/test_shells/outputs/mksh.nodaemon.ok new file mode 100644 index 0000000..d8d9d70 --- /dev/null +++ b/tests/test_shells/outputs/mksh.nodaemon.ok @@ -0,0 +1,32 @@ + +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[1] PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1 +[1] + Terminated bash -c ... +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho -n +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho ' +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mabc +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0mdef +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m' + +abc +def + +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.dividers.left.hard \$ABC +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m$ABC[0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m$ABC[0mfalse diff --git a/tests/test_shells/outputs/pdb.module.ok b/tests/test_shells/outputs/pdb.module.ok new file mode 100644 index 0000000..7554dd6 --- /dev/null +++ b/tests/test_shells/outputs/pdb.module.ok @@ -0,0 +1,222 @@ +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> class Foo(object): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> class Foo(object): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> def __init__(self): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m7 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> @classmethod +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> @staticmethod +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> def bra(self): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +--Return-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> def brah(): +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m25 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +-> f = Foo() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def __init__(self): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m7 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +-> nop('__init__') +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> self.bar() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> @classmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> self.baz() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> @staticmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> nop(1) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(1) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> self.bra() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Return-- +-> self.bra() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +-> Foo.bar() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @classmethod +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> Foo.baz() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @staticmethod +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> nop(1) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(1) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> Foo.bra(f) +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> f.bar() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @classmethod +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> f.baz() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @staticmethod +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> nop(1) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(1) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> f.bra() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> brah() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def brah(): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m25 [0;38;5;250;48;5;240mbrah [0;38;5;240;49;22m [0m +-> nop('brah') +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop('brah') +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;240;49;22m [0m +--Return-- +-> brah() +[0;38;5;235;48;5;252;1m 1 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Return-- +[0;38;5;235;48;5;252;1m 0 [0;38;5;252;49;22m [0m diff --git a/tests/test_shells/outputs/pdb.subclass.ok b/tests/test_shells/outputs/pdb.subclass.ok new file mode 100644 index 0000000..d8eba5e --- /dev/null +++ b/tests/test_shells/outputs/pdb.subclass.ok @@ -0,0 +1,217 @@ +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> class Foo(object): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> class Foo(object): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> def __init__(self): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m7 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> @classmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> @staticmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> def bra(self): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +--Return-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m6 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mFoo [0;38;5;240;49;22m [0m +-> def brah(): +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m25 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +-> f = Foo() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def __init__(self): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m7 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +-> nop('__init__') +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m8 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> self.bar() +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> @classmethod +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m9 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> self.baz() +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> @staticmethod +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> nop(1) +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(1) +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m10 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> self.bra() +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +--Call-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 5 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Return-- +-> self.bra() +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m29 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m11 [0;38;5;250;48;5;240m__init__ [0;38;5;240;49;22m [0m +-> Foo.bar() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @classmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m30 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> Foo.baz() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @staticmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> nop(1) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(1) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m31 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> Foo.bra(f) +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m32 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> f.bar() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @classmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m13 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(cls.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m34 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m15 [0;38;5;250;48;5;240mbar [0;38;5;240;49;22m [0m +-> f.baz() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> @staticmethod +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m17 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> nop(1) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(1) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m35 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m19 [0;38;5;250;48;5;240mbaz [0;38;5;240;49;22m [0m +-> f.bra() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def bra(self): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m21 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop(self.__class__.__name__) +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m36 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m22 [0;38;5;250;48;5;240mbra [0;38;5;240;49;22m [0m +-> brah() +[0;38;5;235;48;5;252;1m 2 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;240;49;22m [0m +--Call-- +-> def brah(): +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m25 [0;38;5;250;48;5;240mbrah [0;38;5;240;49;22m [0m +-> nop('brah') +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;240;49;22m [0m +--Call-- +-> def nop(_): +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m2 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> pass +[0;38;5;235;48;5;252;1m 4 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m3 [0;38;5;250;48;5;240mnop [0;38;5;240;49;22m [0m +--Return-- +-> nop('brah') +[0;38;5;235;48;5;252;1m 3 [0;38;5;252;48;5;240;22m [0;38;5;252;48;5;240;1m<string>:[0;38;5;252;48;5;240;1m1 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m38 [0;38;5;250;48;5;240m<module> [0;38;5;250;48;5;240;22m [0;38;5;252;48;5;240;1mpdb-script.py:[0;38;5;252;48;5;240;1m26 [0;38;5;250;48;5;240mbrah [0;38;5;240;49;22m [0m diff --git a/tests/test_shells/outputs/rc.daemon.ok b/tests/test_shells/outputs/rc.daemon.ok new file mode 100644 index 0000000..c49b9a3 --- /dev/null +++ b/tests/test_shells/outputs/rc.daemon.ok @@ -0,0 +1,24 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV = () +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `{cat pid} ; sleep 1s +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd $DIR1 +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../$DIR2 +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mecho `{ +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m echo Continuation! +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m} +Continuation! diff --git a/tests/test_shells/outputs/rc.nodaemon.ok b/tests/test_shells/outputs/rc.nodaemon.ok new file mode 100644 index 0000000..28376cb --- /dev/null +++ b/tests/test_shells/outputs/rc.nodaemon.ok @@ -0,0 +1,24 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV = () +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `{cat pid} ; sleep 1s +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd $DIR1 +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../$DIR2 +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mset_theme_option default_leftonly.segment_data.hostname.display false +[0;38;5;231;48;5;31;1m USER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mset_theme_option default_leftonly.segment_data.user.display false +[0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mecho `{ +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m echo Continuation! +[0;38;5;252;48;5;240;1m [0;38;5;240;49;22m [0m} +Continuation! diff --git a/tests/test_shells/outputs/tcsh.ok b/tests/test_shells/outputs/tcsh.ok new file mode 100644 index 0000000..07089bf --- /dev/null +++ b/tests/test_shells/outputs/tcsh.ok @@ -0,0 +1,17 @@ +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m[0m +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m[0m diff --git a/tests/test_shells/outputs/zsh.daemon.ok b/tests/test_shells/outputs/zsh.daemon.ok new file mode 100644 index 0000000..32e80d8 --- /dev/null +++ b/tests/test_shells/outputs/zsh.daemon.ok @@ -0,0 +1,52 @@ + +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[1] PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +[1] + terminated bgscript.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbindkey -v ; set_theme default +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m[0;38;5;23;48;5;231;1m COMMND [0;38;5;231;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho abc +abc +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mfalse +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segment_data.hostname.display false +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segment_data.user.display false +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mselect abc in def ghi jkl +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0mdo +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0m echo $abc +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0m break +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0mdone +1) def 2) ghi 3) jkl +[0;38;5;252;48;5;240;1m Select variant [0;38;5;240;49;22m [0m1 +def +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above "$ABOVE_LEFT" +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;22m foo [0;38;5;22;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above "$ABOVE_FULL" +[0;38;5;231;48;5;233m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;233m [0;38;5;22;48;5;233;22m [0;38;5;231;48;5;22m foo [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;231;48;5;233m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mhash -d foo=$PWD:h ; cd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m~foo [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.dividers.left.hard \$ABC +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m$ABC[0;38;5;250;48;5;240m~foo [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m$ABC[0mtrue diff --git a/tests/test_shells/outputs/zsh.nodaemon.ok b/tests/test_shells/outputs/zsh.nodaemon.ok new file mode 100644 index 0000000..3aa285f --- /dev/null +++ b/tests/test_shells/outputs/zsh.nodaemon.ok @@ -0,0 +1,52 @@ + +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[1] PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +[1] + terminated bgscript.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbindkey -v ; set_theme default +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m[0;38;5;23;48;5;231;1m COMMND [0;38;5;231;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho abc +abc +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mfalse +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segment_data.hostname.display false +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segment_data.user.display false +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mselect abc in def ghi jkl +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0mdo +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0m echo $abc +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0m break +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0mdone +1) def 2) ghi 3) jkl +[0;38;5;252;48;5;240;1m Select variant [0;38;5;240;49;22m [0m1 +def +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above "$ABOVE_LEFT" +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;22m foo [0;38;5;22;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above "$ABOVE_FULL" +[0;38;5;231;48;5;233m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;233m [0;38;5;22;48;5;233;22m [0;38;5;231;48;5;22m foo [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;231;48;5;233m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mhash -d foo=$PWD:h ; cd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m~foo [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.dividers.left.hard \$ABC +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m$ABC[0;38;5;250;48;5;240m~foo [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m$ABC[0mtrue diff --git a/tests/test_shells/outputs/zsh.zpython.ok b/tests/test_shells/outputs/zsh.zpython.ok new file mode 100644 index 0000000..32e80d8 --- /dev/null +++ b/tests/test_shells/outputs/zsh.zpython.ok @@ -0,0 +1,52 @@ + +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd .git +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m.git [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;74;22m [0;38;5;231;48;5;74m(e) some-virtual-environment [0;38;5;74;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mVIRTUAL_ENV= +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbgscript.sh & waitpid.sh +[1] PID +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;49;22m [0mfalse +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;48;5;166;22m [0;38;5;220;48;5;166m1 [0;38;5;166;48;5;52;22m [0;38;5;231;48;5;52m1 [0;38;5;52;49;22m [0mkill `cat pid` ; sleep 1s +[1] + terminated bgscript.sh +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd "$DIR1" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^[[32m [0;38;5;240;49;22m [0mcd ../"$DIR2" +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m^H [0;38;5;240;49;22m [0mcd ../'\[\]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m\[\] [0;38;5;240;49;22m [0mcd ../'%%' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m%% [0;38;5;240;49;22m [0mcd ../'#[bold]' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m#[bold] [0;38;5;240;49;22m [0mcd ../'(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m(echo) [0;38;5;240;49;22m [0mcd ../'$(echo)' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m$(echo) [0;38;5;240;49;22m [0mcd ../'`echo`' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m`echo` [0;38;5;240;49;22m [0mcd ../'«Unicode!»' +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240m3rd [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m«Unicode!» [0;38;5;240;49;22m [0mcd .. +[0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;236;22m [0;38;5;250;48;5;236m BRANCH [0;38;5;236;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mbindkey -v ; set_theme default +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m[0;38;5;23;48;5;231;1m COMMND [0;38;5;231;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mecho abc +abc +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mfalse +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;166;22m [0;38;5;220;48;5;166m HOSTNAME [0;38;5;166;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segment_data.hostname.display false +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;31;22m [0;38;5;231;48;5;31;1mUSER [0;38;5;31;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segment_data.user.display false +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mselect abc in def ghi jkl +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0mdo +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0m echo $abc +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0m break +[0;38;5;252;48;5;240;1m select [0;38;5;240;49;22m [0mdone +1) def 2) ghi 3) jkl +[0;38;5;252;48;5;240;1m Select variant [0;38;5;240;49;22m [0m1 +def +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mcd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above "$ABOVE_LEFT" +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;22m foo [0;38;5;22;49;22m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above "$ABOVE_FULL" +[0;38;5;231;48;5;233m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mexport DISPLAYED_ENV_VAR=foo +[0;38;5;231;48;5;233m [0;38;5;22;48;5;233;22m [0;38;5;231;48;5;22m foo [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0munset DISPLAYED_ENV_VAR +[0;38;5;231;48;5;233m [0m +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.segments.above +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m… [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mtmp [0;38;5;245;48;5;240;22m [0;38;5;250;48;5;240mshell [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mhash -d foo=$PWD:h ; cd . +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m [0;38;5;250;48;5;240m~foo [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m [0mset_theme_option default.dividers.left.hard \$ABC +[0;38;5;22;48;5;148;1m INSERT [0;38;5;148;48;5;240;22m$ABC[0;38;5;250;48;5;240m~foo [0;38;5;245;48;5;240;22m [0;38;5;252;48;5;240;1m3rd [0;38;5;240;49;22m$ABC[0mtrue diff --git a/tests/test_shells/pdb-main.py b/tests/test_shells/pdb-main.py new file mode 100644 index 0000000..37af785 --- /dev/null +++ b/tests/test_shells/pdb-main.py @@ -0,0 +1,24 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import pdb +import os +import sys + +from powerline.bindings.pdb import use_powerline_prompt + + +@use_powerline_prompt +class Pdb(pdb.Pdb): + pass + + +p = Pdb() + + +script = os.path.join(os.path.dirname(__file__), 'pdb-script.py') +with open(script, 'r') as fd: + code = compile(fd.read(), script, 'exec') + + +p.run('exec(code)', globals={'code': code}) diff --git a/tests/test_shells/pdb-script.py b/tests/test_shells/pdb-script.py new file mode 100644 index 0000000..40db5e8 --- /dev/null +++ b/tests/test_shells/pdb-script.py @@ -0,0 +1,38 @@ +# vim:fileencoding=utf-8:noet +def nop(_): + pass + + +class Foo(object): + def __init__(self): + nop('__init__') + self.bar() + self.baz() + self.bra() + + @classmethod + def bar(cls): + nop(cls.__name__) + + @staticmethod + def baz(): + nop(1) + + def bra(self): + nop(self.__class__.__name__) + + +def brah(): + nop('brah') + + +f = Foo() +Foo.bar() +Foo.baz() +Foo.bra(f) + +f.bar() +f.baz() +f.bra() + +brah() diff --git a/tests/test_shells/postproc.py b/tests/test_shells/postproc.py new file mode 100755 index 0000000..7926155 --- /dev/null +++ b/tests/test_shells/postproc.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import os +import socket +import sys +import codecs +import platform +import re + + +test_root = os.environ['TEST_ROOT'] +test_type = sys.argv[1] +test_client = sys.argv[2] +shell = sys.argv[3] +fname = os.path.join(test_root, '.'.join((shell, test_type, test_client, 'full.log'))) +new_fname = os.path.join(test_root, '.'.join((shell, test_type, test_client, 'log'))) +pid_fname = os.path.join(test_root, '3rd', 'pid') + +is_pypy = platform.python_implementation() == 'PyPy' + + +try: + with open(pid_fname, 'r') as P: + pid = P.read().strip() +except IOError: + pid = None +hostname = socket.gethostname() +user = os.environ['USER'] + +REFS_RE = re.compile(r'^\[\d+ refs\]\n') +IPYPY_DEANSI_RE = re.compile(r'\033(?:\[(?:\?\d+[lh]|[^a-zA-Z]+[a-ln-zA-Z])|[=>])') +ZSH_HL_RE = re.compile(r'\033\[\?\d+[hl]') + +start_str = 'cd "$TEST_ROOT"/3rd' +if shell == 'pdb': + start_str = 'class Foo(object):' + +with codecs.open(fname, 'r', encoding='utf-8') as R: + with codecs.open(new_fname, 'w', encoding='utf-8') as W: + found_cd = False + i = -1 + for line in (R if shell != 'fish' else R.read().split('\n')): + i += 1 + if not found_cd: + found_cd = (start_str in line) + continue + if 'true is the last line' in line: + break + line = line.translate({ + ord('\r'): None + }) + if REFS_RE.match(line): + continue + line = line.replace(hostname, 'HOSTNAME') + line = line.replace(user, 'USER') + if pid is not None: + line = line.replace(pid, 'PID') + if shell == 'zsh': + line = line.replace('\033[0m\033[23m\033[24m\033[J', '') + line = ZSH_HL_RE.subn('', line)[0] + elif shell == 'fish': + res = '' + try: + while line.index('\033[0;'): + start = line.index('\033[0;') + end = line.index('\033[0m', start) + res += line[start:end + 4] + '\n' + line = line[end + 4:] + except ValueError: + pass + line = res + elif shell == 'tcsh': + try: + start = line.index('\033[0;') + end = line.index(' ', start) + line = line[start:end] + '\n' + except ValueError: + line = '' + elif shell == 'mksh': + # Output is different in travis: on my machine I see full + # command, in travis it is truncated just after `true`. + if line.startswith('[1] + Terminated'): + line = '[1] + Terminated bash -c ...\n' + elif shell == 'dash': + # Position of this line is not stable: it may go both before and + # after the next line + if line.startswith('[1] + Terminated'): + continue + elif shell == 'ipython' and is_pypy: + try: + end_idx = line.rindex('\033[0m') + try: + idx = line[:end_idx].rindex('\033[1;1H') + except ValueError: + idx = line[:end_idx].rindex('\033[?25h') + line = line[idx + len('\033[1;1H'):] + except ValueError: + pass + try: + data_end_idx = line.rindex('\033[1;1H') + line = line[:data_end_idx] + '\n' + except ValueError: + pass + if line == '\033[1;1H\n': + continue + was_empty = line == '\n' + line = IPYPY_DEANSI_RE.subn('', line)[0] + if line == '\n' and not was_empty: + line = '' + elif shell == 'rc': + if line == 'read() failed: Connection reset by peer\n': + line = '' + elif shell == 'pdb': + if is_pypy: + if line == '\033[?1h\033=\033[?25l\033[1A\n': + line = '' + line = IPYPY_DEANSI_RE.subn('', line)[0] + if line == '\n': + line = '' + if line.startswith(('>',)): + line = '' + elif line == '-> self.quitting = 1\n': + line = '-> self.quitting = True\n' + elif line == '\n': + line = '' + if line == '-> self.quitting = True\n': + break + W.write(line) diff --git a/tests/test_shells/run_script.py b/tests/test_shells/run_script.py new file mode 100755 index 0000000..2eebca1 --- /dev/null +++ b/tests/test_shells/run_script.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import argparse +import os +import re + +from time import sleep +from subprocess import check_call +from io import BytesIO + +import pexpect + + +def get_argparser(ArgumentParser=argparse.ArgumentParser): + parser = ArgumentParser(description='Run powerline shell test using pexpect') + parser.add_argument('--wait-for-echo', action='store_true', help='Wait until the input is echoed back.') + parser.add_argument('--type', metavar='TYPE', help='Test type (daemon, nodaemon, …).') + parser.add_argument('--client', metavar='CLIENT', help='Type of the client used (C, shell, zpython, …).') + parser.add_argument('--shell', metavar='SHELL', help='Shell name.') + parser.add_argument('command', nargs=argparse.REMAINDER, metavar='COMMAND', + help='Command to run and its argument.') + return parser + + +def main(): + test_root = os.environ['TEST_ROOT'] + parser = get_argparser() + args = parser.parse_args() + + shell = args.shell or args.command[0] + test_type = args.type or shell + test_client = args.client or test_type + + log_file_base = '{0}.{1}.{2}'.format(shell, test_type, test_client) + full_log_file_name = os.path.join(test_root, '{0}.full.log'.format(log_file_base)) + + local_paths = [ + os.path.abspath(os.path.join(test_root, 'path')), + os.path.abspath('scripts'), + ] + + if test_type == 'fish': + local_paths += ['/usr/bin', '/bin'] + + python_paths = os.environ.get('PYTHONPATH', '') + if python_paths: + python_paths = ':' + python_paths + python_paths = os.path.abspath('.') + python_paths + + environ = { + 'LANG': 'en_US.UTF-8', + 'PATH': os.pathsep.join(local_paths), + 'TERM': 'screen-256color', + 'DIR1': os.environ['DIR1'], + 'DIR2': os.environ['DIR2'], + 'XDG_CONFIG_HOME': os.path.abspath(os.path.join(test_root, 'fish_home')), + 'IPYTHONDIR': os.path.abspath(os.path.join(test_root, 'ipython_home')), + 'PYTHONPATH': python_paths, + 'POWERLINE_CONFIG_OVERRIDES': os.environ.get('POWERLINE_CONFIG_OVERRIDES', ''), + 'POWERLINE_THEME_OVERRIDES': os.environ.get('POWERLINE_THEME_OVERRIDES', ''), + 'POWERLINE_CONFIG_PATHS': os.path.abspath(os.path.join('powerline', 'config_files')), + 'POWERLINE_COMMAND_ARGS': os.environ.get('POWERLINE_COMMAND_ARGS', ''), + 'POWERLINE_COMMAND': os.environ.get('POWERLINE_COMMAND', ''), + 'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH', ''), + 'TEST_ROOT': test_root, + } + + os.environ['PATH'] = environ['PATH'] + + if test_type == 'daemon': + environ['POWERLINE_SHELL_CONTINUATION'] = '1' + environ['POWERLINE_SHELL_SELECT'] = '1' + + if test_type != 'zpython' and shell == 'zsh': + environ['POWERLINE_NO_ZSH_ZPYTHON'] = '1' + + sio = BytesIO() + + child = pexpect.spawn( + args.command[0], + args.command[1:], + env=environ, + logfile=sio, + timeout=30, + ) + child.expect(re.compile(b'.*')) + sleep(0.5) + child.setwinsize(1, 300) + + with open(os.path.join('tests', 'test_shells', 'inputs', shell), 'rb') as F: + if not args.wait_for_echo: + child.send(F.read()) + else: + for line in F: + child.send(line) + sleep(1) + # TODO Implement something more smart + + with open(full_log_file_name, 'wb') as LF: + while True: + try: + s = child.read_nonblocking(1000) + except pexpect.TIMEOUT: + break + except pexpect.EOF: + break + else: + LF.write(s) + + child.close(force=True) + + check_call([ + os.path.join(test_root, 'path', 'python'), + os.path.join('tests', 'test_shells', 'postproc.py'), + test_type, test_client, shell + ]) + pidfile = os.path.join(test_root, '3rd', 'pid') + if os.path.exists(pidfile): + os.unlink(pidfile) + + +if __name__ == '__main__': + main() diff --git a/tests/test_shells/test.sh b/tests/test_shells/test.sh new file mode 100755 index 0000000..4494302 --- /dev/null +++ b/tests/test_shells/test.sh @@ -0,0 +1,491 @@ +#!/bin/sh +. tests/shlib/common.sh + +enter_suite shell final + +if test $# -eq 0 ; then + FAST=1 +fi +ONLY_SHELL="$1" +ONLY_TEST_TYPE="$2" +ONLY_TEST_CLIENT="$3" + +export PYTHON + +if test "$ONLY_SHELL" = "--help" ; then +cat << EOF +Usage: + $0 [[[ONLY_SHELL | ""] (ONLY_TEST_TYPE | "")] (ONLY_TEST_CLIENT | "")] + +ONLY_SHELL: execute only tests for given shell +ONLY_TEST_TYPE: execute only "daemon" or "nodaemon" tests +ONLY_TEST_CLIENT: use only given test client (one of C, python, render, shell) +EOF +exit 0 +fi + +check_screen_log() { + TEST_TYPE="$1" + TEST_CLIENT="$2" + SH="$3" + if test -e "$ROOT/tests/test_shells/outputs/${SH}.${TEST_TYPE}.ok" ; then + diff -a -u "$ROOT/tests/test_shells/outputs/${SH}.${TEST_TYPE}.ok" \ + "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log" + return $? + elif test -e "$ROOT/tests/test_shells/outputs/${SH}.ok" ; then + diff -a -u "$ROOT/tests/test_shells/outputs/${SH}.ok" \ + "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log" + return $? + else + cat "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log" + return 1 + fi +} + +# HACK: get newline for use in strings given that "\n" and $'' do not work. +NL="$(printf '\nE')" +NL="${NL%E}" + +print_full_output() { + TEST_TYPE="$1" + TEST_CLIENT="$2" + SH="$3" + echo "Full output:" + echo '============================================================' + cat "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.full.log" + echo + echo '____________________________________________________________' + if test "$POWERLINE_TEST_NO_CAT_V" != "1" ; then + echo "Full output (cat -v):" + echo '============================================================' + cat -v "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.full.log" + echo + echo '____________________________________________________________' + fi +} + +do_run_test() { + TEST_TYPE="$1" + shift + TEST_CLIENT="$1" + shift + SH="$1" + + local wait_for_echo_arg= + if ( \ + test "${SH}" = "dash" \ + || ( \ + test "${SH}" = "pdb" \ + && ( \ + ( \ + test "$PYTHON_VERSION_MAJOR" -eq 3 \ + && test "$PYTHON_VERSION_MINOR" -eq 2 \ + && test "$PYTHON_IMPLEMENTATION" = "CPython" \ + ) \ + || test "$PYTHON_IMPLEMENTATION" = "PyPy" \ + ) \ + ) \ + || ( \ + test "${SH}" = "ipython" \ + && test "$("${PYTHON}" -mIPython --version | head -n1 | cut -d. -f1)" -ge 5 \ + ) \ + ) ; then + wait_for_echo_arg="--wait-for-echo" + fi + "${PYTHON}" tests/test_shells/run_script.py \ + $wait_for_echo_arg --type=${TEST_TYPE} --client=${TEST_CLIENT} --shell=${SH} \ + "$@" + if ! check_screen_log ${TEST_TYPE} ${TEST_CLIENT} ${SH} ; then + echo '____________________________________________________________' + if test "$POWERLINE_TEST_NO_CAT_V" != "1" ; then + # Repeat the diff to make it better viewable in travis output + echo "Diff (cat -v):" + echo '============================================================' + check_screen_log ${TEST_TYPE} ${TEST_CLIENT} ${SH} | cat -v + echo '____________________________________________________________' + fi + echo -n "Failed ${SH}. " + print_full_output ${TEST_TYPE} ${TEST_CLIENT} ${SH} + case "${SH}" in + *ksh) + "$TEST_ROOT/path/${SH}" -c 'echo ${KSH_VERSION}' + ;; + dash) + # ? + ;; + busybox) + busybox --help + ;; + *) + "$TEST_ROOT/path/${SH}" --version + ;; + esac + if which dpkg >/dev/null ; then + dpkg -s ${SH} + fi + return 1 + fi + return 0 +} + +run_test() { + TEST_TYPE="$1" + TEST_CLIENT="$2" + SH="$3" + local attempts=3 + if test -n "$ONLY_SHELL$ONLY_TEST_TYPE$ONLY_TEST_CLIENT" ; then + attempts=1 + fi + while test $attempts -gt 0 ; do + rm -f "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.log" + rm -f "$TEST_ROOT/${SH}.${TEST_TYPE}.${TEST_CLIENT}.full.log" + do_run_test "$@" && return 0 + attempts=$(( attempts - 1 )) + done + return 1 +} + +make_test_root + +git init "$TEST_ROOT/3rd" +git --git-dir="$TEST_ROOT/3rd/.git" checkout -b BRANCH +export DIR1="[32m" +export DIR2="" +mkdir "$TEST_ROOT/3rd/$DIR1" +mkdir "$TEST_ROOT/3rd/$DIR2" +mkdir "$TEST_ROOT"/3rd/'\[\]' +mkdir "$TEST_ROOT"/3rd/'%%' +mkdir "$TEST_ROOT"/3rd/'#[bold]' +mkdir "$TEST_ROOT"/3rd/'(echo)' +mkdir "$TEST_ROOT"/3rd/'$(echo)' +mkdir "$TEST_ROOT"/3rd/'`echo`' +mkdir "$TEST_ROOT"/3rd/'«Unicode!»' + +mkdir "$TEST_ROOT/fish_home" +mkdir "$TEST_ROOT/fish_home/fish" +mkdir "$TEST_ROOT/fish_home/fish/generated_completions" +cp -r "$ROOT/tests/test_shells/ipython_home" "$TEST_ROOT" + +mkdir "$TEST_ROOT/path" +ln -s "$(which "${PYTHON}")" "$TEST_ROOT/path/python" +ln -s "$(which env)" "$TEST_ROOT/path" +ln -s "$(which git)" "$TEST_ROOT/path" +ln -s "$(which sleep)" "$TEST_ROOT/path" +ln -s "$(which cat)" "$TEST_ROOT/path" +ln -s "$(which false)" "$TEST_ROOT/path" +ln -s "$(which true)" "$TEST_ROOT/path" +ln -s "$(which kill)" "$TEST_ROOT/path" +ln -s "$(which echo)" "$TEST_ROOT/path" +ln -s "$(which which)" "$TEST_ROOT/path" +ln -s "$(which dirname)" "$TEST_ROOT/path" +ln -s "$(which wc)" "$TEST_ROOT/path" +ln -s "$(which stty)" "$TEST_ROOT/path" +ln -s "$(which cut)" "$TEST_ROOT/path" +ln -s "$(which bc)" "$TEST_ROOT/path" +ln -s "$(which expr)" "$TEST_ROOT/path" +ln -s "$(which mktemp)" "$TEST_ROOT/path" +ln -s "$(which grep)" "$TEST_ROOT/path" +ln -s "$(which sed)" "$TEST_ROOT/path" +ln -s "$(which rm)" "$TEST_ROOT/path" +ln -s "$(which tr)" "$TEST_ROOT/path" +ln -s "$(which uname)" "$TEST_ROOT/path" +ln -s "$(which test)" "$TEST_ROOT/path" +ln -s "$(which pwd)" "$TEST_ROOT/path" +ln -s "$(which hostname)" "$TEST_ROOT/path" +ln -s "$ROOT/tests/test_shells/bgscript.sh" "$TEST_ROOT/path" +ln -s "$ROOT/tests/test_shells/waitpid.sh" "$TEST_ROOT/path" +if which socat ; then + ln -s "$(which socat)" "$TEST_ROOT/path" +fi +for pexe in powerline powerline-config powerline-render powerline.sh powerline.py ; do + if test -e "$ROOT/scripts/$pexe" ; then + ln -s "$ROOT/scripts/$pexe" "$TEST_ROOT/path" + elif test -e client/$pexe ; then + ln -s "$ROOT/client/$pexe" "$TEST_ROOT/path" + elif which $pexe ; then + ln -s "$(which $pexe)" "$TEST_ROOT/path" + else + echo "Executable $pexe was not found" + exit 1 + fi +done + +ln -s python "$TEST_ROOT/path/pdb" +PDB_PYTHON=pdb +ln -s python "$TEST_ROOT/path/ipython" +IPYTHON_PYTHON=ipython + +if test -z "$POWERLINE_RC_EXE" ; then + if which rc-status >/dev/null ; then + # On Gentoo `rc` executable is from OpenRC. Thus app-shells/rc instals + # `rcsh` executable. + POWERLINE_RC_EXE=rcsh + else + POWERLINE_RC_EXE=rc + fi +fi + +if which "$POWERLINE_RC_EXE" >/dev/null ; then + ln -s "$(which $POWERLINE_RC_EXE)" "$TEST_ROOT/path/rc" +fi + +exes="bash zsh busybox tcsh mksh" + +if test "$TRAVIS" != "true" ; then + # For some reason fish does not work on travis + exes="$exes fish" +fi + +# dash has some problems with job control +#exes="$exes dash" + +for exe in $exes ; do + if which $exe >/dev/null ; then + if test "$exe" = "fish" ; then + fish_version="$(fish --version 2>&1)" + fish_version="${fish_version##* }" + fish_version_major="${fish_version%%.*}" + if test "$fish_version_major" != "$fish_version" ; then + # No dot is in development version compiled by bot-ci + fish_version_minor="${fish_version#*.}" + fish_version_patch="${fish_version_minor#*.}" + fish_version_dev="${fish_version_patch#*-}" + if test "$fish_version_dev" = "$fish_version_patch" ; then + fish_version_dev="" + fi + fish_version_minor="${fish_version_minor%%.*}" + fish_version_patch="${fish_version_patch%%-*}" + if test $fish_version_major -lt 2 || ( \ + test $fish_version_major -eq 2 && (\ + test $fish_version_minor -lt 1 || (\ + test $fish_version_minor -eq 1 && + test $fish_version_patch -lt 2 && \ + test -z "$fish_version_dev" + ) \ + ) \ + ) ; then + continue + fi + fi + fi + ln -s "$(which $exe)" "$TEST_ROOT/path" + fi +done + +mkdir "$TEST_ROOT/home" +export HOME="$TEST_ROOT/home" + +unset ENV + +export ADDRESS="powerline-ipc-test-$$" +export PYTHON +echo "Powerline address: $ADDRESS" + +check_test_client() { + local executable="$1" + local client_type="$2" + local actual_mime_type="$( + file --mime-type --brief --dereference "$TEST_ROOT/path/$executable" \ + | cut -d/ -f1 + )" + local expected_mime_type + case "$client_type" in + C) expected_mime_type="application/x-executable" ;; + python) expected_mime_type="text/x-python" ;; + render) expected_mime_type="text/x-python" ;; + shell) expected_mime_type="text/x-shellscript" ;; + esac + expected_mime_type="${expected_mime_type%/*}" + if test "$expected_mime_type" != "$actual_mime_type" ; then + fail "MIME-$executable" "M" "Expected $executable to have MIME type $expected_mime_type, but got $actual_mime_type" + fi +} + +if ( \ + test -z "${ONLY_SHELL}" \ + || test "${ONLY_SHELL%sh}" != "${ONLY_SHELL}" \ + || test "${ONLY_SHELL}" = "busybox" \ + || test "${ONLY_SHELL}" = "rc" \ +) ; then + scripts/powerline-config shell command + + for TEST_TYPE in "daemon" "nodaemon" ; do + if test -n "$ONLY_TEST_TYPE" && test "$ONLY_TEST_TYPE" != "$TEST_TYPE" + then + continue + fi + if test "$FAST" = 1 ; then + if test $TEST_TYPE = daemon ; then + VARIANTS=3 + else + VARIANTS=4 + fi + EXETEST="$(( ${RANDOM:-`date +%N | sed s/^0*//`} % $VARIANTS ))" + echo "Execute tests: $EXETEST" + fi + + if test $TEST_TYPE = daemon ; then + sh -c ' + echo $$ > "$TEST_ROOT/daemon_pid" + exec "$PYTHON" ./scripts/powerline-daemon -s"$ADDRESS" -f >"$TEST_ROOT/daemon_log" 2>&1 + ' & + fi + echo "> Testing $TEST_TYPE" + I=-1 + for POWERLINE_COMMAND in \ + powerline \ + powerline-render \ + powerline.py \ + powerline.sh + do + case "$POWERLINE_COMMAND" in + powerline) TEST_CLIENT=C ;; + powerline-render) TEST_CLIENT=render ;; + powerline.py) TEST_CLIENT=python ;; + powerline.sh) TEST_CLIENT=shell ;; + esac + check_test_client "$POWERLINE_COMMAND" $TEST_CLIENT + if test "$TEST_CLIENT" = render && test "$TEST_TYPE" = daemon ; then + continue + fi + I="$(( I + 1 ))" + if test "$TEST_CLIENT" = "C" && ! test -x "$ROOT/scripts/powerline" + then + if which powerline >/dev/null ; then + POWERLINE_COMMAND=powerline + else + continue + fi + fi + if ( \ + test "$TEST_CLIENT" = "shell" \ + && ! test -x "$TEST_ROOT/path/socat" \ + ) ; then + continue + fi + if ( \ + test -n "$ONLY_TEST_CLIENT" \ + && test "$TEST_CLIENT" != "$ONLY_TEST_CLIENT" \ + ) ; then + continue + fi + export POWERLINE_COMMAND_ARGS="--socket $ADDRESS" + export POWERLINE_COMMAND="$POWERLINE_COMMAND" + echo ">> powerline command is ${POWERLINE_COMMAND:-empty}" + J=-1 + for TEST_COMMAND in \ + "bash --norc --noprofile -i" \ + "zsh -f -i" \ + "fish -i" \ + "tcsh -f -i" \ + "busybox ash -i" \ + "mksh -i" \ + "dash -i" \ + "rc -i -p" + do + J="$(( J + 1 ))" + if test "$FAST" = 1 ; then + if test $(( (I + J) % $VARIANTS )) -ne $EXETEST ; then + continue + fi + fi + SH="${TEST_COMMAND%% *}" + if test -n "$ONLY_SHELL" && test "$ONLY_SHELL" != "$SH" ; then + continue + fi + if ! test -x "$TEST_ROOT/path/$SH" ; then + continue + fi + echo ">>> $(readlink "$TEST_ROOT/path/$SH")" + if ! run_test $TEST_TYPE $TEST_CLIENT $TEST_COMMAND ; then + fail "$SH-$TEST_TYPE-$TEST_CLIENT:test" F \ + "Failed checking $TEST_COMMAND" + fi + done + done + if test $TEST_TYPE = daemon ; then + "$PYTHON" ./scripts/powerline-daemon -s"$ADDRESS" -k + wait $(cat "$TEST_ROOT/daemon_pid") + if ! test -z "$(cat "$TEST_ROOT/daemon_log")" ; then + echo '____________________________________________________________' + echo "Daemon log:" + echo '============================================================' + cat "$TEST_ROOT/daemon_log" + fail "$SH-$TEST_TYPE-$TEST_CLIENT:log" E \ + "Non-empty daemon log for ${TEST_COMMAND}" + fi + fi + done +fi + +if "$PYTHON" scripts/powerline-daemon -s"$ADDRESS" \ + > "$TEST_ROOT/daemon_log_2" 2>&1 +then + sleep 1 + "$PYTHON" scripts/powerline-daemon -s"$ADDRESS" -k +else + fail "daemon:run" F "Daemon exited with status $?" +fi + +if ! test -z "$(cat "$TEST_ROOT/daemon_log_2")" ; then + echo '____________________________________________________________' + echo "Daemon log (2nd):" + echo '============================================================' + cat "$TEST_ROOT/daemon_log_2" + fail "daemon:log" E "Daemon run with non-empty log" +fi + +if ( test -z "${ONLY_SHELL}" || test "${ONLY_SHELL}" = "zsh" ) \ + && ( test -z "${ONLY_TEST_TYPE}" || test "${ONLY_TEST_TYPE}" = "zpython" ) \ + && "$TEST_ROOT/path/zsh" "$ROOT/tests/test_shells/zsh_test_script.zsh" +then + echo "> zpython" + if ! run_test zpython zpython zsh -f -i ; then + fail "zsh-zpython:test" F "Failed checking zsh -f -i" + fi +fi + +if test -z "${ONLY_SHELL}" || test "${ONLY_SHELL}" = "pdb" ; then + if test "$PYTHON_IMPLEMENTATION" != "PyPy" ; then + if test -z "${ONLY_TEST_TYPE}" || test "${ONLY_TEST_TYPE}" = "subclass" + then + echo "> pdb subclass" + if ! run_test subclass python $PDB_PYTHON \ + "$ROOT/tests/test_shells/pdb-main.py" + then + fail --allow-failure "pdb-subclass:test" F \ + "Failed checking $PDB_PYTHON $ROOT/tests/test_shells/pdb-main.py" + fi + fi + if test -z "${ONLY_TEST_TYPE}" || test "${ONLY_TEST_TYPE}" = "module" ; then + echo "> pdb module" + MODULE="powerline.bindings.pdb" + if test "$PYTHON_MM" = "2.6" ; then + MODULE="powerline.bindings.pdb.__main__" + fi + if ! run_test module python "$PDB_PYTHON" -m"$MODULE" \ + "$ROOT/tests/test_shells/pdb-script.py" + then + fail --allow-failure "pdb-module:test" F \ + "Failed checking $PDB_PYTHON -m$MODULE $ROOT/tests/test_shells/pdb-script" + fi + fi + fi +fi + +if test -z "${ONLY_SHELL}" || test "${ONLY_SHELL}" = "ipython" ; then + if "${PYTHON}" -c "try: import IPython${NL}except ImportError: raise SystemExit(1)" ; then + # Define some overrides which should be ignored by IPython. + export POWERLINE_CONFIG_OVERRIDES='common.term_escape_style=fbterm' + export POWERLINE_THEME_OVERRIDES='in.segments.left=[]' + echo "> ipython" + if ! run_test ipython ipython ${IPYTHON_PYTHON} -mIPython ; then + # Do not allow ipython tests to spoil the build + fail --allow-failure "ipython:test" F "Failed checking ${IPYTHON_PYTHON} -mIPython" + fi + unset POWERLINE_THEME_OVERRIDES + unset POWERLINE_CONFIG_OVERRIDES + fi +fi + +exit_suite diff --git a/tests/test_shells/waitpid.sh b/tests/test_shells/waitpid.sh new file mode 100755 index 0000000..8d98e21 --- /dev/null +++ b/tests/test_shells/waitpid.sh @@ -0,0 +1,4 @@ +#!/bin/sh +while ! test -e pid ; do + sleep 0.1s +done diff --git a/tests/test_shells/zsh_test_script.zsh b/tests/test_shells/zsh_test_script.zsh new file mode 100644 index 0000000..3957f56 --- /dev/null +++ b/tests/test_shells/zsh_test_script.zsh @@ -0,0 +1,11 @@ +set -e +set -x +. tests/bot-ci/scripts/common/main.sh +zmodload zpython || zmodload libzpython +zpython 'import zsh' +zpython 'import platform' +zpython 'zsh.setvalue("ZSH_PYTHON_VERSION", platform.python_version())' +zpython 'zsh.setvalue("ZSH_PYTHON_IMPLEMENTATION", platform.python_implementation())' + +[[ $ZSH_PYTHON_IMPLEMENTATION = $PYTHON_IMPLEMENTATION ]] +[[ $ZSH_PYTHON_VERSION = $PYTHON_VERSION ]] diff --git a/tests/test_vim/pyfiles/setup_statusline_catcher.py b/tests/test_vim/pyfiles/setup_statusline_catcher.py new file mode 100644 index 0000000..014a2e9 --- /dev/null +++ b/tests/test_vim/pyfiles/setup_statusline_catcher.py @@ -0,0 +1,18 @@ +# vim:fileencoding=utf-8:noet +import json + +import vim + +from powerline.lib.unicode import u + + +_powerline_old_render = powerline.render # NOQA + + +def _powerline_test_render_function(*args, **kwargs): + ret = _powerline_old_render(*args, **kwargs) + vim.eval('add(g:statusline_values, %s)' % json.dumps(u(ret))) + return ret + + +powerline.render = _powerline_test_render_function # NOQA diff --git a/tests/test_vim/test.sh b/tests/test_vim/test.sh new file mode 100755 index 0000000..ecd0285 --- /dev/null +++ b/tests/test_vim/test.sh @@ -0,0 +1,60 @@ +#!/bin/sh +. tests/shlib/common.sh +. tests/shlib/vterm.sh +. tests/shlib/vim.sh + +enter_suite vim final + +vterm_setup vim + +# Define some overrides. These ones must be ignored and do not affect Vim +# status/tab lines. +export POWERLINE_CONFIG_OVERRIDES='common.default_top_theme=ascii' +export POWERLINE_THEME_OVERRIDES='default.segments.left=[]' + +test_script() { + local vim="$1" ; shift + local script="$1" ; shift + local allow_failure_arg="$1" ; shift + echo "Running script $script with $vim" + if ! test -e "$vim" ; then + return 0 + fi + if ! script="$script" "$vim" -u NONE -c 'source $script' \ + || test -f message.fail + then + local test_name="${script##*/}" + fail $allow_failure_arg "${test_name%.vim}" \ + F "Failed script $script run with $vim" + if test -e message.fail ; then + cat message.fail >&2 + rm message.fail + fi + fi +} + +TEST_SCRIPT_ROOT="$ROOT/tests/test_vim/tests" + +cd "$TEST_ROOT" + +for script in "$TEST_SCRIPT_ROOT"/*.vim ; do + if test "${script%.old.vim}" = "${script}" ; then + test_script "$NEW_VIM" "$script" "" + fi +done + +if test "$PYTHON_VERSION_MAJOR.$PYTHON_VERSION_MINOR" = "2.7" ; then + ALLOW_FAILURE_ARG=--allow-failure +else + ALLOW_FAILURE_ARG= +fi + +if test -e "$OLD_VIM" ; then + for script in "$TEST_SCRIPT_ROOT"/*.old.vim ; do + test_script "$OLD_VIM" "$script" "$ALLOW_FAILURE_ARG" + done +fi + +vterm_shutdown + +exit_suite diff --git a/tests/test_vim/tests/commandt_plugin.vim b/tests/test_vim/tests/commandt_plugin.vim new file mode 100755 index 0000000..9f944b9 --- /dev/null +++ b/tests/test_vim/tests/commandt_plugin.vim @@ -0,0 +1,17 @@ +#!/usr/bin/vim -S +set nocompatible +set columns=80 +execute 'source' fnameescape(expand('<sfile>:p:h:h').'/vim_utils.vim') +call EnablePlugins('command-t') +call SourcePowerline() +let g:statusline_values = [] +call PyFile('setup_statusline_catcher') +execute 'CommandTBuffer'|call feedkeys("\<C-c>") +call RunPython('powerline.render = _powerline_old_render') +let g:expected_statusline = '%#Pl_231_16777215_240_5789784_bold# Command-T %#Pl_231_16777215_240_5789784_NONE# %#Pl_231_16777215_240_5789784_bold#BufferFinder %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE# ' +call CheckMessages() +if index(g:statusline_values, g:expected_statusline) == -1 + call CheckStatuslineValue(get(g:statusline_values, -1, ''), g:expected_statusline) + cquit +endif +qall diff --git a/tests/test_vim/tests/empty_encoding.old.vim b/tests/test_vim/tests/empty_encoding.old.vim new file mode 100755 index 0000000..124a10a --- /dev/null +++ b/tests/test_vim/tests/empty_encoding.old.vim @@ -0,0 +1,32 @@ +#!/usr/bin/vim -S +if has('multi_byte') + if empty(&encoding) + call writefile(['&encoding option value is empty, even though Vim has +multibyte'], 'message.fail') + cquit + endif + qall +endif +if !empty(&encoding) + call writefile(['&encoding option value is not empty, even though Vim does not have +multibyte'], 'message.fail') + cquit +endif + +let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files'] + +try + source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim +catch + call writefile(['Unexpected exception:', v:exception], 'message.fail') + cquit +endtry +set ls=2 +redrawstatus! +redir => g:messages + messages +redir END +let mess=split(g:messages, "\n") +if len(mess)>1 + call writefile(['Unexpected message(s):']+mess, 'message.fail') + cquit +endif +qall! diff --git a/tests/test_vim/tests/foreign_stl_override.vim b/tests/test_vim/tests/foreign_stl_override.vim new file mode 100644 index 0000000..2a5b8c0 --- /dev/null +++ b/tests/test_vim/tests/foreign_stl_override.vim @@ -0,0 +1,22 @@ +scriptencoding utf-8 +set encoding=utf-8 +let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files'] +set laststatus=2 +redir => g:messages + try + source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim + redrawstatus! + vsplit + redrawstatus! + setlocal statusline=«» + redrawstatus! + catch + call writefile(['Unexpected exception', v:exception], 'message.fail') + cquit + endtry +redir END +if g:messages =~# '\v\S' + call writefile(['Unexpected messages'] + split(g:messages, "\n", 1), 'message.fail') + cquit +endif +qall! diff --git a/tests/test_vim/tests/invalid_unicode.vim b/tests/test_vim/tests/invalid_unicode.vim new file mode 100644 index 0000000..ac91f3c --- /dev/null +++ b/tests/test_vim/tests/invalid_unicode.vim @@ -0,0 +1,19 @@ +set encoding=utf-8 +let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files'] +set laststatus=2 +set showtabline=2 +edit `="\xFF"` +redir => g:messages + try + source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim + redrawstatus! + catch + call writefile(['Unexpected exception', v:exception], 'message.fail') + cquit + endtry +redir END +if g:messages =~# '\v\S' + call writefile(['Unexpected messages'] + split(g:messages, "\n", 1), 'message.fail') + cquit +endif +qall! diff --git a/tests/test_vim/tests/local_overrides.vim b/tests/test_vim/tests/local_overrides.vim new file mode 100755 index 0000000..aba14e2 --- /dev/null +++ b/tests/test_vim/tests/local_overrides.vim @@ -0,0 +1,48 @@ +#!/usr/bin/vim -S +set encoding=utf-8 +let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files'] +let g:powerline_config_overrides = {'common': {'default_top_theme': 'ascii'}} +let g:powerline_theme_overrides = {'default': {'segment_data': {'line_current_symbol': {'contents': 'LN '}, 'branch': {'before': 'B '}}}} + +redir => g:messages + +try + python import powerline.vim + let pycmd = 'python' +catch + try + python3 import powerline.vim + let pycmd = 'python3' + catch + call writefile(['Unable to determine python version', v:exception], 'message.fail') + cquit + endtry +endtry + +try + execute pycmd 'powerline.vim.setup()' +catch + call writefile(['Failed to run setup function', v:exception], 'message.fail') + cquit +endtry + +try + let &columns = 80 + let result = eval(&statusline[2:]) +catch + call writefile(['Exception while evaluating &stl', v:exception], 'message.fail') + cquit +endtry + +if result isnot# '%#Pl_22_24320_148_11523840_bold# NORMAL %#Pl_148_11523840_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#unix%#Pl_240_5789784_236_3158064_NONE# %#Pl_247_10329757_240_5789784_NONE# 100%%%#Pl_252_13684944_240_5789784_NONE# %#Pl_235_2500134_252_13684944_NONE# LN %#Pl_235_2500134_252_13684944_bold# 1%#Pl_22_24576_252_13684944_NONE#:1 ' + call writefile(['Unexpected result', result], 'message.fail') + cquit +endif + +redir END +if g:messages =~ '\S' + call writefile(['Non-empty messages:', g:messages], 'message.fail') + cquit +endif + +qall! diff --git a/tests/test_vim/tests/nerdtree_plugin.vim b/tests/test_vim/tests/nerdtree_plugin.vim new file mode 100755 index 0000000..761cb5f --- /dev/null +++ b/tests/test_vim/tests/nerdtree_plugin.vim @@ -0,0 +1,11 @@ +#!/usr/bin/vim -S +set nocompatible +set columns=80 +execute 'source' fnameescape(expand('<sfile>:p:h:h').'/vim_utils.vim') +call EnablePlugins('nerdtree') +call SourcePowerline() +NERDTree /home +redrawstatus +call CheckCurrentStatusline('%#Pl_231_16777215_240_5789784_bold# /home %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE# ') +call CheckMessages() +qall diff --git a/tests/test_vim/tests/plugin_file.vim b/tests/test_vim/tests/plugin_file.vim new file mode 100755 index 0000000..1848933 --- /dev/null +++ b/tests/test_vim/tests/plugin_file.vim @@ -0,0 +1,22 @@ +#!/usr/bin/vim -S +set encoding=utf-8 +let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files'] +tabedit abc +tabedit def +try + source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim +catch + call writefile([v:exception], 'message.fail') + cquit +endtry +set ls=2 +redrawstatus! +redir =>mes + messages +redir END +let mess=split(mes, "\n") +if len(mess)>1 + call writefile(mess, 'message.fail') + cquit +endif +qall! diff --git a/tests/test_vim/tests/tabline.vim b/tests/test_vim/tests/tabline.vim new file mode 100755 index 0000000..ff76dc0 --- /dev/null +++ b/tests/test_vim/tests/tabline.vim @@ -0,0 +1,56 @@ +#!/usr/bin/vim -S +set encoding=utf-8 +let g:powerline_config_paths = [expand('<sfile>:p:h:h:h:h') . '/powerline/config_files'] +source <sfile>:p:h:h:h:h/powerline/bindings/vim/plugin/powerline.vim +edit abc +tabedit def +tabedit ghi + +redir => g:messages + +try + let &columns = 80 + let result = eval(&tabline[2:]) +catch + call writefile(['Exception while evaluating &tabline', v:exception], 'message.fail') + cquit +endtry + +if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %2T%#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE# %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs ' + call writefile(['Unexpected tabline', result], 'message.fail') + cquit +endif + +tabonly! + +try + let result = eval(&tabline[2:]) +catch + call writefile(['Exception while evaluating &tabline (2)', v:exception], 'message.fail') + cquit +endtry + +if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE# %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' + call writefile(['Unexpected tabline (2)', result], 'message.fail') + cquit +endif + +try + vsplit + let result = eval(&tabline[2:]) +catch + call writefile(['Exception while evaluating &tabline (3)', v:exception], 'message.fail') +endtry + +if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE# %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' + call writefile(['Unexpected tabline (3)', result], 'message.fail') + cquit +endif + +redir END +if g:messages =~ '\S' + call writefile(['Non-empty messages:', g:messages], 'message.fail') + cquit +endif + +qall! diff --git a/tests/test_vim/vim_utils.vim b/tests/test_vim/vim_utils.vim new file mode 100644 index 0000000..6219ec4 --- /dev/null +++ b/tests/test_vim/vim_utils.vim @@ -0,0 +1,88 @@ +let g:powerline_use_var_handler = 1 + +let g:pyfiles_root=expand('<sfile>:p:h').'/pyfiles' +let g:root=expand('<sfile>:p:h:h:h') +let g:mf=fnamemodify('message.fail', ':p') + +command -nargs=1 LST :call writefile(<args>, g:mf, 'a') | cquit +command -nargs=1 ERR :LST [<args>] +command -nargs=1 EXC :ERR 'Unexpected exception', <q-args>, v:exception, v:throwpoint + +function EnablePlugins(...) + let &runtimepath = join(map(copy(a:000), 'escape(g:root."/tests/vim-plugins/".v:val, "\\,")'), ',') + try + runtime! plugin/*.vim + silent doautocmd BufWinEnter + silent doautocmd BufEnter + silent doautocmd VimEnter + catch + EXC EnablePlugins + endtry +endfunction +function RecordStatusline() + let g:statusline = &l:statusline + if g:statusline[:1] is# '%!' + let g:statusline_value=eval(g:statusline[2:]) + else + ERR 'Statusline does not start with %!', g:statusline + endif + return '' +endfunction +function SourcePowerline() + let g:powerline_config_paths = [g:root . '/powerline/config_files'] + try + execute 'source' fnameescape(g:root . '/powerline/bindings/vim/plugin/powerline.vim') + catch + EXC SourcePowerline + endtry +endfunction +function NDiff(actual, expected) + return systemlist(shellescape(g:root.'/tests/bot-ci/scripts/ndiff-strings.py').' '.shellescape(a:actual).' '.shellescape(a:expected)) +endfunction +function CheckStatuslineValue(actual, expected) + if a:actual isnot# a:expected + LST ['Expected different statusline value', a:actual, a:expected] + NDiff(a:actual, a:expected) + endif +endfunction +function CheckRecordedStatuslineValue(expected) + return CheckStatuslineValue(g:statusline_value, a:expected) +endfunction +function GetCurrentStatusline() + if &l:statusline[:1] isnot# '%!' + ERR 'Statusline does not start with %!', &l:statusline + endif + return eval(&l:statusline[2:]) +endfunction +function CheckCurrentStatusline(expected) + return CheckStatuslineValue(GetCurrentStatusline(), a:expected) +endfunction +function CheckMessages() + if !empty(g:powerline_log_messages) + LST ['Unexpected messages in log'] + g:powerline_log_messages + endif + redir => mes + messages + redir END + let mesl = split(mes, "\n")[1:] + if !empty(mesl) + LST ['Unexpected messages'] + split(mes, "\n", 1) + endif +endfunction +function RunPython(s) + if has('python') + execute 'python' a:s + else + execute 'python3' a:s + endif +endfunction +function PyFile(f) + if has('python') + execute 'pyfile' fnameescape(g:pyfiles_root.'/'.a:f.'.py') + else + execute 'py3file' fnameescape(g:pyfiles_root.'/'.a:f.'.py') + endif +endfunction + +for s:c in ['noremap', 'noremap!'] + execute s:c '<special><expr>' '<Plug>(PowerlineTestRecordStatusline)' 'RecordStatusline()' +endfor diff --git a/tests/vim_sys_path/vim.py b/tests/vim_sys_path/vim.py new file mode 100644 index 0000000..e9dba66 --- /dev/null +++ b/tests/vim_sys_path/vim.py @@ -0,0 +1,7 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import tests.modules.vim as vim + + +globals().update(vim._init()) diff --git a/tools/colors.map b/tools/colors.map new file mode 100644 index 0000000..61d8c4a --- /dev/null +++ b/tools/colors.map @@ -0,0 +1,646 @@ +Grey 545454 +Grey, Silver C0C0C0 +grey BEBEBE +LightGray D3D3D3 +LightSlateGrey 778899 +SlateGray 708090 +SlateGray1 C6E2FF +SlateGray2 B9D3EE +SlateGray3 9FB6CD +SlateGray4 6C7B8B +black 000000 +grey0 000000 +grey1 030303 +grey2 050505 +grey3 080808 +grey4 0A0A0A +grey5 0D0D0D +grey6 0F0F0F +grey7 121212 +grey8 141414 +grey9 171717 +grey10 1A1A1A +grey11 1C1C1C +grey12 1F1F1F +grey13 212121 +grey14 242424 +grey15 262626 +grey16 292929 +grey17 2B2B2B +grey18 2E2E2E +grey19 303030 +grey20 333333 +grey21 363636 +grey22 383838 +grey23 3B3B3B +grey24 3D3D3D +grey25 404040 +grey26 424242 +grey27 454545 +grey28 474747 +grey29 4A4A4A +grey30 4D4D4D +grey31 4F4F4F +grey32 525252 +grey33 545454 +grey34 575757 +grey35 595959 +grey36 5C5C5C +grey37 5E5E5E +grey38 616161 +grey39 636363 +grey40 666666 +grey41, DimGrey 696969 +grey42 6B6B6B +grey43 6E6E6E +grey44 707070 +grey45 737373 +grey46 757575 +grey47 787878 +grey48 7A7A7A +grey49 7D7D7D +grey50 7F7F7F +grey51 828282 +grey52 858585 +grey53 878787 +grey54 8A8A8A +grey55 8C8C8C +grey56 8F8F8F +grey57 919191 +grey58 949494 +grey59 969696 +grey60 999999 +grey61 9C9C9C +grey62 9E9E9E +grey63 A1A1A1 +grey64 A3A3A3 +grey65 A6A6A6 +grey66 A8A8A8 +grey67 ABABAB +grey68 ADADAD +grey69 B0B0B0 +grey70 B3B3B3 +grey71 B5B5B5 +grey72 B8B8B8 +grey73 BABABA +grey74 BDBDBD +grey75 BFBFBF +grey76 C2C2C2 +grey77 C4C4C4 +grey78 C7C7C7 +grey79 C9C9C9 +grey80 CCCCCC +grey81 CFCFCF +grey82 D1D1D1 +grey83 D4D4D4 +grey84 D6D6D6 +grey85 D9D9D9 +grey86 DBDBDB +grey87 DEDEDE +grey88 E0E0E0 +grey89 E3E3E3 +grey90 E5E5E5 +grey91 E8E8E8 +grey92 EBEBEB +grey93 EDEDED +grey94 F0F0F0 +grey95 F2F2F2 +grey96 F5F5F5 +grey97 F7F7F7 +grey98 FAFAFA +grey99 FCFCFC +grey100, White FFFFFF +Dark Slate Grey 2F4F4F +Dim Grey 545454 +Very Light Grey CDCDCD +Free Speech Grey 635688 +AliceBlue F0F8FF +BlueViolet 8A2BE2 +Cadet Blue 5F9F9F +CadetBlue 5F9EA0 +CadetBlue 5F9EA0 +CadetBlue1 98F5FF +CadetBlue2 8EE5EE +CadetBlue3 7AC5CD +CadetBlue4 53868B +Corn Flower Blue 42426F +CornflowerBlue 6495ED +DarkSlateBlue 483D8B +DarkTurquoise 00CED1 +DeepSkyBlue 00BFFF +DeepSkyBlue1 00BFFF +DeepSkyBlue2 00B2EE +DeepSkyBlue3 009ACD +DeepSkyBlue4 00688B +DodgerBlue 1E90FF +DodgerBlue1 1E90FF +DodgerBlue2 1C86EE +DodgerBlue3 1874CD +DodgerBlue4 104E8B +LightBlue ADD8E6 +LightBlue1 BFEFFF +LightBlue2 B2DFEE +LightBlue3 9AC0CD +LightBlue4 68838B +LightCyan E0FFFF +LightCyan1 E0FFFF +LightCyan2 D1EEEE +LightCyan3 B4CDCD +LightCyan4 7A8B8B +LightSkyBlue 87CEFA +LightSkyBlue1 B0E2FF +LightSkyBlue2 A4D3EE +LightSkyBlue3 8DB6CD +LightSkyBlue4 607B8B +LightSlateBlue 8470FF +LightSteelBlue B0C4DE +LightSteelBlue1 CAE1FF +LightSteelBlue2 BCD2EE +LightSteelBlue3 A2B5CD +LightSteelBlue4 6E7B8B +Aquamarine 70DB93 +MediumBlue 0000CD +MediumSlateBlue 7B68EE +MediumTurquoise 48D1CC +MidnightBlue 191970 +NavyBlue 000080 +PaleTurquoise AFEEEE +PaleTurquoise1 BBFFFF +PaleTurquoise2 AEEEEE +PaleTurquoise3 96CDCD +PaleTurquoise4 668B8B +PowderBlue B0E0E6 +RoyalBlue 4169E1 +RoyalBlue1 4876FF +RoyalBlue2 436EEE +RoyalBlue3 3A5FCD +RoyalBlue4 27408B +RoyalBlue5 002266 +SkyBlue 87CEEB +SkyBlue1 87CEFF +SkyBlue2 7EC0EE +SkyBlue3 6CA6CD +SkyBlue4 4A708B +SlateBlue 6A5ACD +SlateBlue1 836FFF +SlateBlue2 7A67EE +SlateBlue3 6959CD +SlateBlue4 473C8B +SteelBlue 4682B4 +SteelBlue1 63B8FF +SteelBlue2 5CACEE +SteelBlue3 4F94CD +SteelBlue4 36648B +aquamarine 7FFFD4 +aquamarine1 7FFFD4 +aquamarine2 76EEC6 +aquamarine3, MediumAquamarine 66CDAA +aquamarine4 458B74 +azure F0FFFF +azure1 F0FFFF +azure2 E0EEEE +azure3 C1CDCD +azure4 838B8B +blue 0000FF +blue1 0000FF +blue2 0000EE +blue3 0000CD +blue4 00008B +aqua 00FFFF +True Iris Blue 03B4CC +cyan 00FFFF +cyan1 00FFFF +cyan2 00EEEE +cyan3 00CDCD +cyan4 008B8B +navy 000080 +teal 008080 +turquoise 40E0D0 +turquoise1 00F5FF +turquoise2 00E5EE +turquoise3 00C5CD +turquoise4 00868B +DarkSlateGray 2F4F4F +DarkSlateGray1 97FFFF +DarkSlateGray2 8DEEEE +DarkSlateGray3 79CDCD +DarkSlateGray4 528B8B +Dark Slate Blue 241882 +Dark Turquoise 7093DB +Medium Slate Blue 7F00FF +Medium Turquoise 70DBDB +Midnight Blue 2F2F4F +Navy Blue 23238E +Neon Blue 4D4DFF +New Midnight Blue 00009C +Rich Blue 5959AB +Sky Blue 3299CC +Slate Blue 007FFF +Summer Sky 38B0DE +Iris Blue 03B4C8 +Free Speech Blue 4156C5 +RosyBrown BC8F8F +RosyBrown1 FFC1C1 +RosyBrown2 EEB4B4 +RosyBrown3 CD9B9B +RosyBrown4 8B6969 +SaddleBrown 8B4513 +SandyBrown F4A460 +beige F5F5DC +brown A52A2A +brown A62A2A +brown1 FF4040 +brown2 EE3B3B +brown3 CD3333 +brown4 8B2323 +dark brown 5C4033 +burlywood DEB887 +burlywood1 FFD39B +burlywood2 EEC591 +burlywood3 CDAA7D +burlywood4 8B7355 +baker's chocolate 5C3317 +chocolate D2691E +chocolate1 FF7F24 +chocolate2 EE7621 +chocolate3 CD661D +chocolate4 8B4513 +peru CD853F +tan D2B48C +tan1 FFA54F +tan2 EE9A49 +tan3 CD853F +tan4 8B5A2B +Dark Tan 97694F +Dark Wood 855E42 +Light Wood 856363 +Medium Wood A68064 +New Tan EBC79E +Semi-Sweet Chocolate 6B4226 +Sienna 8E6B23 +Tan DB9370 +Very Dark Brown 5C4033 +Dark Green 2F4F2F +DarkGreen 006400 +dark green copper 4A766E +DarkKhaki BDB76B +DarkOliveGreen 556B2F +DarkOliveGreen1 CAFF70 +DarkOliveGreen2 BCEE68 +DarkOliveGreen3 A2CD5A +DarkOliveGreen4 6E8B3D +olive 808000 +DarkSeaGreen 8FBC8F +DarkSeaGreen1 C1FFC1 +DarkSeaGreen2 B4EEB4 +DarkSeaGreen3 9BCD9B +DarkSeaGreen4 698B69 +ForestGreen 228B22 +GreenYellow ADFF2F +LawnGreen 7CFC00 +LightSeaGreen 20B2AA +LimeGreen 32CD32 +MediumSeaGreen 3CB371 +MediumSpringGreen 00FA9A +MintCream F5FFFA +OliveDrab 6B8E23 +OliveDrab1 C0FF3E +OliveDrab2 B3EE3A +OliveDrab3 9ACD32 +OliveDrab4 698B22 +PaleGreen 98FB98 +PaleGreen1 9AFF9A +PaleGreen2 90EE90 +PaleGreen3 7CCD7C +PaleGreen4 548B54 +SeaGreen, SeaGreen4 2E8B57 +SeaGreen1 54FF9F +SeaGreen2 4EEE94 +SeaGreen3 43CD80 +SpringGreen 00FF7F +SpringGreen1 00FF7F +SpringGreen2 00EE76 +SpringGreen3 00CD66 +SpringGreen4 008B45 +YellowGreen 9ACD32 +chartreuse 7FFF00 +chartreuse1 7FFF00 +chartreuse2 76EE00 +chartreuse3 66CD00 +chartreuse4 458B00 +green 00FF00 +green 008000 +lime 00FF00 +green1 00FF00 +green2 00EE00 +green3 00CD00 +green4 008B00 +khaki F0E68C +khaki1 FFF68F +khaki2 EEE685 +khaki3 CDC673 +khaki4 8B864E +Dark Olive Green 4F4F2F +Green Yellow <a href=#sic>[sic]</a> D19275 +Hunter Green <a href=#sic>[sic]</a> 8E2323 +Forest Green, Khaki, Medium Aquamarine 238E23 +Medium Forest Green DBDB70 +Medium Sea Green 426F42 +Medium Spring Green 7FFF00 +Pale Green 8FBC8F +Sea Green 238E68 +Spring Green 00FF7F +Free Speech Green 09F911 +Free Speech Aquamarine 029D74 +DarkOrange FF8C00 +DarkOrange1 FF7F00 +DarkOrange2 EE7600 +DarkOrange3 CD6600 +DarkOrange4 8B4500 +DarkSalmon E9967A +LightCoral F08080 +LightSalmon FFA07A +LightSalmon1 FFA07A +LightSalmon2 EE9572 +LightSalmon3 CD8162 +LightSalmon4 8B5742 +PeachPuff FFDAB9 +PeachPuff1 FFDAB9 +PeachPuff2 EECBAD +PeachPuff3 CDAF95 +PeachPuff4 8B7765 +bisque FFE4C4 +bisque1 FFE4C4 +bisque2 EED5B7 +bisque3 CDB79E +bisque4 8B7D6B +coral FF7F00 +coral FF7F50 +coral1 FF7256 +coral2 EE6A50 +coral3 CD5B45 +coral4 8B3E2F +honeydew F0FFF0 +honeydew1 F0FFF0 +honeydew2 E0EEE0 +honeydew3 C1CDC1 +honeydew4 838B83 +orange FFA500 +orange1 FFA500 +orange2 EE9A00 +orange3 CD8500 +orange4 8B5A00 +salmon FA8072 +salmon1 FF8C69 +salmon2 EE8262 +salmon3 CD7054 +salmon4 8B4C39 +sienna A0522D +sienna1 FF8247 +sienna2 EE7942 +sienna3 CD6839 +sienna4 8B4726 +Mandarian Orange 8E2323 +Orange FF7F00 +Orange Red FF2400 +DeepPink FF1493 +DeepPink1 FF1493 +DeepPink2 EE1289 +DeepPink3 CD1076 +DeepPink4 8B0A50 +HotPink FF69B4 +HotPink1 FF6EB4 +HotPink2 EE6AA7 +HotPink3 CD6090 +HotPink4 8B3A62 +IndianRed CD5C5C +IndianRed1 FF6A6A +IndianRed2 EE6363 +IndianRed3 CD5555 +IndianRed4 8B3A3A +LightPink FFB6C1 +LightPink1 FFAEB9 +LightPink2 EEA2AD +LightPink3 CD8C95 +LightPink4 8B5F65 +MediumVioletRed C71585 +MistyRose FFE4E1 +MistyRose1 FFE4E1 +MistyRose2 EED5D2 +MistyRose3 CDB7B5 +MistyRose4 8B7D7B +OrangeRed FF4500 +OrangeRed1 FF4500 +OrangeRed2 EE4000 +OrangeRed3 CD3700 +OrangeRed4 8B2500 +PaleVioletRed DB7093 +PaleVioletRed1 FF82AB +PaleVioletRed2 EE799F +PaleVioletRed3 CD6889 +PaleVioletRed4 8B475D +VioletRed D02090 +VioletRed1 FF3E96 +VioletRed2 EE3A8C +VioletRed3 CD3278 +VioletRed4 8B2252 +firebrick B22222 +firebrick1 FF3030 +firebrick2 EE2C2C +firebrick3 CD2626 +firebrick4 8B1A1A +pink FFC0CB +pink1 FFB5C5 +pink2 EEA9B8 +pink3 CD919E +pink4 8B636C +Flesh F5CCB0 +Feldspar D19275 +red FF0000 +red1 FF0000 +red2 EE0000 +red3 CD0000 +red4 8B0000 +tomato FF6347 +tomato1 FF6347 +tomato2 EE5C42 +tomato3 CD4F39 +tomato4 8B3626 +Dusty Rose 856363 +Firebrick 8E2323 +Indian Red F5CCB0 +Pink BC8F8F +Salmon 6F4242 +Scarlet 8C1717 +Spicy Pink FF1CAE +Free Speech Magenta E35BD8 +Free Speech Red C00000 +DarkOrchid 9932CC +DarkOrchid1 BF3EFF +DarkOrchid2 B23AEE +DarkOrchid3 9A32CD +DarkOrchid4 68228B +DarkViolet 9400D3 +LavenderBlush FFF0F5 +LavenderBlush1 FFF0F5 +LavenderBlush2 EEE0E5 +LavenderBlush3 CDC1C5 +LavenderBlush4 8B8386 +MediumOrchid BA55D3 +MediumOrchid1 E066FF +MediumOrchid2 D15FEE +MediumOrchid3 B452CD +MediumOrchid4 7A378B +MediumPurple 9370DB +Medium Orchid 9370DB +MediumPurple1 AB82FF +Dark Orchid 9932CD +MediumPurple2 9F79EE +MediumPurple3 8968CD +MediumPurple4 5D478B +lavender E6E6FA +magenta FF00FF +fuchsia FF00FF +magenta1 FF00FF +magenta2 EE00EE +magenta3 CD00CD +magenta4 8B008B +maroon B03060 +maroon1 FF34B3 +maroon2 EE30A7 +maroon3 CD2990 +maroon4 8B1C62 +orchid DA70D6 +Orchid DB70DB +orchid1 FF83FA +orchid2 EE7AE9 +orchid3 CD69C9 +orchid4 8B4789 +plum DDA0DD +plum1 FFBBFF +plum2 EEAEEE +plum3 CD96CD +plum4 8B668B +purple A020F0 +purple 800080 +purple1 9B30FF +purple2 912CEE +purple3 7D26CD +purple4 551A8B +thistle D8BFD8 +thistle1 FFE1FF +thistle2 EED2EE +thistle3 CDB5CD +thistle4 8B7B8B +violet EE82EE +violet blue 9F5F9F +Dark Purple 871F78 +Maroon 800000 +Medium Violet Red DB7093 +Neon Pink FF6EC7 +Plum EAADEA +Thistle D8BFD8 +Turquoise ADEAEA +Violet 4F2F4F +Violet Red CC3299 +AntiqueWhite FAEBD7 +AntiqueWhite1 FFEFDB +AntiqueWhite2 EEDFCC +AntiqueWhite3 CDC0B0 +AntiqueWhite4 8B8378 +FloralWhite FFFAF0 +GhostWhite F8F8FF +NavajoWhite FFDEAD +NavajoWhite1 FFDEAD +NavajoWhite2 EECFA1 +NavajoWhite3 CDB38B +NavajoWhite4 8B795E +OldLace FDF5E6 +WhiteSmoke F5F5F5 +gainsboro DCDCDC +ivory FFFFF0 +ivory1 FFFFF0 +ivory2 EEEEE0 +ivory3 CDCDC1 +ivory4 8B8B83 +linen FAF0E6 +seashell FFF5EE +seashell1 FFF5EE +seashell2 EEE5DE +seashell3 CDC5BF +seashell4 8B8682 +snow FFFAFA +snow1 FFFAFA +snow2 EEE9E9 +snow3 CDC9C9 +snow4 8B8989 +wheat F5DEB3 +wheat1 FFE7BA +wheat2 EED8AE +wheat3 CDBA96 +wheat4 8B7E66 +white FFFFFF +Quartz D9D9F3 +Wheat D8D8BF +BlanchedAlmond FFEBCD +DarkGoldenrod B8860B +DarkGoldenrod1 FFB90F +DarkGoldenrod2 EEAD0E +DarkGoldenrod3 CD950C +DarkGoldenrod4 8B6508 +LemonChiffon FFFACD +LemonChiffon1 FFFACD +LemonChiffon2 EEE9BF +LemonChiffon3 CDC9A5 +LemonChiffon4 8B8970 +LightGoldenrod EEDD82 +LightGoldenrod1 FFEC8B +LightGoldenrod2 EEDC82 +LightGoldenrod3 CDBE70 +LightGoldenrod4 8B814C +LightGoldenrodYellow FAFAD2 +LightYellow FFFFE0 +LightYellow1 FFFFE0 +LightYellow2 EEEED1 +LightYellow3 CDCDB4 +LightYellow4 8B8B7A +PaleGoldenrod EEE8AA +PapayaWhip FFEFD5 +cornsilk FFF8DC +cornsilk1 FFF8DC +cornsilk2 EEE8CD +cornsilk3 CDC8B1 +cornsilk4 8B8878 +goldenrod DAA520 +goldenrod1 FFC125 +goldenrod2 EEB422 +goldenrod3 CD9B1D +goldenrod4 8B6914 +moccasin FFE4B5 +yellow FFFF00 +yellow1 FFFF00 +yellow2 EEEE00 +yellow3 CDCD00 +yellow4 8B8B00 +gold FFD700 +gold1 FFD700 +gold2 EEC900 +gold3 CDAD00 +gold4 8B7500 +Goldenrod DBDB70 +Medium Goldenrod EAEAAE +Yellow Green 99CC32 +copper B87333 +cool copper D98719 +Green Copper 856363 +brass B5A642 +bronze 8C7853 +bronze II A67D3D +bright gold D9D919 +Old Gold CFB53B +CSS Gold CC9900 +gold CD7F32 +silver E6E8FA +Silver, Grey C0C0C0 +Light Steel Blue 545454 +Steel Blue 236B8E diff --git a/tools/colors_find.py b/tools/colors_find.py new file mode 100755 index 0000000..cf66ef9 --- /dev/null +++ b/tools/colors_find.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os + +from colormath.color_objects import sRGBColor, LabColor +from colormath.color_conversions import convert_color +from colormath.color_diff import delta_e_cie2000 + + +def get_lab(name, rgb): + rgb = sRGBColor( + int(rgb[:2], 16), int(rgb[2:4], 16), int(rgb[4:6], 16), + is_upscaled=True + ) + lab = convert_color(rgb, LabColor) + return name, lab + + +with open(os.path.join(os.path.dirname(__file__), 'colors.map'), 'r') as f: + colors = [get_lab(*line.split('\t')) for line in f] + + +ulab = get_lab(None, sys.argv[1])[1] + + +def find_color(urgb, colors): + cur_distance = 3 * (255 ** 2 + 1) + cur_color = None + for color, clab in colors: + dist = delta_e_cie2000(ulab, clab) + if dist < cur_distance: + cur_distance = dist + cur_color = (color, clab) + return cur_color + + +cur_color = find_color(ulab, colors) + + +def lab_to_csi(lab): + rgb = convert_color(lab, sRGBColor) + colstr = ';2;' + ';'.join((str(i) for i in get_upscaled_values(rgb))) + return colstr + 'm' + + +def get_upscaled_values(rgb): + return [min(max(0, i), 255) for i in rgb.get_upscaled_value_tuple()] + + +def get_rgb(lab): + rgb = convert_color(lab, sRGBColor) + rgb = sRGBColor(*get_upscaled_values(rgb), is_upscaled=True) + return rgb.get_rgb_hex()[1:] + +print(get_rgb(ulab), ':', cur_color[0], ':', get_rgb(cur_color[1])) + +col_1 = lab_to_csi(ulab) +col_2 = lab_to_csi(cur_color[1]) +sys.stdout.write('\033[48' + col_1 + '\033[38' + col_2 + 'abc\033[0m <-- bg:urgb, fg:crgb\n') +sys.stdout.write('\033[48' + col_2 + '\033[38' + col_1 + 'abc\033[0m <-- bg:crgb, fg:urgb\n') diff --git a/tools/generate_gradients.py b/tools/generate_gradients.py new file mode 100755 index 0000000..290e75e --- /dev/null +++ b/tools/generate_gradients.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet + +'''Gradients generator +''' + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import json +import argparse + +from itertools import groupby + +from colormath.color_objects import sRGBColor, LabColor +from colormath.color_conversions import convert_color +from colormath.color_diff import delta_e_cie2000 + +from powerline.colorscheme import cterm_to_hex + + +def num2(s): + try: + return (True, [int(v) for v in s.partition(' ')[::2]]) + except TypeError: + return (False, [float(v) for v in s.partition(' ')[::2]]) + + +def rgbint_to_lab(rgbint): + rgb = sRGBColor( + (rgbint >> 16) & 0xFF, (rgbint >> 8) & 0xFF, rgbint & 0xFF, + is_upscaled=True + ) + return convert_color(rgb, LabColor) + + +cterm_to_lab = tuple((rgbint_to_lab(v) for v in cterm_to_hex)) + + +def color(s): + if len(s) <= 3: + return cterm_to_lab[int(s)] + else: + return rgbint_to_lab(int(s, 16)) + + +def nums(s): + return [int(i) for i in s.split()] + + +def linear_gradient(start_value, stop_value, start_offset, stop_offset, offset): + return start_value + ((offset - start_offset) * (stop_value - start_value) / (stop_offset - start_offset)) + + +def lab_gradient(slab, elab, soff, eoff, off): + svals = slab.get_value_tuple() + evals = elab.get_value_tuple() + return LabColor(*[ + linear_gradient(start_value, end_value, soff, eoff, off) + for start_value, end_value in zip(svals, evals) + ]) + + +def generate_gradient_function(DATA): + def gradient_function(y): + initial_offset = 0 + for offset, start, end in DATA: + if y <= offset: + return lab_gradient(start, end, initial_offset, offset, y) + initial_offset = offset + return gradient_function + + +def get_upscaled_values(rgb): + return [min(max(0, i), 255) for i in rgb.get_upscaled_value_tuple()] + + +def get_rgb(lab): + rgb = convert_color(lab, sRGBColor) + rgb = sRGBColor(*get_upscaled_values(rgb), is_upscaled=True) + return rgb.get_rgb_hex()[1:] + + +def find_color(ulab, colors, ctrans): + cur_distance = float('inf') + cur_color = None + i = 0 + for clab in colors: + dist = delta_e_cie2000(ulab, clab) + if dist < cur_distance: + cur_distance = dist + cur_color = (ctrans(i), clab) + i += 1 + return cur_color + + +def print_color(color): + if type(color) is int: + colstr = '5;' + str(color) + else: + rgb = convert_color(color, sRGBColor) + colstr = '2;' + ';'.join((str(i) for i in get_upscaled_values(rgb))) + sys.stdout.write('\033[48;' + colstr + 'm ') + + +def print_colors(colors, num): + for i in range(num): + color = colors[int(round(i * (len(colors) - 1) / num))] + print_color(color) + sys.stdout.write('\033[0m\n') + + +def dec_scale_generator(num): + j = 0 + r = '' + while num: + r += '\033[{0}m'.format(j % 2) + for i in range(10): + r += str(i) + num -= 1 + if not num: + break + j += 1 + r += '\033[0m\n' + return r + + +def compute_steps(gradient, weights): + maxweight = len(gradient) - 1 + if weights: + weight_sum = sum(weights) + norm_weights = [100.0 * weight / weight_sum for weight in weights] + steps = [0] + for weight in norm_weights: + steps.append(steps[-1] + weight) + steps.pop(0) + steps.pop(0) + else: + step = m / maxweight + steps = [i * step for i in range(1, maxweight + 1)] + return steps + + +palettes = { + '16': (cterm_to_lab[:16], lambda c: c), + '256': (cterm_to_lab, lambda c: c), + None: (cterm_to_lab[16:], lambda c: c + 16), +} + + +def show_scale(rng, num_output): + if not rng and num_output >= 32 and (num_output - 1) // 10 >= 4 and (num_output - 1) % 10 == 0: + sys.stdout.write('0') + sys.stdout.write(''.join(('%*u' % (num_output // 10, i) for i in range(10, 101, 10)))) + sys.stdout.write('\n') + else: + if rng: + vmin, vmax = rng[1] + isint = rng[0] + else: + isint = True + vmin = 0 + vmax = 100 + s = '' + lasts = ' ' + str(vmax) + while len(s) + len(lasts) < num_output: + curpc = len(s) + 1 if s else 0 + curval = vmin + curpc * (vmax - vmin) / num_output + if isint: + curval = int(round(curval)) + s += str(curval) + ' ' + sys.stdout.write(s[:-1] + lasts + '\n') + sys.stdout.write(dec_scale_generator(num_output) + '\n') + + +if __name__ == '__main__': + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('gradient', nargs='*', metavar='COLOR', type=color, help='List of colors (either indexes from 8-bit palette or 24-bit RGB in hexadecimal notation)') + p.add_argument('-n', '--num_items', metavar='INT', type=int, help='Number of items in resulting list', default=101) + p.add_argument('-N', '--num_output', metavar='INT', type=int, help='Number of characters in sample', default=101) + p.add_argument('-r', '--range', metavar='V1 V2', type=num2, help='Use this range when outputting scale') + p.add_argument('-s', '--show', action='store_true', help='If present output gradient sample') + p.add_argument('-p', '--palette', choices=('16', '256'), help='Use this palette. Defaults to 240-color palette (256 colors without first 16)') + p.add_argument('-w', '--weights', metavar='INT INT ...', type=nums, help='Adjust weights of colors. Number of weights must be equal to number of colors') + p.add_argument('-C', '--omit-terminal', action='store_true', help='If present do not compute values for terminal') + + args = p.parse_args() + + m = args.num_items + + steps = compute_steps(args.gradient, args.weights) + + data = [ + (weight, args.gradient[i - 1], args.gradient[i]) + for weight, i in zip(steps, range(1, len(args.gradient))) + ] + gr_func = generate_gradient_function(data) + gradient = [gr_func(y) for y in range(0, m)] + + r = [get_rgb(lab) for lab in gradient] + if not args.omit_terminal: + r2 = [find_color(lab, *palettes[args.palette])[0] for lab in gradient] + r3 = [i[0] for i in groupby(r2)] + + if not args.omit_terminal: + print(json.dumps(r3) + ',') + print(json.dumps(r2) + ',') + print(json.dumps(r)) + + if args.show: + print_colors(args.gradient, args.num_output) + if not args.omit_terminal: + print_colors(r3, args.num_output) + print_colors(r2, args.num_output) + print_colors(gradient, args.num_output) + + show_scale(args.range, args.num_output) diff --git a/tools/purge-PRs.py b/tools/purge-PRs.py new file mode 100755 index 0000000..38f44d9 --- /dev/null +++ b/tools/purge-PRs.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import argparse + +from getpass import getpass + +from github import Github + + +p = argparse.ArgumentParser(description='Powerline release script') +p.add_argument('-u', '--user', type=str, metavar='USER', help='Github username.', required=True) +p.add_argument('-p', '--password', type=str, metavar='PASS', help='Github password. You will be prompted if it is not supplied.') + +if __name__ == '__main__': + args = p.parse_args() + user = args.user + password = args.password or getpass('Password for {0}: '.format(user)) + gh = Github(user, password) + grepo = gh.get_repo('powerline/powerline') + for pr in grepo.get_pulls(): + if pr.base.ref != 'develop': + issue = grepo.get_issue(pr.number) + issue.create_comment('PRs to any branch, but develop, are not accepted.', ) + issue.add_to_labels('s:invalid') + issue.edit(state='closed') |