summaryrefslogtreecommitdiffstats
path: root/powerline/segments
diff options
context:
space:
mode:
Diffstat (limited to 'powerline/segments')
-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.py201
-rw-r--r--powerline/segments/common/mail.py78
-rw-r--r--powerline/segments/common/net.py315
-rw-r--r--powerline/segments/common/players.py636
-rw-r--r--powerline/segments/common/sys.py184
-rw-r--r--powerline/segments/common/time.py123
-rw-r--r--powerline/segments/common/vcs.py89
-rw-r--r--powerline/segments/common/wthr.py234
-rw-r--r--powerline/segments/i3wm.py309
-rw-r--r--powerline/segments/ipython.py9
-rw-r--r--powerline/segments/pdb.py61
-rw-r--r--powerline/segments/shell.py196
-rw-r--r--powerline/segments/tmux.py22
-rw-r--r--powerline/segments/vim/__init__.py805
-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/coc.py51
-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
25 files changed, 3982 insertions, 0 deletions
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..bbfe3e2
--- /dev/null
+++ b/powerline/segments/common/env.py
@@ -0,0 +1,201 @@
+# 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, ignored_names=("venv", ".venv")):
+ '''Return the name of the current Python or conda virtualenv.
+ :param list ignored_names:
+ Names of venvs to ignore. Will then get the name of the venv by ascending to the parent directory
+ :param bool ignore_venv:
+ Whether to ignore virtual environments. Default is False.
+ :param bool ignore_conda:
+ Whether to ignore conda environments. Default is False.
+ '''
+ if not ignore_venv:
+ for candidate in reversed(segment_info['environ'].get('VIRTUAL_ENV', '').split("/")):
+ if candidate and candidate not in ignored_names:
+ return candidate
+ if not ignore_conda:
+ for candidate in reversed(segment_info['environ'].get('CONDA_DEFAULT_ENV', '').split("/")):
+ if candidate and candidate not in ignored_names:
+ return candidate
+ return 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..f43db0c
--- /dev/null
+++ b/powerline/segments/common/players.py
@@ -0,0 +1,636 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import re
+
+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'''
+ if isinstance(seconds, str):
+ seconds = seconds.replace(",",".")
+ 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 accessible 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',
+ '-h', host,
+ '-p', str(port)
+ ], strip=False)
+ album = run_cmd(pl, [
+ 'mpc', 'current',
+ '-f', '%album%',
+ '-h', host,
+ '-p', str(port)
+ ])
+ if not now_playing or now_playing.count("\n") != 3:
+ return
+ now_playing = re.match(
+ r"(.*) - (.*)\n\[([a-z]+)\] +[#0-9\/]+ +([0-9\:]+)\/([0-9\:]+)",
+ now_playing
+ )
+ return {
+ 'state': _convert_state(now_playing[3]),
+ 'album': album,
+ 'artist': now_playing[1],
+ 'title': now_playing[2],
+ 'elapsed': now_playing[4],
+ 'total': now_playing[5]
+ }
+ 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 accessible 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=None,
+ iface_prop='org.freedesktop.DBus.Properties',
+ iface_player='org.mpris.MediaPlayer2.Player',
+ player_path='/org/mpris/MediaPlayer2',
+ player_name='player'):
+ bus = dbus.SessionBus()
+
+ if bus_name is None:
+ for service in bus.list_names():
+ if re.match('org.mpris.MediaPlayer2.', service):
+ bus_name = service
+ break
+
+ 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])
+
+ length = info.get('mpris:length')
+ # avoid parsing `None` length values, that would
+ # raise an error otherwise
+ parsed_length = length and _convert_seconds(length / 1e6)
+
+ return {
+ 'state': state,
+ 'album': album,
+ 'artist': artist,
+ 'title': title,
+ 'elapsed': elapsed,
+ 'total': parsed_length,
+ }
+
+
+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..29a2459
--- /dev/null
+++ b/powerline/segments/common/sys.py
@@ -0,0 +1,184 @@
+# 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):
+ 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:02d}m',
+ seconds_format=' {seconds:02d}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_format else None,
+ hours_format.format(hours=hours) if hours_format else None,
+ minutes_format.format(minutes=minutes) if minutes_format else None,
+ seconds_format.format(seconds=seconds) if seconds_format else None,
+ ]))
+ first_non_zero = next((i for i, x in enumerate([days, hours, minutes, seconds]) if x != 0))
+ time_formatted = time_formatted[first_non_zero:first_non_zero + 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..be727c9
--- /dev/null
+++ b/powerline/segments/common/time.py
@@ -0,0 +1,123 @@
+# 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, timezone=None):
+ '''Return the current date.
+
+ :param str format:
+ strftime-style date format string
+ :param bool istime:
+ If true then segment uses ``time`` highlight group.
+ :param string timezone:
+ Specify a timezone to use as ``+HHMM`` or ``-HHMM``.
+ (Defaults to system defaults.)
+
+ Divider highlight group used: ``time:divider``.
+
+ Highlight groups used: ``time`` or ``date``.
+ '''
+
+ try:
+ tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None
+ except ValueError:
+ tz = None
+
+ nw = datetime.now(tz)
+
+ try:
+ contents = nw.strftime(format)
+ except UnicodeEncodeError:
+ contents = nw.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, format='{minute_str} {hour_str}', unicode_text=False, timezone=None, hour_str=['twelve', 'one', 'two', 'three', 'four',
+ 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven'], minute_str = {
+ '0': 'o\'clock', '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',
+ }):
+
+ '''Display the current time as fuzzy time, e.g. "quarter past six".
+
+ :param string format:
+ Format used to display the fuzzy time. (Ignored when a special time
+ is displayed.)
+ :param bool unicode_text:
+ If true then hyphenminuses (regular ASCII ``-``) and single quotes are
+ replaced with unicode dashes and apostrophes.
+ :param string timezone:
+ Specify a timezone to use as ``+HHMM`` or ``-HHMM``.
+ (Defaults to system defaults.)
+ :param string list hour_str:
+ Strings to be used to display the hour, starting with midnight.
+ (This list may contain 12 or 24 entries.)
+ :param dict minute_str:
+ Dictionary mapping minutes to strings to be used to display them.
+ :param dict special_case_str:
+ Special strings for special times.
+
+ Highlight groups used: ``fuzzy_time``.
+ '''
+
+ try:
+ tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None
+ except ValueError:
+ tz = None
+
+ now = datetime.now(tz)
+
+ try:
+ # We don't want to enforce a special type of spaces/ alignment in the input
+ from ast import literal_eval
+ special_case_str = {literal_eval(x):special_case_str[x] for x in special_case_str}
+ result = special_case_str[(now.hour, now.minute)]
+ if unicode_text:
+ result = result.translate(UNICODE_TEXT_TRANSLATION)
+ return result
+ except KeyError:
+ pass
+
+ hour = now.hour
+ if now.minute >= 30:
+ hour = hour + 1
+ hour = hour % len(hour_str)
+
+ min_dis = 100
+ min_pos = 0
+
+ for mn in minute_str:
+ mn = int(mn)
+ if now.minute >= mn and now.minute - mn < min_dis:
+ min_dis = now.minute - mn
+ min_pos = mn
+ elif now.minute < mn and mn - now.minute < min_dis:
+ min_dis = mn - now.minute
+ min_pos = mn
+ result = format.format(minute_str=minute_str[str(min_pos)], hour_str=hour_str[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..2c54cca
--- /dev/null
+++ b/powerline/segments/common/wthr.py
@@ -0,0 +1,234 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import json
+from collections import namedtuple
+
+from powerline.lib.url import urllib_read, urllib_urlencode
+from powerline.lib.threaded import KwThreadedSegment
+from powerline.segments import with_docstring
+
+
+_WeatherKey = namedtuple('Key', 'location_query weather_api_key')
+
+
+# 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
+# https://openweathermap.org/weather-conditions
+weather_conditions_codes = {
+ 200: ('stormy',),
+ 201: ('stormy',),
+ 202: ('stormy',),
+ 210: ('stormy',),
+ 211: ('stormy',),
+ 212: ('stormy',),
+ 221: ('stormy',),
+ 230: ('stormy',),
+ 231: ('stormy',),
+ 232: ('stormy',),
+ 300: ('rainy',),
+ 301: ('rainy',),
+ 302: ('rainy',),
+ 310: ('rainy',),
+ 311: ('rainy',),
+ 312: ('rainy',),
+ 313: ('rainy',),
+ 314: ('rainy',),
+ 321: ('rainy',),
+ 500: ('rainy',),
+ 501: ('rainy',),
+ 502: ('rainy',),
+ 503: ('rainy',),
+ 504: ('rainy',),
+ 511: ('snowy',),
+ 520: ('rainy',),
+ 521: ('rainy',),
+ 522: ('rainy',),
+ 531: ('rainy',),
+ 600: ('snowy',),
+ 601: ('snowy',),
+ 602: ('snowy',),
+ 611: ('snowy',),
+ 612: ('snowy',),
+ 613: ('snowy',),
+ 615: ('snowy',),
+ 616: ('snowy',),
+ 620: ('snowy',),
+ 621: ('snowy',),
+ 622: ('snowy',),
+ 701: ('foggy',),
+ 711: ('foggy',),
+ 721: ('foggy',),
+ 731: ('foggy',),
+ 741: ('foggy',),
+ 751: ('foggy',),
+ 761: ('foggy',),
+ 762: ('foggy',),
+ 771: ('foggy',),
+ 781: ('foggy',),
+ 800: ('sunny',),
+ 801: ('cloudy',),
+ 802: ('cloudy',),
+ 803: ('cloudy',),
+ 804: ('cloudy',),
+}
+
+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 - 273.15,
+ 'F': lambda temp: (temp * 9 / 5) - 459.67,
+ 'K': lambda temp: temp,
+}
+
+# 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 = {}
+ weather_api_key = "fbc9549d91a5e4b26c15be0dbdac3460"
+
+ @staticmethod
+ def key(location_query=None, **kwargs):
+ try:
+ weather_api_key = kwargs["weather_api_key"]
+ except KeyError:
+ weather_api_key = WeatherSegment.weather_api_key
+ return _WeatherKey(location_query, weather_api_key)
+
+ def get_request_url(self, weather_key):
+ try:
+ return self.location_urls[weather_key]
+ except KeyError:
+ query_data = {
+ "appid": weather_key.weather_api_key
+ }
+ location_query = weather_key.location_query
+ if location_query is None:
+ location_data = json.loads(urllib_read('https://freegeoip.app/json/'))
+ query_data["lat"] = location_data["latitude"]
+ query_data["lon"] = location_data["longitude"]
+ else:
+ query_data["q"] = location_query
+ self.location_urls[location_query] = url = (
+ "https://api.openweathermap.org/data/2.5/weather?" +
+ urllib_urlencode(query_data))
+ return url
+
+ def compute_state(self, weather_key):
+ url = self.get_request_url(weather_key)
+ 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['weather'][0]
+ condition_code = int(condition['id'])
+ temp = float(response['main']['temp'])
+ except (KeyError, ValueError):
+ self.exception('OpenWeatherMap returned malformed or unexpected response: {0}', repr(raw_response))
+ return None
+
+ try:
+ icon_names = weather_conditions_codes[condition_code]
+ except IndexError:
+ 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 converted_temp <= temp_coldest:
+ gradient_level = 0
+ elif converted_temp >= temp_hottest:
+ gradient_level = 100
+ else:
+ gradient_level = (converted_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 OpenWeatherMaps.
+
+Uses GeoIP lookup from https://freegeoip.app 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 OpenWeatherMap.
+''')
diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py
new file mode 100644
index 0000000..57ab377
--- /dev/null
+++ b/powerline/segments/i3wm.py
@@ -0,0 +1,309 @@
+# 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('workspace:focused')
+ group.append('w_focused')
+ if w.urgent:
+ group.append('workspace:urgent')
+ group.append('w_urgent')
+ if w.visible:
+ group.append('workspace: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
+
+
+def is_empty_workspace(workspace, containers):
+ if workspace.focused or workspace.visible:
+ return False
+ wins = [win for win in containers[workspace.name].leaves()]
+ return False if len(wins) > 0 else True
+
+WS_ICONS = {"multiple": "M"}
+
+def get_icon(workspace, separator, icons, show_multiple_icons, ws_containers):
+ icons_tmp = WS_ICONS
+ icons_tmp.update(icons)
+ icons = icons_tmp
+
+ wins = [win for win in ws_containers[workspace.name].leaves() \
+ if win.parent.scratchpad_state == 'none']
+ if len(wins) == 0:
+ return ''
+
+ result = ''
+ cnt = 0
+ for key in icons:
+ if not icons[key] or len(icons[key]) < 1:
+ continue
+ if any(key in win.window_class for win in wins if win.window_class):
+ result += (separator if cnt > 0 else '') + icons[key]
+ cnt += 1
+ if not show_multiple_icons and cnt > 1:
+ if 'multiple' in icons:
+ return icons['multiple']
+ else:
+ return ''
+ return result
+
+@requires_segment_info
+def workspaces(pl, segment_info, only_show=None, output=None, strip=0, format='{name}',
+ icons=WS_ICONS, sort_workspaces=False, show_output=False, priority_workspaces=[],
+ hide_empty_workspaces=False):
+ '''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.
+ Use "__all__" to show workspaces on all outputs.
+ :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.
+ :param str format:
+ Specifies the format used to display workspaces; defaults to ``{name}``.
+ Valid fields are: ``name`` (workspace name), ``number`` (workspace number
+ if present), `stipped_name`` (workspace name stripped of leading number),
+ ``icon`` (if available, icon for application running in the workspace,
+ uses the ``multiple`` icon instead of multiple different icons), ``multi_icon``
+ (similar to ``icon``, but does not use ``multiple``, instead joins all icons
+ with a single space)
+ :param dict icons:
+ A dictionary mapping a substring of window classes to strings to be used as an
+ icon for that window class.
+ Further, there is a ``multiple`` icon for workspaces containing more than one
+ window.
+ :param bool sort_workspaces:
+ Sort the workspaces displayed by their name according to the natural ordering.
+ :param bool show_output:
+ Shows the name of the output if more than one output is connected.
+ :param list priority_workspaces:
+ A list of workspace names to be sorted before any other workspaces in the given
+ order.
+ :param bool hide_empty_workspaces:
+ Hides all workspaces without any open window.
+ Also hides non-focussed workspaces containing only an open scratchpad.
+
+
+ Highlight groups used: ``workspace`` or ``w_visible``, ``workspace:visible``, ``workspace`` or ``w_focused``, ``workspace:focused``, ``workspace`` or ``w_urgent``, ``workspace:urgent``, ``workspace`` or ``output``.
+ '''
+ conn = get_i3_connection()
+
+ if not output == "__all__":
+ output = output or segment_info.get('output')
+ else:
+ output = None
+
+ if output:
+ output = [output]
+ else:
+ output = [o.name for o in conn.get_outputs() if o.active]
+
+
+ def sort_ws(ws):
+ if sort_workspaces:
+ def natural_key(ws):
+ str = ws.name
+ return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', str)]
+ ws = sorted(ws, key=natural_key)
+ result = []
+ for n in priority_workspaces:
+ result += [w for w in ws if w.name == n]
+ return result + [w for w in ws if not w.name in priority_workspaces]
+
+ ws_containers = {w_con.name : w_con for w_con in conn.get_tree().workspaces()}
+
+ if len(output) <= 1:
+ res = []
+ if show_output:
+ res += [{
+ 'contents': output[0],
+ 'highlight_groups': ['output']
+ }]
+ res += [{
+ 'contents': format.format(name = w.name[min(len(w.name), strip):],
+ stripped_name = format_name(w.name, strip=True),
+ number = w.num,
+ icon = get_icon(w, '', icons, False, ws_containers),
+ multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
+ 'highlight_groups': workspace_groups(w)
+ } for w in sort_ws(conn.get_workspaces()) \
+ if (not only_show or any(getattr(w, tp) for tp in only_show)) \
+ if w.output == output[0] \
+ if not (hide_empty_workspaces and is_empty_workspace(w, ws_containers))]
+ return res
+ else:
+ res = []
+ for n in output:
+ if show_output:
+ res += [{
+ 'contents': n,
+ 'highlight_groups': ['output']
+ }]
+ res += [{
+ 'contents': format.format(name = w.name[min(len(w.name), strip):],
+ stripped_name = format_name(w.name, strip=True),
+ number = w.num,
+ icon = get_icon(w, '', icons, False, ws_containers),
+ multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
+ 'highlight_groups': workspace_groups(w)
+ } for w in sort_ws(conn.get_workspaces()) \
+ if (not only_show or any(getattr(w, tp) for tp in only_show)) \
+ if w.output == n \
+ if not (hide_empty_workspaces and is_empty_workspace(w, ws_containers))]
+ return res
+
+@requires_segment_info
+def workspace(pl, segment_info, workspace=None, strip=False, format=None, icons=WS_ICONS):
+ '''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.
+ Deprecated: Use {name} or {stripped_name} of format instead.
+
+ :param str format:
+ Specifies the format used to display workspaces; defaults to ``{name}``.
+ Valid fields are: ``name`` (workspace name), ``number`` (workspace number
+ if present), `stipped_name`` (workspace name stripped of leading number),
+ ``icon`` (if available, icon for application running in the workspace,
+ uses the ``multiple`` icon instead of multiple different icons), ``multi_icon``
+ (similar to ``icon``, but does not use ``multiple``, instead joins all icons
+ with a single space)
+
+ :param dict icons:
+ A dictionary mapping a substring of window classes to strings to be used as an
+ icon for that window class.
+ Further, there is a ``multiple`` icon for workspaces containing more than one
+ window.
+
+ Highlight groups used: ``workspace`` or ``w_visible``, ``workspace:visible``, ``workspace`` or ``w_focused``, ``workspace:focused``, ``workspace`` or ``w_urgent``, ``workspace:urgent``, ``workspace``.
+ '''
+ if format == None:
+ format = '{stripped_name}' if strip else '{name}'
+
+ conn = get_i3_connection()
+ ws_containers = {w_con.name : w_con for w_con in conn.get_tree().workspaces()}
+
+ if workspace:
+ try:
+ w = next((
+ w for w in conn.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 conn.get_workspaces()
+ if w.focused
+ ))
+ except StopIteration:
+ return None
+
+ return [{
+ 'contents': format.format(name = w.name,
+ stripped_name = format_name(w.name, strip=True),
+ number = w.num,
+ icon = get_icon(w, '', icons, False, ws_containers),
+ multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
+ '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().descendants()
+ if w.scratchpad_state != 'none'
+ ]
+
+def active_window(pl, cutoff=100):
+ '''Returns the title of the currently active window.
+
+ :param int cutoff:
+ Maximum title length. If the title is longer, the window_class is used instead.
+
+ Highlight groups used: ``active_window_title``.
+ '''
+
+ focused = get_i3_connection().get_tree().find_focused()
+ cont = focused.name
+ if len(cont) > cutoff:
+ cont = focused.window_class
+
+ return [{'contents': cont, 'highlight_groups': ['active_window_title']}] if focused.name != focused.workspace().name else []
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..66991c7
--- /dev/null
+++ b/powerline/segments/shell.py
@@ -0,0 +1,196 @@
+# 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)
+
+try:
+ import signal
+ exit_codes = dict((k, v) for v, k in reversed(sorted(signal.__dict__.items())) \
+ if v.startswith('SIG') and not v.startswith('SIG_'))
+except ImportError:
+ exit_codes = dict()
+
+@requires_segment_info
+def last_status(pl, segment_info, signal_names=True):
+ '''Return last exit code.
+
+ :param bool signal_names:
+ If True (default), translate signal numbers to human-readable names.
+
+ Highlight groups used: ``exit_fail``
+ '''
+ if not segment_info['args'].last_exit_code:
+ return None
+
+ try:
+ if signal_names and segment_info['args'].last_exit_code - 128 in exit_codes:
+ return [{'contents': exit_codes[segment_info['args'].last_exit_code - 128], 'highlight_groups': ['exit_fail']}]
+ except TypeError:
+ pass
+ return [{'contents': str(segment_info['args'].last_exit_code), 'highlight_groups': ['exit_fail']}]
+
+
+@requires_segment_info
+def last_pipe_status(pl, segment_info, signal_names=True):
+ '''Return last pipe status.
+
+ :param bool signal_names:
+ If True (default), translate signal numbers to human-readable names.
+
+ 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):
+ try:
+ return [{
+ 'contents': exit_codes[status - 128] if signal_names and \
+ status - 128 in exit_codes else str(status),
+ 'highlight_groups': ['exit_fail' if status else 'exit_success'],
+ 'draw_inner_divider': True
+ } for status in last_pipe_status]
+ except TypeError:
+ 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..d999d07
--- /dev/null
+++ b/powerline/segments/vim/__init__.py
@@ -0,0 +1,805 @@
+# 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_bom(pl, segment_info):
+ '''Return BOM of the current file
+
+ :return: Byte order mark or None if unknown or missing BOM
+
+ Divider highlight group used: ``background:divider``.
+ '''
+ return 'bom' if vim_getbufoption(segment_info, 'bomb') else 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 ignored
+
+ :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 the 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/coc.py b/powerline/segments/vim/plugin/coc.py
new file mode 100644
index 0000000..290faec
--- /dev/null
+++ b/powerline/segments/vim/plugin/coc.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
+from powerline.theme import requires_segment_info
+
+# coc_status's format: E1 W2
+def parse_coc_status(coc_status):
+ # type(coc_status) is tuple
+ errors_count = 0
+ warnings_count = 0
+ if len(coc_status) <= 0:
+ return errors_count, warnings_count
+ status_str = coc_status[0]
+ if len(status_str) <= 0:
+ return errors_count, warnings_count
+ status_list = status_str.split(' ')
+ for item in status_list:
+ if len(item) > 0 and item[0] == 'E':
+ errors_count = int(item[1:])
+ if len(item) > 0 and item[0] == 'W':
+ warnings_count = int(item[1:])
+ return errors_count, warnings_count
+
+@requires_segment_info
+def coc(segment_info, pl):
+ '''Show whether coc.nvim has found any errors or warnings
+
+ Highlight groups used: ``coc:warning`` or ``warning``, ``coc:error`` or ``error``.
+ '''
+ segments = []
+ if not vim_command_exists('CocCommand'):
+ return segments
+ coc_status = vim.eval('coc#status()'),
+ errors_count, warnings_count = parse_coc_status(coc_status)
+ if errors_count > 0:
+ segments.append({
+ 'contents': 'E:' + str(errors_count),
+ 'highlight_groups': ['coc:error', 'error'],
+ })
+ if warnings_count > 0:
+ segments.append({
+ 'contents': 'W:' + str(warnings_count),
+ 'highlight_groups': ['coc:warning', 'warning'],
+ })
+ return segments
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