diff --git a/docs/source/installation.rst b/docs/source/installation.rst index eb40ed02..8dc42360 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -54,7 +54,7 @@ Generic requirements Pip installation ================ -Due to a naming conflict with an unrelated project powerline is available on +Due to a naming conflict with an unrelated project, powerline is available on PyPI under the ``powerline-status`` name: .. code-block:: sh @@ -66,7 +66,7 @@ development version .. code-block:: sh - pip install --user git+git://github.com/powerline/powerline + pip install --user git+https://github.com/powerline/powerline may be used. If powerline was already checked out into some directory @@ -85,10 +85,9 @@ will have to be done (:file:`~/.local/bin` should be replaced with some path present in ``$PATH``). .. note:: - If ISP blocks git protocol for some reason github also provides ``ssh`` - (``git+ssh://git@github.com/powerline/powerline``) and ``https`` - (``git+https://github.com/powerline/powerline``) protocols. ``git`` protocol - should be the fastest, but least secure one though. + We can use either ``https``(``git+ssh://git@github.com/powerline/powerline``) + or ``https``(``git+https://github.com/powerline/powerline``) protocols. + ``git`` protocol is deprecated by Github. Fonts installation ================== diff --git a/docs/source/installation/linux.rst b/docs/source/installation/linux.rst index 95b6636e..e134707c 100644 --- a/docs/source/installation/linux.rst +++ b/docs/source/installation/linux.rst @@ -29,7 +29,7 @@ should be followed: .. code-block:: sh - pip install --user git+git://github.com/powerline/powerline + pip install --user git+https://github.com/powerline/powerline will get the latest development version. diff --git a/docs/source/installation/osx.rst b/docs/source/installation/osx.rst index e520348d..1d6fafc3 100644 --- a/docs/source/installation/osx.rst +++ b/docs/source/installation/osx.rst @@ -16,9 +16,13 @@ Python package brew install python .. note:: - In case :file:`powerline.sh` as a client ``socat`` and ``coreutils`` need - to be installed. ``coreutils`` may be installed using ``brew install - coreutils``. + There are three variants of the powerline client. The fastest is + written in C and will be compiled if the compiler and libraries are + detected during installation. The second fastest option is + :file:`powerline.sh` which requires ``socat`` and ``coreutils``. + ``coreutils`` may be installed using ``brew install + coreutils``. If neither of these are viable, then Powerline will + utilize a fallback client written in Python. 2. Install Powerline using one of the following commands: @@ -30,7 +34,7 @@ Python package .. code-block:: sh - pip install --user git+git://github.com/powerline/powerline + pip install --user git+https://github.com/powerline/powerline will get latest development version. diff --git a/docs/source/troubleshooting.rst b/docs/source/troubleshooting.rst index 30f5d4cf..2b917474 100644 --- a/docs/source/troubleshooting.rst +++ b/docs/source/troubleshooting.rst @@ -193,8 +193,12 @@ I am suffering bad lags before displaying shell prompt To get rid of these lags there currently are two options: * Run ``powerline-daemon``. Powerline does not automatically start it for you. + See installation instructions for more details. * Compile and install ``libzpython`` module that lives in https://bitbucket.org/ZyX_I/zpython. This variant is zsh-specific. +* If you are a python package manager, be sure to set ``POWERLINE_COMMAND`` + to your Powerline command. See installation instructions for details. + Prompt is spoiled after completing files in ksh ----------------------------------------------- diff --git a/docs/source/usage/shell-prompts.rst b/docs/source/usage/shell-prompts.rst index 1ddb0a14..17774da6 100644 --- a/docs/source/usage/shell-prompts.rst +++ b/docs/source/usage/shell-prompts.rst @@ -5,8 +5,9 @@ Shell prompts ************* .. note:: - Powerline daemon is not run automatically by any of my bindings. It is - advised to add + Powerline can operate without a background daemon, but the shell performance + can be very slow. The Powerline daemon improves this performance + significantly, but must be started separately. It is advised to add .. code-block:: bash @@ -142,3 +143,19 @@ following in ``~/.profile``: .. warning:: Busybox has two shells: ``ash`` and ``hush``. Second is known to segfault in busybox 1.22.1 when using :file:`powerline.sh` script. + +Python Virtual Environments (conda, pyenv) +========================================== + +If your system uses virtual environments to manage different Python versions, +such as pyenv or anaconda, these tools will add a performance delay to every +shell prompt. This delay can be bypassed by explicitly specifying your command +path. + + .. code-block:: bash + + export POWERLINE_COMMAND={path_to_powerline} + +where ``{path_to_powerline}`` is the full filepath for powerline. If you +installed Powerline within an environment, you can find this path for pyenv +with ``pyenv which powerline`` or for conda with ``which powerline``. diff --git a/powerline/bindings/ipython/since_7.py b/powerline/bindings/ipython/since_7.py index a555d64a..4d35b68b 100644 --- a/powerline/bindings/ipython/since_7.py +++ b/powerline/bindings/ipython/since_7.py @@ -1,14 +1,13 @@ # vim:fileencoding=utf-8:noet -from __future__ import (unicode_literals, division, absolute_import, print_function) - from weakref import ref +from atexit import register as atexit from IPython.terminal.prompts import Prompts from pygments.token import Token # NOQA from powerline.ipython import IPythonPowerline from powerline.renderers.ipython.since_7 import PowerlinePromptStyle -from powerline.bindings.ipython.post_0_11 import PowerlineMagics, ShutdownHook +from powerline.bindings.ipython.post_0_11 import PowerlineMagics class ConfigurableIPythonPowerline(IPythonPowerline): @@ -20,7 +19,7 @@ class ConfigurableIPythonPowerline(IPythonPowerline): super(ConfigurableIPythonPowerline, self).init( renderer_module='.since_7') - def do_setup(self, ip, prompts, shutdown_hook): + def do_setup(self, ip, prompts): prompts.powerline = self msfn_missing = () @@ -50,18 +49,16 @@ class ConfigurableIPythonPowerline(IPythonPowerline): magics = PowerlineMagics(ip, self) ip.register_magics(magics) - if shutdown_hook: - shutdown_hook.powerline = ref(self) + atexit(self.shutdown) class PowerlinePrompts(Prompts): '''Class that returns powerline prompts ''' def __init__(self, shell): - shutdown_hook = ShutdownHook(shell) powerline = ConfigurableIPythonPowerline(shell) self.shell = shell - powerline.do_setup(shell, self, shutdown_hook) + powerline.do_setup(shell, self) self.last_output_count = None self.last_output = {} diff --git a/powerline/bindings/zsh/powerline.zsh b/powerline/bindings/zsh/powerline.zsh index 5d7cb28a..ff46cb06 100644 --- a/powerline/bindings/zsh/powerline.zsh +++ b/powerline/bindings/zsh/powerline.zsh @@ -197,7 +197,7 @@ _powerline_add_widget() { } if test -z "${POWERLINE_CONFIG_COMMAND}" ; then - if which powerline-config >/dev/null ; then + if which powerline-config >/dev/null 2>/dev/null ; then typeset -g POWERLINE_CONFIG_COMMAND=powerline-config else typeset -g POWERLINE_CONFIG_COMMAND="${_POWERLINE_SOURCED:h:h:h:h}/scripts/powerline-config" diff --git a/powerline/config_files/colorschemes/default.json b/powerline/config_files/colorschemes/default.json index 7e271ef3..767e72e4 100644 --- a/powerline/config_files/colorschemes/default.json +++ b/powerline/config_files/colorschemes/default.json @@ -51,6 +51,7 @@ "cwd:current_folder": "information:regular", "cwd:divider": { "fg": "gray7", "bg": "gray4", "attrs": [] }, "virtualenv": { "fg": "white", "bg": "darkcyan", "attrs": [] }, - "attached_clients": { "fg": "gray8", "bg": "gray0", "attrs": [] } + "attached_clients": { "fg": "gray8", "bg": "gray0", "attrs": [] }, + "workspace": "information:regular" } } diff --git a/powerline/config_files/themes/wm/default.json b/powerline/config_files/themes/wm/default.json index 579080d1..3d468b49 100644 --- a/powerline/config_files/themes/wm/default.json +++ b/powerline/config_files/themes/wm/default.json @@ -1,10 +1,6 @@ { "segments": { "right": [ - { - "function": "powerline.segments.common.wthr.weather", - "priority": 50 - }, { "function": "powerline.segments.common.time.date" }, @@ -15,14 +11,6 @@ "format": "%H:%M", "istime": true } - }, - { - "function": "powerline.segments.common.mail.email_imap_alert", - "priority": 10, - "args": { - "username": "", - "password": "" - } } ] } diff --git a/powerline/lint/markedjson/constructor.py b/powerline/lint/markedjson/constructor.py index 2a95d840..372d84b3 100644 --- a/powerline/lint/markedjson/constructor.py +++ b/powerline/lint/markedjson/constructor.py @@ -122,7 +122,7 @@ class BaseConstructor: mapping = {} for key_node, value_node in node.value: key = self.construct_object(key_node, deep=deep) - if not isinstance(key, collections.Hashable): + if not isinstance(key, collections.abc.Hashable): self.echoerr( 'While constructing a mapping', node.start_mark, 'found unhashable key', key_node.start_mark diff --git a/powerline/listers/i3wm.py b/powerline/listers/i3wm.py index b1984e94..9224d160 100644 --- a/powerline/listers/i3wm.py +++ b/powerline/listers/i3wm.py @@ -26,20 +26,20 @@ def output_lister(pl, segment_info): def workspace_lister(pl, segment_info, only_show=None, output=None): '''List all workspaces in segment_info format - Sets the segment info values of ``workspace`` and ``output`` to the name of + Sets the segment info values of ``workspace`` and ``output`` to the name of the i3 workspace and the ``xrandr`` output respectively and the keys ``"visible"``, ``"urgent"`` and ``"focused"`` to a boolean indicating these states. :param list only_show: - Specifies which workspaces to list. Valid entries are ``"visible"``, - ``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces + Specifies which workspaces to list. Valid entries are ``"visible"``, + ``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces are listed. :param str output: - May be set to the name of an X output. If specified, only workspaces - on that output are listed. Overrides automatic output detection by - the lemonbar renderer and bindings. Set to ``false`` to force + May be set to the name of an X output. If specified, only workspaces + on that output are listed. Overrides automatic output detection by + the lemonbar renderer and bindings. Set to ``false`` to force all workspaces to be shown. ''' diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index a6c0ee85..57ab3770 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -6,17 +6,18 @@ 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 @@ -28,58 +29,191 @@ def format_name(name, strip=False): 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): +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 + 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 + 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 + 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`` or ``w_focused``, ``workspace`` or ``w_urgent``. + + Highlight groups used: ``workspace`` or ``w_visible``, ``workspace:visible``, ``workspace`` or ``w_focused``, ``workspace:focused``, ``workspace`` or ``w_urgent``, ``workspace:urgent``, ``workspace`` or ``output``. ''' - output = output or segment_info.get('output') + conn = get_i3_connection() - return [ - { - 'contents': w.name[strip:], + 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 get_i3_connection().get_workspaces() - if ((not only_show or any(getattr(w, typ) for typ in only_show)) - and (not output or w.output == output)) - ] - + } 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): +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 + 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 + 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. - Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``. + :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 get_i3_connection().get_workspaces() + w for w in conn.get_workspaces() if w.name == workspace )) except StopIteration: @@ -89,16 +223,20 @@ def workspace(pl, segment_info, workspace=None, strip=False): else: try: w = next(( - w for w in get_i3_connection().get_workspaces() + w for w in conn.get_workspaces() if w.focused )) except StopIteration: return None return [{ - 'contents': format_name(w.name, strip=strip), + '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 @@ -139,7 +277,7 @@ 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 + 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``. @@ -153,3 +291,19 @@ def scratchpad(pl, icons=SCRATCHPAD_ICONS): 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/version.py b/powerline/version.py index 0f46c6e8..f380c80b 100644 --- a/powerline/version.py +++ b/powerline/version.py @@ -1,10 +1,7 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) -import subprocess -from traceback import print_exc - -__version__ = "2.8.2" +__version__ = "2.8.3" def get_version(): return __version__ diff --git a/tests/modules/__init__.py b/tests/modules/__init__.py index 12aae20d..5d671b6c 100644 --- a/tests/modules/__init__.py +++ b/tests/modules/__init__.py @@ -33,6 +33,7 @@ class PowerlineDummyTest(object): class PowerlineTestSuite(object): def __init__(self, name): self.name = name + self.suite = '' def __enter__(self): self.saved_current_suite = os.environ['POWERLINE_CURRENT_SUITE'] diff --git a/tests/test_python/test_segments.py b/tests/test_python/test_segments.py index 1ee8cd0a..c7a9870a 100644 --- a/tests/test_python/test_segments.py +++ b/tests/test_python/test_segments.py @@ -1001,81 +1001,158 @@ class TestWthr(TestCommon): class TestI3WM(TestCase): - @staticmethod - def get_workspaces(): - return iter([ - Args(name='1: w1', output='LVDS1', focused = False, urgent = False, visible = False), - Args(name='2: w2', output='LVDS1', focused = False, urgent = False, visible = True), - Args(name='3: w3', output='HDMI1', focused = False, urgent = True, visible = True), - Args(name='4: w4', output='DVI01', focused = True, urgent = True, visible = True), - ]) - def test_workspaces(self): + class Conn(object): + def get_tree(self): + return self + + def descendents(self): + nodes_unfocused = [Args(focused = False)] + nodes_focused = [Args(focused = True)] + + workspace_scratch = lambda: Args(name='__i3_scratch') + workspace_noscratch = lambda: Args(name='2: w2') + return [ + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused), + Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused), + ] + + def workspaces(self): + return iter([ + Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []), + Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []), + Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []), + Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: []) + ]) + + def get_workspaces(self): + return iter([ + Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []), + Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []), + Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []), + Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: []) + ]) + + def get_outputs(self): + return iter([ + Args(name='LVDS1', active=True), + Args(name='HDMI1', active=True), + Args(name='DVI01', active=True), + Args(name='HDMI2', active=False), + ]) + pl = Pl() - with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): + with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()): segment_info = {} self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [ {'contents': '1: w1', 'highlight_groups': ['workspace']}, - {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, - {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, - {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': '2: w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=None), [ {'contents': '1: w1', 'highlight_groups': ['workspace']}, - {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, - {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, - {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': '2: w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent']), [ - {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, - {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible']), [ - {'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']}, - {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, - {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': '2: w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [ - {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, - {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, - {'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': 'w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']}, + {'contents': 'w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, + {'contents': 'w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent'], output='DVI01'), [ - {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [ - {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3, output='LVDS1'), [ - {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, + {'contents': 'w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']}, ]) segment_info['output'] = 'LVDS1' self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [ - {'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [ - {'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']}, + {'contents': 'w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']}, ]) def test_workspace(self): + class Conn(object): + def get_tree(self): + return self + + def descendents(self): + nodes_unfocused = [Args(focused = False)] + nodes_focused = [Args(focused = True)] + + workspace_scratch = lambda: Args(name='__i3_scratch') + workspace_noscratch = lambda: Args(name='2: w2') + return [ + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused), + Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused), + Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused), + Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused), + ] + + def workspaces(self): + return iter([ + Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []), + Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []), + Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []), + Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: []) + ]) + + def get_workspaces(self): + return iter([ + Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []), + Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []), + Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []), + Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: []) + ]) + + def get_outputs(self): + return iter([ + Args(name='LVDS1', active=True), + Args(name='HDMI1', active=True), + Args(name='DVI01', active=True), + Args(name='HDMI2', active=False), + ]) + pl = Pl() - with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)): + with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()): segment_info = {} self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='1: w1'), [ {'contents': '1: w1', 'highlight_groups': ['workspace']}, ]) self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='3: w3', strip=True), [ - {'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']}, + {'contents': 'w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='9: w9'), None) self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info), [ - {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) - segment_info['workspace'] = next(self.get_workspaces()) + segment_info['workspace'] = next(Conn().get_workspaces()) self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='4: w4'), [ - {'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']}, + {'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']}, ]) self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [ {'contents': 'w1', 'highlight_groups': ['workspace']},