summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:04:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:04:06 +0000
commita8637fe80c24fb04bf6cc8ae8459877535f1341b (patch)
tree5d263b4543e10940f5e9a79a8fe981c5a3414bd7
parentInitial commit. (diff)
downloadpowerline-a8637fe80c24fb04bf6cc8ae8459877535f1341b.tar.xz
powerline-a8637fe80c24fb04bf6cc8ae8459877535f1341b.zip
Adding upstream version 2.7.upstream/2.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--.editorconfig20
-rw-r--r--.gitattributes1
-rw-r--r--.gitignore16
-rw-r--r--.local.vimrc11
-rw-r--r--.travis.yml42
-rw-r--r--CONTRIBUTING.rst138
-rw-r--r--LICENSE21
-rw-r--r--MANIFEST.in7
-rw-r--r--README.rst95
-rw-r--r--client/powerline.c164
-rwxr-xr-xclient/powerline.py104
-rwxr-xr-xclient/powerline.sh53
-rw-r--r--docs/.gitignore1
-rw-r--r--docs/Makefile39
-rw-r--r--docs/source/_static/css/theme_overrides.css6
-rw-r--r--docs/source/_static/img/icons/cross.pngbin0 -> 473 bytes
-rw-r--r--docs/source/_static/img/icons/error.pngbin0 -> 543 bytes
-rw-r--r--docs/source/_static/img/icons/tick.pngbin0 -> 451 bytes
-rw-r--r--docs/source/_static/img/pl-mode-insert.pngbin0 -> 6894 bytes
-rw-r--r--docs/source/_static/img/pl-mode-normal.pngbin0 -> 6879 bytes
-rw-r--r--docs/source/_static/img/pl-mode-replace.pngbin0 -> 6949 bytes
-rw-r--r--docs/source/_static/img/pl-mode-visual.pngbin0 -> 6868 bytes
-rw-r--r--docs/source/_static/img/pl-truncate1.pngbin0 -> 6882 bytes
-rw-r--r--docs/source/_static/img/pl-truncate2.pngbin0 -> 5541 bytes
-rw-r--r--docs/source/_static/img/pl-truncate3.pngbin0 -> 3093 bytes
-rw-r--r--docs/source/commands.rst9
-rw-r--r--docs/source/commands/config.rst12
-rw-r--r--docs/source/commands/daemon.rst12
-rw-r--r--docs/source/commands/lint.rst14
-rw-r--r--docs/source/commands/main.rst12
-rw-r--r--docs/source/conf.py70
-rw-r--r--docs/source/configuration.rst146
-rw-r--r--docs/source/configuration/listers.rst35
-rw-r--r--docs/source/configuration/local.rst260
-rw-r--r--docs/source/configuration/reference.rst602
-rw-r--r--docs/source/configuration/segments.rst28
-rw-r--r--docs/source/configuration/segments/common.rst57
-rw-r--r--docs/source/configuration/segments/i3wm.rst6
-rw-r--r--docs/source/configuration/segments/pdb.rst7
-rw-r--r--docs/source/configuration/segments/shell.rst6
-rw-r--r--docs/source/configuration/segments/tmux.rst6
-rw-r--r--docs/source/configuration/segments/vim.rst46
-rw-r--r--docs/source/configuration/selectors.rst17
-rw-r--r--docs/source/configuration/selectors/vim.rst6
-rw-r--r--docs/source/develop.rst13
-rw-r--r--docs/source/develop/extensions.rst47
-rw-r--r--docs/source/develop/listers.rst49
-rw-r--r--docs/source/develop/local-themes.rst59
-rw-r--r--docs/source/develop/segments.rst547
-rw-r--r--docs/source/develop/tips-and-tricks.rst21
-rw-r--r--docs/source/index.rst28
-rw-r--r--docs/source/installation.rst132
-rw-r--r--docs/source/installation/linux.rst110
-rw-r--r--docs/source/installation/osx.rst67
-rw-r--r--docs/source/license-and-credits.rst31
-rw-r--r--docs/source/overview.rst67
-rw-r--r--docs/source/powerline_autodoc.py64
-rw-r--r--docs/source/powerline_automan.py408
-rw-r--r--docs/source/tips-and-tricks.rst94
-rw-r--r--docs/source/troubleshooting.rst329
-rw-r--r--docs/source/troubleshooting/linux.rst78
-rw-r--r--docs/source/troubleshooting/osx.rst71
-rw-r--r--docs/source/usage.rst88
-rw-r--r--docs/source/usage/other.rst209
-rw-r--r--docs/source/usage/shell-prompts.rst144
-rw-r--r--docs/source/usage/wm-widgets.rst102
-rw-r--r--font/10-powerline-symbols.conf105
-rw-r--r--font/PowerlineSymbols.otfbin0 -> 2264 bytes
-rw-r--r--powerline/__init__.py991
-rw-r--r--powerline/bindings/__init__.py0
-rwxr-xr-xpowerline/bindings/awesome/powerline-awesome.py20
-rw-r--r--powerline/bindings/awesome/powerline.lua15
-rwxr-xr-xpowerline/bindings/bar/powerline-bar.py60
-rw-r--r--powerline/bindings/bash/powerline.sh153
-rw-r--r--powerline/bindings/config.py286
-rw-r--r--powerline/bindings/fish/powerline-setup.fish109
-rwxr-xr-xpowerline/bindings/i3/powerline-i3.py52
-rw-r--r--powerline/bindings/ipython/__init__.py0
-rw-r--r--powerline/bindings/ipython/post_0_11.py123
-rw-r--r--powerline/bindings/ipython/pre_0_11.py146
-rw-r--r--powerline/bindings/ipython/since_5.py81
-rwxr-xr-xpowerline/bindings/lemonbar/powerline-lemonbar.py61
-rw-r--r--powerline/bindings/pdb/__init__.py183
-rwxr-xr-xpowerline/bindings/pdb/__main__.py9
-rw-r--r--powerline/bindings/qtile/__init__.py0
-rw-r--r--powerline/bindings/qtile/widget.py61
-rw-r--r--powerline/bindings/rc/powerline.rc92
-rw-r--r--powerline/bindings/shell/powerline.sh239
-rw-r--r--powerline/bindings/tcsh/powerline.tcsh60
-rw-r--r--powerline/bindings/tmux/__init__.py84
-rw-r--r--powerline/bindings/tmux/powerline-base.conf11
-rw-r--r--powerline/bindings/tmux/powerline.conf2
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.7_plus.conf3
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.8.conf5
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.8_minus.conf11
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.8_plus.conf5
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.9_plus.conf8
-rw-r--r--powerline/bindings/tmux/powerline_tmux_2.1_plus.conf3
-rw-r--r--powerline/bindings/vim/__init__.py482
-rw-r--r--powerline/bindings/vim/autoload/powerline/debug.vim20
-rw-r--r--powerline/bindings/vim/plugin/powerline.vim169
-rw-r--r--powerline/bindings/wm/__init__.py85
-rw-r--r--powerline/bindings/wm/awesome.py59
-rw-r--r--powerline/bindings/zsh/__init__.py228
-rw-r--r--powerline/bindings/zsh/powerline.zsh216
-rw-r--r--powerline/colorscheme.py147
-rw-r--r--powerline/commands/__init__.py0
-rw-r--r--powerline/commands/config.py109
-rw-r--r--powerline/commands/daemon.py24
-rw-r--r--powerline/commands/lemonbar.py35
-rwxr-xr-xpowerline/commands/lint.py21
-rw-r--r--powerline/commands/main.py190
-rw-r--r--powerline/config.py10
-rw-r--r--powerline/config_files/colors.json124
-rw-r--r--powerline/config_files/colorschemes/default.json56
-rw-r--r--powerline/config_files/colorschemes/ipython/__main__.json6
-rw-r--r--powerline/config_files/colorschemes/pdb/__main__.json8
-rw-r--r--powerline/config_files/colorschemes/pdb/default.json5
-rw-r--r--powerline/config_files/colorschemes/pdb/solarized.json5
-rw-r--r--powerline/config_files/colorschemes/shell/__main__.json10
-rw-r--r--powerline/config_files/colorschemes/shell/default.json16
-rw-r--r--powerline/config_files/colorschemes/shell/solarized.json13
-rw-r--r--powerline/config_files/colorschemes/solarized.json40
-rw-r--r--powerline/config_files/colorschemes/tmux/default.json14
-rw-r--r--powerline/config_files/colorschemes/tmux/solarized.json14
-rw-r--r--powerline/config_files/colorschemes/vim/__main__.json50
-rw-r--r--powerline/config_files/colorschemes/vim/default.json154
-rw-r--r--powerline/config_files/colorschemes/vim/solarized.json121
-rw-r--r--powerline/config_files/colorschemes/vim/solarizedlight.json122
-rw-r--r--powerline/config_files/config.json53
-rw-r--r--powerline/config_files/themes/ascii.json153
-rw-r--r--powerline/config_files/themes/ipython/in.json25
-rw-r--r--powerline/config_files/themes/ipython/in2.json12
-rw-r--r--powerline/config_files/themes/ipython/out.json24
-rw-r--r--powerline/config_files/themes/ipython/rewrite.json23
-rw-r--r--powerline/config_files/themes/pdb/default.json27
-rw-r--r--powerline/config_files/themes/powerline.json151
-rw-r--r--powerline/config_files/themes/powerline_terminus.json151
-rw-r--r--powerline/config_files/themes/powerline_unicode7.json165
-rw-r--r--powerline/config_files/themes/shell/__main__.json14
-rw-r--r--powerline/config_files/themes/shell/continuation.json12
-rw-r--r--powerline/config_files/themes/shell/default.json43
-rw-r--r--powerline/config_files/themes/shell/default_leftonly.json34
-rw-r--r--powerline/config_files/themes/shell/select.json13
-rw-r--r--powerline/config_files/themes/tmux/default.json28
-rw-r--r--powerline/config_files/themes/unicode.json151
-rw-r--r--powerline/config_files/themes/unicode_terminus.json151
-rw-r--r--powerline/config_files/themes/unicode_terminus_condensed.json151
-rw-r--r--powerline/config_files/themes/vim/__main__.json10
-rw-r--r--powerline/config_files/themes/vim/cmdwin.json18
-rw-r--r--powerline/config_files/themes/vim/default.json128
-rw-r--r--powerline/config_files/themes/vim/help.json36
-rw-r--r--powerline/config_files/themes/vim/plugin_commandt.json26
-rw-r--r--powerline/config_files/themes/vim/plugin_gundo-preview.json18
-rw-r--r--powerline/config_files/themes/vim/plugin_gundo.json18
-rw-r--r--powerline/config_files/themes/vim/plugin_nerdtree.json17
-rw-r--r--powerline/config_files/themes/vim/quickfix.json40
-rw-r--r--powerline/config_files/themes/vim/tabline.json93
-rw-r--r--powerline/config_files/themes/wm/default.json29
-rw-r--r--powerline/dist/systemd/powerline-daemon.service10
-rw-r--r--powerline/ipython.py68
-rw-r--r--powerline/lemonbar.py21
-rw-r--r--powerline/lib/__init__.py28
-rw-r--r--powerline/lib/config.py218
-rwxr-xr-xpowerline/lib/debug.py97
-rw-r--r--powerline/lib/dict.py88
-rw-r--r--powerline/lib/encoding.py125
-rw-r--r--powerline/lib/humanize_bytes.py25
-rw-r--r--powerline/lib/inotify.py184
-rw-r--r--powerline/lib/memoize.py42
-rw-r--r--powerline/lib/monotonic.py100
-rw-r--r--powerline/lib/overrides.py80
-rw-r--r--powerline/lib/path.py18
-rw-r--r--powerline/lib/shell.py133
-rw-r--r--powerline/lib/threaded.py262
-rw-r--r--powerline/lib/unicode.py283
-rw-r--r--powerline/lib/url.py17
-rw-r--r--powerline/lib/vcs/__init__.py276
-rw-r--r--powerline/lib/vcs/bzr.py108
-rw-r--r--powerline/lib/vcs/git.py208
-rw-r--r--powerline/lib/vcs/mercurial.py88
-rw-r--r--powerline/lib/watcher/__init__.py76
-rw-r--r--powerline/lib/watcher/inotify.py268
-rw-r--r--powerline/lib/watcher/stat.py44
-rw-r--r--powerline/lib/watcher/tree.py90
-rw-r--r--powerline/lib/watcher/uv.py207
-rw-r--r--powerline/lint/__init__.py625
-rw-r--r--powerline/lint/checks.py866
-rw-r--r--powerline/lint/context.py68
-rw-r--r--powerline/lint/imp.py56
-rw-r--r--powerline/lint/inspect.py63
-rw-r--r--powerline/lint/markedjson/__init__.py19
-rw-r--r--powerline/lint/markedjson/composer.py119
-rw-r--r--powerline/lint/markedjson/constructor.py285
-rw-r--r--powerline/lint/markedjson/error.py241
-rw-r--r--powerline/lint/markedjson/events.py97
-rw-r--r--powerline/lint/markedjson/loader.py25
-rw-r--r--powerline/lint/markedjson/markedvalue.py151
-rw-r--r--powerline/lint/markedjson/nodes.py55
-rw-r--r--powerline/lint/markedjson/parser.py255
-rw-r--r--powerline/lint/markedjson/reader.py141
-rw-r--r--powerline/lint/markedjson/resolver.py131
-rw-r--r--powerline/lint/markedjson/scanner.py499
-rw-r--r--powerline/lint/markedjson/tokens.py72
-rw-r--r--powerline/lint/selfcheck.py16
-rw-r--r--powerline/lint/spec.py759
-rw-r--r--powerline/listers/__init__.py0
-rw-r--r--powerline/listers/i3wm.py68
-rw-r--r--powerline/listers/pdb.py37
-rw-r--r--powerline/listers/vim.py123
-rw-r--r--powerline/matchers/__init__.py6
-rw-r--r--powerline/matchers/vim/__init__.py19
-rw-r--r--powerline/matchers/vim/plugin/__init__.py6
-rw-r--r--powerline/matchers/vim/plugin/commandt.py14
-rw-r--r--powerline/matchers/vim/plugin/gundo.py16
-rw-r--r--powerline/matchers/vim/plugin/nerdtree.py15
-rw-r--r--powerline/pdb.py48
-rw-r--r--powerline/renderer.py594
-rw-r--r--powerline/renderers/__init__.py0
-rw-r--r--powerline/renderers/i3bar.py36
-rw-r--r--powerline/renderers/ipython/__init__.py34
-rw-r--r--powerline/renderers/ipython/pre_5.py56
-rw-r--r--powerline/renderers/ipython/since_5.py130
-rw-r--r--powerline/renderers/lemonbar.py61
-rw-r--r--powerline/renderers/pango_markup.py39
-rw-r--r--powerline/renderers/pdb.py50
-rw-r--r--powerline/renderers/shell/__init__.py182
-rw-r--r--powerline/renderers/shell/bash.py18
-rw-r--r--powerline/renderers/shell/ksh.py19
-rw-r--r--powerline/renderers/shell/rcsh.py7
-rw-r--r--powerline/renderers/shell/readline.py14
-rw-r--r--powerline/renderers/shell/tcsh.py31
-rw-r--r--powerline/renderers/shell/zsh.py16
-rw-r--r--powerline/renderers/tmux.py79
-rw-r--r--powerline/renderers/vim.py188
-rw-r--r--powerline/segment.py450
-rw-r--r--powerline/segments/__init__.py63
-rw-r--r--powerline/segments/common/__init__.py0
-rw-r--r--powerline/segments/common/bat.py302
-rw-r--r--powerline/segments/common/env.py197
-rw-r--r--powerline/segments/common/mail.py78
-rw-r--r--powerline/segments/common/net.py315
-rw-r--r--powerline/segments/common/players.py607
-rw-r--r--powerline/segments/common/sys.py183
-rw-r--r--powerline/segments/common/time.py94
-rw-r--r--powerline/segments/common/vcs.py89
-rw-r--r--powerline/segments/common/wthr.py235
-rw-r--r--powerline/segments/i3wm.py155
-rw-r--r--powerline/segments/ipython.py9
-rw-r--r--powerline/segments/pdb.py61
-rw-r--r--powerline/segments/shell.py174
-rw-r--r--powerline/segments/tmux.py22
-rw-r--r--powerline/segments/vim/__init__.py793
-rw-r--r--powerline/segments/vim/plugin/__init__.py6
-rw-r--r--powerline/segments/vim/plugin/ale.py52
-rw-r--r--powerline/segments/vim/plugin/capslock.py30
-rw-r--r--powerline/segments/vim/plugin/commandt.py97
-rw-r--r--powerline/segments/vim/plugin/nerdtree.py25
-rw-r--r--powerline/segments/vim/plugin/syntastic.py43
-rw-r--r--powerline/segments/vim/plugin/tagbar.py51
-rw-r--r--powerline/selectors/__init__.py0
-rw-r--r--powerline/selectors/vim.py10
-rw-r--r--powerline/shell.py38
-rw-r--r--powerline/theme.py182
-rw-r--r--powerline/vim.py347
-rw-r--r--scripts/.gitignore1
-rwxr-xr-xscripts/powerline-config22
-rwxr-xr-xscripts/powerline-daemon495
-rwxr-xr-xscripts/powerline-lint13
-rwxr-xr-xscripts/powerline-release.py242
-rwxr-xr-xscripts/powerline-render31
-rw-r--r--setup.py135
-rw-r--r--tests/__init__.py0
-rwxr-xr-xtests/install.sh81
-rw-r--r--tests/modules/__init__.py94
-rw-r--r--tests/modules/lib/__init__.py183
-rw-r--r--tests/modules/lib/config_mock.py230
-rw-r--r--tests/modules/lib/fsconfig.py83
-rw-r--r--tests/modules/lib/terminal.py307
-rw-r--r--tests/modules/lib/vterm.py193
-rw-r--r--tests/modules/matchers.py6
-rw-r--r--tests/modules/vim.py927
-rw-r--r--tests/shlib/common.sh150
-rw-r--r--tests/shlib/vim.sh33
-rw-r--r--tests/shlib/vterm.sh17
-rw-r--r--tests/terminfo/s/screenbin0 -> 1564 bytes
-rw-r--r--tests/terminfo/s/st-256colorbin0 -> 2324 bytes
-rwxr-xr-xtests/test.sh42
-rwxr-xr-xtests/test_awesome/path/awesome-client3
-rw-r--r--tests/test_awesome/powerline/config.json7
-rw-r--r--tests/test_awesome/powerline/themes/wm/default.json18
-rw-r--r--tests/test_awesome/powerline/themes/wm/dvi.json18
-rwxr-xr-xtests/test_awesome/test.sh188
-rwxr-xr-xtests/test_bar/path/lemonbar11
-rwxr-xr-xtests/test_bar/path/xrandr31
-rw-r--r--tests/test_bar/powerline/config.json9
-rw-r--r--tests/test_bar/powerline/themes/wm/default.json18
-rw-r--r--tests/test_bar/powerline/themes/wm/dvi.json18
-rwxr-xr-xtests/test_bar/test.sh195
-rwxr-xr-xtests/test_daemon/test.sh41
-rw-r--r--tests/test_in_vterm/shell/inits/dash16
-rwxr-xr-xtests/test_in_vterm/test.sh13
-rwxr-xr-xtests/test_in_vterm/test_shells.py162
-rwxr-xr-xtests/test_in_vterm/test_shells.sh119
-rwxr-xr-xtests/test_in_vterm/test_tmux.py251
-rwxr-xr-xtests/test_in_vterm/test_tmux.sh44
-rwxr-xr-xtests/test_in_vterm/test_vim.py73
-rwxr-xr-xtests/test_in_vterm/test_vim.sh39
-rwxr-xr-xtests/test_lint/test.sh10
-rw-r--r--tests/test_python/empty0
-rwxr-xr-xtests/test_python/test.sh13
-rw-r--r--tests/test_python/test_cmdline.py149
-rw-r--r--tests/test_python/test_config_merging.py270
-rw-r--r--tests/test_python/test_config_reload.py319
-rw-r--r--tests/test_python/test_configuration.py877
-rw-r--r--tests/test_python/test_lib.py733
-rw-r--r--tests/test_python/test_lib_config.py52
-rw-r--r--tests/test_python/test_listers.py227
-rw-r--r--tests/test_python/test_logging.py467
-rw-r--r--tests/test_python/test_provided_config_files.py201
-rw-r--r--tests/test_python/test_segments.py1711
-rw-r--r--tests/test_python/test_selectors.py36
-rw-r--r--tests/test_python/test_watcher.py245
-rwxr-xr-xtests/test_shells/bgscript.sh5
-rw-r--r--tests/test_shells/inputs/bash69
-rw-r--r--tests/test_shells/inputs/busybox37
-rw-r--r--tests/test_shells/inputs/dash37
-rw-r--r--tests/test_shells/inputs/fish69
-rw-r--r--tests/test_shells/inputs/ipython7
-rw-r--r--tests/test_shells/inputs/mksh38
-rw-r--r--tests/test_shells/inputs/pdb89
-rw-r--r--tests/test_shells/inputs/rc33
-rw-r--r--tests/test_shells/inputs/tcsh24
-rw-r--r--tests/test_shells/inputs/zsh90
-rw-r--r--tests/test_shells/ipython_home/profile_default/ipython_config.py19
-rw-r--r--tests/test_shells/outputs/bash.daemon.ok42
-rw-r--r--tests/test_shells/outputs/bash.nodaemon.ok42
-rw-r--r--tests/test_shells/outputs/busybox.daemon.ok29
-rw-r--r--tests/test_shells/outputs/busybox.nodaemon.ok29
-rw-r--r--tests/test_shells/outputs/dash.daemon.ok28
-rw-r--r--tests/test_shells/outputs/dash.nodaemon.ok28
-rw-r--r--tests/test_shells/outputs/fish.ok52
-rw-r--r--tests/test_shells/outputs/ipython.ok14
-rw-r--r--tests/test_shells/outputs/mksh.daemon.ok32
-rw-r--r--tests/test_shells/outputs/mksh.nodaemon.ok32
-rw-r--r--tests/test_shells/outputs/pdb.module.ok222
-rw-r--r--tests/test_shells/outputs/pdb.subclass.ok217
-rw-r--r--tests/test_shells/outputs/rc.daemon.ok24
-rw-r--r--tests/test_shells/outputs/rc.nodaemon.ok24
-rw-r--r--tests/test_shells/outputs/tcsh.ok17
-rw-r--r--tests/test_shells/outputs/zsh.daemon.ok52
-rw-r--r--tests/test_shells/outputs/zsh.nodaemon.ok52
-rw-r--r--tests/test_shells/outputs/zsh.zpython.ok52
-rw-r--r--tests/test_shells/pdb-main.py24
-rw-r--r--tests/test_shells/pdb-script.py38
-rwxr-xr-xtests/test_shells/postproc.py130
-rwxr-xr-xtests/test_shells/run_script.py125
-rwxr-xr-xtests/test_shells/test.sh491
-rwxr-xr-xtests/test_shells/waitpid.sh4
-rw-r--r--tests/test_shells/zsh_test_script.zsh11
-rw-r--r--tests/test_vim/pyfiles/setup_statusline_catcher.py18
-rwxr-xr-xtests/test_vim/test.sh60
-rwxr-xr-xtests/test_vim/tests/commandt_plugin.vim17
-rwxr-xr-xtests/test_vim/tests/empty_encoding.old.vim32
-rw-r--r--tests/test_vim/tests/foreign_stl_override.vim22
-rw-r--r--tests/test_vim/tests/invalid_unicode.vim19
-rwxr-xr-xtests/test_vim/tests/local_overrides.vim48
-rwxr-xr-xtests/test_vim/tests/nerdtree_plugin.vim11
-rwxr-xr-xtests/test_vim/tests/plugin_file.vim22
-rwxr-xr-xtests/test_vim/tests/tabline.vim56
-rw-r--r--tests/test_vim/vim_utils.vim88
-rw-r--r--tests/vim_sys_path/vim.py7
-rw-r--r--tools/colors.map646
-rwxr-xr-xtools/colors_find.py63
-rwxr-xr-xtools/generate_gradients.py217
-rwxr-xr-xtools/purge-PRs.py27
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>`_.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..23b2ab2
--- /dev/null
+++ b/LICENSE
@@ -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
new file mode 100644
index 0000000..33a3837
--- /dev/null
+++ b/docs/source/_static/img/icons/cross.png
Binary files differ
diff --git a/docs/source/_static/img/icons/error.png b/docs/source/_static/img/icons/error.png
new file mode 100644
index 0000000..dbfda22
--- /dev/null
+++ b/docs/source/_static/img/icons/error.png
Binary files differ
diff --git a/docs/source/_static/img/icons/tick.png b/docs/source/_static/img/icons/tick.png
new file mode 100644
index 0000000..c277e6b
--- /dev/null
+++ b/docs/source/_static/img/icons/tick.png
Binary files differ
diff --git a/docs/source/_static/img/pl-mode-insert.png b/docs/source/_static/img/pl-mode-insert.png
new file mode 100644
index 0000000..9b09e18
--- /dev/null
+++ b/docs/source/_static/img/pl-mode-insert.png
Binary files differ
diff --git a/docs/source/_static/img/pl-mode-normal.png b/docs/source/_static/img/pl-mode-normal.png
new file mode 100644
index 0000000..29d3716
--- /dev/null
+++ b/docs/source/_static/img/pl-mode-normal.png
Binary files differ
diff --git a/docs/source/_static/img/pl-mode-replace.png b/docs/source/_static/img/pl-mode-replace.png
new file mode 100644
index 0000000..d7c89a4
--- /dev/null
+++ b/docs/source/_static/img/pl-mode-replace.png
Binary files differ
diff --git a/docs/source/_static/img/pl-mode-visual.png b/docs/source/_static/img/pl-mode-visual.png
new file mode 100644
index 0000000..d654763
--- /dev/null
+++ b/docs/source/_static/img/pl-mode-visual.png
Binary files differ
diff --git a/docs/source/_static/img/pl-truncate1.png b/docs/source/_static/img/pl-truncate1.png
new file mode 100644
index 0000000..c687502
--- /dev/null
+++ b/docs/source/_static/img/pl-truncate1.png
Binary files differ
diff --git a/docs/source/_static/img/pl-truncate2.png b/docs/source/_static/img/pl-truncate2.png
new file mode 100644
index 0000000..1630f1d
--- /dev/null
+++ b/docs/source/_static/img/pl-truncate2.png
Binary files differ
diff --git a/docs/source/_static/img/pl-truncate3.png b/docs/source/_static/img/pl-truncate3.png
new file mode 100644
index 0000000..83e5b21
--- /dev/null
+++ b/docs/source/_static/img/pl-truncate3.png
Binary files differ
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&section=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 &#8211; (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
new file mode 100644
index 0000000..b1582af
--- /dev/null
+++ b/font/PowerlineSymbols.otf
Binary files differ
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
new file mode 100644
index 0000000..d380072
--- /dev/null
+++ b/tests/terminfo/s/screen
Binary files differ
diff --git a/tests/terminfo/s/st-256color b/tests/terminfo/s/st-256color
new file mode 100644
index 0000000..9c2db04
--- /dev/null
+++ b/tests/terminfo/s/st-256color
Binary files differ
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=""
+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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)
+  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT"
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+ foo  
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                       foo 
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)
+  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT"
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+ foo  
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                       foo 
+  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                            
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1]+ Terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+set_theme_option default_leftonly.segment_data.hostname.display false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1   USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+set_theme_option default_leftonly.segment_data.hostname.display false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1   USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+ foo  
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+                                                                                                                                                                                                                                                                                                           
+                                                                                                                                                                                                                                                                                                      foo 
+                                                                                                                                                                                                                                                                                                           
+ USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ DEFAULT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ DEFAULT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+ INSERT  USER  …  shell  3rd  «Unicode!»  
+   BRANCH 
+   BRANCH 
+   BRANCH 
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 @@
+
+ In [2]  bool 42
+ 2>  bool(42)
+ Out[2]  True
+
+ In [3]  bool 44
+ 3>  bool(44)
+ Out[3]  True
+
+ In [4]  class Test(object):
+   pass
+  
+
+ In [5]  exit
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 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1
+[1] + Terminated bash -c ...
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo -n
+  BRANCH  …  tmp  shell  3rd  echo '
+                                   abc
+                                   def
+                                   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1
+[1] + Terminated bash -c ...
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  tmp  shell  3rd  echo -n
+  BRANCH  …  tmp  shell  3rd  echo '
+   abc
+   def
+   '
+
+abc
+def
+
+  BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC
+  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse
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 @@
+ 1  pdb-script.py:6 <module>  
+--Call--
+-> class Foo(object):
+ 2  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> class Foo(object):
+ 2  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> def __init__(self):
+ 2  pdb-script.py:6 <module>  pdb-script.py:7 Foo  
+-> @classmethod
+ 2  pdb-script.py:6 <module>  pdb-script.py:13 Foo  
+-> @staticmethod
+ 2  pdb-script.py:6 <module>  pdb-script.py:17 Foo  
+-> def bra(self):
+ 2  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+--Return--
+-> def bra(self):
+ 2  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+-> def brah():
+ 1  pdb-script.py:25 <module>  
+-> f = Foo()
+ 1  pdb-script.py:29 <module>  
+--Call--
+-> def __init__(self):
+ 2  pdb-script.py:29 <module>  pdb-script.py:7 __init__  
+-> nop('__init__')
+ 2  pdb-script.py:29 <module>  pdb-script.py:8 __init__  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+-> self.bar()
+ 2  pdb-script.py:29 <module>  pdb-script.py:9 __init__  
+--Call--
+-> @classmethod
+ 3  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+-> self.baz()
+ 2  pdb-script.py:29 <module>  pdb-script.py:10 __init__  
+--Call--
+-> @staticmethod
+ 3  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:17 baz  
+-> nop(1)
+ 3  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 3  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+-> self.bra()
+ 2  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+--Call--
+-> def bra(self):
+ 3  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 3  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Return--
+-> self.bra()
+ 2  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+-> Foo.bar()
+ 1  pdb-script.py:30 <module>  
+--Call--
+-> @classmethod
+ 2  pdb-script.py:30 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 2  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 2  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+-> Foo.baz()
+ 1  pdb-script.py:31 <module>  
+--Call--
+-> @staticmethod
+ 2  pdb-script.py:31 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 2  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 2  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+-> Foo.bra(f)
+ 1  pdb-script.py:32 <module>  
+--Call--
+-> def bra(self):
+ 2  pdb-script.py:32 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+-> f.bar()
+ 1  pdb-script.py:34 <module>  
+--Call--
+-> @classmethod
+ 2  pdb-script.py:34 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 2  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 2  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+-> f.baz()
+ 1  pdb-script.py:35 <module>  
+--Call--
+-> @staticmethod
+ 2  pdb-script.py:35 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 2  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 2  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+-> f.bra()
+ 1  pdb-script.py:36 <module>  
+--Call--
+-> def bra(self):
+ 2  pdb-script.py:36 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 2  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+-> brah()
+ 1  pdb-script.py:38 <module>  
+--Call--
+-> def brah():
+ 2  pdb-script.py:38 <module>  pdb-script.py:25 brah  
+-> nop('brah')
+ 2  pdb-script.py:38 <module>  pdb-script.py:26 brah  
+--Call--
+-> def nop(_):
+ 3  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:2 nop  
+-> pass
+ 3  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 3  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> nop('brah')
+ 2  pdb-script.py:38 <module>  pdb-script.py:26 brah  
+--Return--
+-> brah()
+ 1  pdb-script.py:38 <module>  
+--Return--
+ 0  
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 @@
+ 2  <string>:1 <module>  pdb-script.py:6 <module>  
+--Call--
+-> class Foo(object):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> class Foo(object):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:6 Foo  
+-> def __init__(self):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:7 Foo  
+-> @classmethod
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:13 Foo  
+-> @staticmethod
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:17 Foo  
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+--Return--
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:6 <module>  pdb-script.py:21 Foo  
+-> def brah():
+ 2  <string>:1 <module>  pdb-script.py:25 <module>  
+-> f = Foo()
+ 2  <string>:1 <module>  pdb-script.py:29 <module>  
+--Call--
+-> def __init__(self):
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:7 __init__  
+-> nop('__init__')
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:8 __init__  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:29 <module>  pdb-script.py:8 __init__  pdb-script.py:3 nop  
+-> self.bar()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:9 __init__  
+--Call--
+-> @classmethod
+ 4  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 5  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 5  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 5  pdb-script.py:9 __init__  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:9 __init__  pdb-script.py:15 bar  
+-> self.baz()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:10 __init__  
+--Call--
+-> @staticmethod
+ 4  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:17 baz  
+-> nop(1)
+ 4  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 5  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 5  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 5  pdb-script.py:10 __init__  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 4  pdb-script.py:29 <module>  pdb-script.py:10 __init__  pdb-script.py:19 baz  
+-> self.bra()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+--Call--
+-> def bra(self):
+ 4  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 5  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 5  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 5  pdb-script.py:11 __init__  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 4  pdb-script.py:29 <module>  pdb-script.py:11 __init__  pdb-script.py:22 bra  
+--Return--
+-> self.bra()
+ 3  <string>:1 <module>  pdb-script.py:29 <module>  pdb-script.py:11 __init__  
+-> Foo.bar()
+ 2  <string>:1 <module>  pdb-script.py:30 <module>  
+--Call--
+-> @classmethod
+ 3  <string>:1 <module>  pdb-script.py:30 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:30 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:30 <module>  pdb-script.py:15 bar  
+-> Foo.baz()
+ 2  <string>:1 <module>  pdb-script.py:31 <module>  
+--Call--
+-> @staticmethod
+ 3  <string>:1 <module>  pdb-script.py:31 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:31 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:31 <module>  pdb-script.py:19 baz  
+-> Foo.bra(f)
+ 2  <string>:1 <module>  pdb-script.py:32 <module>  
+--Call--
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:32 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:32 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:32 <module>  pdb-script.py:22 bra  
+-> f.bar()
+ 2  <string>:1 <module>  pdb-script.py:34 <module>  
+--Call--
+-> @classmethod
+ 3  <string>:1 <module>  pdb-script.py:34 <module>  pdb-script.py:13 bar  
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:34 <module>  pdb-script.py:15 bar  pdb-script.py:3 nop  
+--Return--
+-> nop(cls.__name__)
+ 3  <string>:1 <module>  pdb-script.py:34 <module>  pdb-script.py:15 bar  
+-> f.baz()
+ 2  <string>:1 <module>  pdb-script.py:35 <module>  
+--Call--
+-> @staticmethod
+ 3  <string>:1 <module>  pdb-script.py:35 <module>  pdb-script.py:17 baz  
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:35 <module>  pdb-script.py:19 baz  pdb-script.py:3 nop  
+--Return--
+-> nop(1)
+ 3  <string>:1 <module>  pdb-script.py:35 <module>  pdb-script.py:19 baz  
+-> f.bra()
+ 2  <string>:1 <module>  pdb-script.py:36 <module>  
+--Call--
+-> def bra(self):
+ 3  <string>:1 <module>  pdb-script.py:36 <module>  pdb-script.py:21 bra  
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:36 <module>  pdb-script.py:22 bra  pdb-script.py:3 nop  
+--Return--
+-> nop(self.__class__.__name__)
+ 3  <string>:1 <module>  pdb-script.py:36 <module>  pdb-script.py:22 bra  
+-> brah()
+ 2  <string>:1 <module>  pdb-script.py:38 <module>  
+--Call--
+-> def brah():
+ 3  <string>:1 <module>  pdb-script.py:38 <module>  pdb-script.py:25 brah  
+-> nop('brah')
+ 3  <string>:1 <module>  pdb-script.py:38 <module>  pdb-script.py:26 brah  
+--Call--
+-> def nop(_):
+ 4  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:2 nop  
+-> pass
+ 4  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> pass
+ 4  pdb-script.py:38 <module>  pdb-script.py:26 brah  pdb-script.py:3 nop  
+--Return--
+-> nop('brah')
+ 3  <string>:1 <module>  pdb-script.py:38 <module>  pdb-script.py:26 brah  
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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment'
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = ()
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `{cat pid} ; sleep 1s
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd $DIR1
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../$DIR2
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  false
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  shell  3rd  «Unicode!»  echo `{
+                                           echo Continuation!
+                                          }
+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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = '/home/foo/.virtenvs/some-virtual-environment'
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV = ()
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `{cat pid} ; sleep 1s
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd $DIR1
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../$DIR2
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  false
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  1  set_theme_option default_leftonly.segment_data.hostname.display false
+ USER   BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segment_data.user.display false
+  BRANCH  …  shell  3rd  «Unicode!»  echo `{
+    echo Continuation!
+   }
+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 @@
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  
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 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1] + terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bindkey -v ; set_theme default
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd   COMMND   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  echo abc
+abc
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  false
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
+ INSERT  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
+ INSERT  …  tmp  shell  3rd  select abc in def ghi jkl
+ select                          do
+ select                           echo $abc
+ select                           break
+ select                          done
+1) def 2) ghi 3) jkl
+                 Select variant  1
+def
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_LEFT"
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+ foo  
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                      foo 
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above
+ INSERT  …  tmp  shell  3rd  hash -d foo=$PWD:h ; cd .
+ INSERT  ~foo  3rd  set_theme_option default.dividers.left.hard \$ABC
+ INSERT $ABC~foo  3rd $ABCtrue
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 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1] + terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bindkey -v ; set_theme default
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd   COMMND   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  echo abc
+abc
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  false
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
+ INSERT  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
+ INSERT  …  tmp  shell  3rd  select abc in def ghi jkl
+ select  do
+ select   echo $abc
+ select   break
+ select  done
+1) def 2) ghi 3) jkl
+ Select variant  1
+def
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_LEFT"
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+ foo  
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                      foo 
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above
+ INSERT  …  tmp  shell  3rd  hash -d foo=$PWD:h ; cd .
+ INSERT  ~foo  3rd  set_theme_option default.dividers.left.hard \$ABC
+ INSERT $ABC~foo  3rd $ABCtrue
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 @@
+
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd .git
+  HOSTNAME  USER   BRANCH  …  shell  3rd  .git  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV="/home/USER/.virtenvs/some-virtual-environment"
+  HOSTNAME  USER  (e) some-virtual-environment   BRANCH  …  tmp  shell  3rd  VIRTUAL_ENV=
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bgscript.sh & waitpid.sh
+[1] PID
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
+[1] + terminated bgscript.sh
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  cd "$DIR1"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^[[32m  cd ../"$DIR2"
+  HOSTNAME  USER   BRANCH  …  shell  3rd  ^H  cd ../'\[\]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  \[\]  cd ../'%%'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  %%  cd ../'#[bold]'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  #[bold]  cd ../'(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  (echo)  cd ../'$(echo)'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
+  HOSTNAME  USER   BRANCH  …  shell  3rd  «Unicode!»  cd ..
+  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  bindkey -v ; set_theme default
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd   COMMND   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  echo abc
+abc
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  false
+ INSERT   HOSTNAME  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
+ INSERT  USER  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
+ INSERT  …  tmp  shell  3rd  select abc in def ghi jkl
+ select                          do
+ select                           echo $abc
+ select                           break
+ select                          done
+1) def 2) ghi 3) jkl
+                 Select variant  1
+def
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  cd .
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_LEFT"
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+ foo  
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above "$ABOVE_FULL"
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  export DISPLAYED_ENV_VAR=foo
+                                                                                                                                                                                                                                                                                                      foo 
+ INSERT  …  tmp  shell  3rd  unset DISPLAYED_ENV_VAR
+                                                                                                                                                                                                                                                                                                           
+ INSERT  …  tmp  shell  3rd  set_theme_option default.segments.above
+ INSERT  …  tmp  shell  3rd  hash -d foo=$PWD:h ; cd .
+ INSERT  ~foo  3rd  set_theme_option default.dividers.left.hard \$ABC
+ INSERT $ABC~foo  3rd $ABCtrue
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=""
+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')