From 7310d53adef5ba5829712b1ac06839be1b4852b9 Mon Sep 17 00:00:00 2001 From: Philip Wellnitz Date: Mon, 15 Mar 2021 03:45:17 +0900 Subject: [PATCH 01/17] i3 segments rework (#2159) rework i3wm segments --- .../config_files/colorschemes/default.json | 3 +- powerline/config_files/themes/wm/default.json | 12 - powerline/listers/i3wm.py | 12 +- powerline/segments/i3wm.py | 216 +++++++++++++++--- tests/modules/__init__.py | 1 + tests/test_python/test_segments.py | 145 +++++++++--- 6 files changed, 305 insertions(+), 84 deletions(-) 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/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/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']}, From 8f0d3f8bd227ba0329aed9a3ca51dc0228de6747 Mon Sep 17 00:00:00 2001 From: MrFishFinger <4768596+MrFishFinger@users.noreply.github.com> Date: Mon, 26 Jul 2021 09:01:39 +0200 Subject: [PATCH 02/17] doco - add a comma, to make sentence more readable (#2173) --- docs/source/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index eb40ed02..6be736d3 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 From 57aea1e400c08d76a7da84444ba530ef640426e1 Mon Sep 17 00:00:00 2001 From: Josh Gao Date: Fri, 20 Aug 2021 19:17:03 -0700 Subject: [PATCH 03/17] powerline.zsh: squelch which output. (#2176) If powerline-config isn't on $PATH, GNU which will output "no powerline-config in ($PATH)" to stderr, which we previously weren't silencing. --- powerline/bindings/zsh/powerline.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 82c1373ba0b424c57e8c12cb5f6f1a7ee3829c27 Mon Sep 17 00:00:00 2001 From: Darren <3759672+svdarren@users.noreply.github.com> Date: Fri, 20 Aug 2021 21:21:14 -0500 Subject: [PATCH 04/17] Added tips to installation docs (#2175) * Added virtual environment tips Documented the use of `POWERLINE_COMMAND` env variable for improved performance with virtual environments. * Clarify client versions Adding details from the GitHub issues to clarify the installation docs. * Added troubleshooting tip for Python environments --- docs/source/installation/osx.rst | 10 +++++++--- docs/source/troubleshooting.rst | 4 ++++ docs/source/usage/shell-prompts.rst | 21 +++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/source/installation/osx.rst b/docs/source/installation/osx.rst index e520348d..d625407b 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: 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``. From 9ce39e6333865f3460a047983d763ace797bcff8 Mon Sep 17 00:00:00 2001 From: Albert Paul Date: Mon, 21 Mar 2022 10:59:31 +0100 Subject: [PATCH 05/17] Remove git protocol (#2199) * Fix outdated Github url git:// is deprecated from Github starting from Jan. 11th, 2022. * Update git protocols --- docs/source/installation.rst | 9 ++++----- docs/source/installation/linux.rst | 2 +- docs/source/installation/osx.rst | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 6be736d3..8dc42360 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -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 d625407b..1d6fafc3 100644 --- a/docs/source/installation/osx.rst +++ b/docs/source/installation/osx.rst @@ -34,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. From 90af71c122da8db311dad1d60ab91ca73b76268e Mon Sep 17 00:00:00 2001 From: PHP Wellnitz Date: Sat, 9 Apr 2022 04:27:02 +0900 Subject: [PATCH 06/17] Fix #2200 --- powerline/lint/markedjson/constructor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 567e2c930b38b1c850d3e920ebc7c9eae9778a1d Mon Sep 17 00:00:00 2001 From: PHP Wellnitz Date: Thu, 19 May 2022 16:52:00 +0900 Subject: [PATCH 07/17] fix #2194 --- powerline/bindings/ipython/since_7.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 = {} From 3555dc97c94ab6978e23b7a48df964f64f8352c8 Mon Sep 17 00:00:00 2001 From: PHP Wellnitz Date: Thu, 19 May 2022 16:57:18 +0900 Subject: [PATCH 08/17] change get_version to adhere to PEP 440 --- powerline/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/version.py b/powerline/version.py index 8b5195f1..0cceed84 100644 --- a/powerline/version.py +++ b/powerline/version.py @@ -8,7 +8,7 @@ __version__ = "2.8.2" def get_version(): try: - return __version__ + '.dev9999+git.' + str(subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()) + return __version__ + 'b' + subprocess.check_output(['git', 'rev-list', '--count', __version__ + '..HEAD']).strip().decode() except Exception: print_exc() return __version__ From 8af6302c81b495328f7455fc2e4c21a80551a093 Mon Sep 17 00:00:00 2001 From: PHP Wellnitz Date: Thu, 19 May 2022 17:06:54 +0900 Subject: [PATCH 09/17] bump version to 2.8.3 --- powerline/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/version.py b/powerline/version.py index 0cceed84..8e131abd 100644 --- a/powerline/version.py +++ b/powerline/version.py @@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct import subprocess from traceback import print_exc -__version__ = "2.8.2" +__version__ = "2.8.3" def get_version(): try: From 50d73bfbc8903d526e9de2bf98521e00206c018a Mon Sep 17 00:00:00 2001 From: Christoph Erhardt Date: Thu, 23 Jun 2022 12:05:54 +0200 Subject: [PATCH 10/17] Ensure compatibility with Python 3.11 (#2212) * Replace deprecated `getargspec()` with `getfullargspec()` * Replace deprecated `formatargspec()` with custom implementation Fixes #2209. --- docs/source/powerline_autodoc.py | 6 ++---- powerline/lint/inspect.py | 34 +++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/docs/source/powerline_autodoc.py b/docs/source/powerline_autodoc.py index eba42edb..83996aea 100644 --- a/docs/source/powerline_autodoc.py +++ b/docs/source/powerline_autodoc.py @@ -3,11 +3,9 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct import os -from inspect import formatargspec - from sphinx.ext import autodoc -from powerline.lint.inspect import getconfigargspec +from powerline.lint.inspect import formatconfigargspec, getconfigargspec from powerline.segments import Segment from powerline.lib.unicode import unicode @@ -28,7 +26,7 @@ class ThreadedDocumenter(autodoc.FunctionDocumenter): def format_args(self): argspec = getconfigargspec(self.object) - return formatargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\') + return formatconfigargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\') class Repr(object): diff --git a/powerline/lint/inspect.py b/powerline/lint/inspect.py index 15bb6103..7ab720f8 100644 --- a/powerline/lint/inspect.py +++ b/powerline/lint/inspect.py @@ -1,7 +1,8 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) -from inspect import ArgSpec, getargspec +from inspect import FullArgSpec, getfullargspec +from itertools import zip_longest from powerline.segments import Segment @@ -33,7 +34,7 @@ def getconfigargspec(obj): requires_filesystem_watcher = hasattr(obj, 'powerline_requires_filesystem_watcher') for name, method in argspecobjs: - argspec = getargspec(method) + argspec = getfullargspec(method) omitted_args = get_omitted_args(name, method) largs = len(argspec.args) for i, arg in enumerate(reversed(argspec.args)): @@ -60,4 +61,31 @@ def getconfigargspec(obj): if arg not in args: args.insert(0, arg) - return ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults)) + return FullArgSpec(args=args, varargs=None, varkw=None, defaults=tuple(defaults), kwonlyargs=(), kwonlydefaults={}, annotations={}) + + +def formatconfigargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}, + formatvalue=lambda value: '=' + repr(value)): + '''Format an argument spec from the values returned by getconfigargspec. + + This is a specialized replacement for inspect.formatargspec, which has been + deprecated since Python 3.5 and was removed in Python 3.11. It supports + valid values for args, defaults and formatvalue; all other parameters are + expected to be either empty or None. + ''' + assert varargs is None + assert varkw is None + assert not kwonlyargs + assert not kwonlydefaults + assert not annotations + + specs = [] + if defaults: + firstdefault = len(args) - len(defaults) + for i, arg in enumerate(args): + spec = arg + if defaults and i >= firstdefault: + spec += formatvalue(defaults[i - firstdefault]) + specs.append(spec) + return '(' + ', '.join(specs) + ')' From 833f30e88efa51246dadc6daf21638ec61b6b912 Mon Sep 17 00:00:00 2001 From: JesseBot Date: Sun, 18 Dec 2022 09:38:49 +0100 Subject: [PATCH 11/17] Feature: add username_variable, password_variable, server_variable, port_variable to options to mail segment (#2225) * add username_variable and password_variable to options to mail segment * add server variable * make username/password optional for mail, and fix getting env var typo * add port variable for email * fix sleepy port_variable typo --- powerline/segments/common/mail.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/powerline/segments/common/mail.py b/powerline/segments/common/mail.py index 82024921..9c3d9032 100644 --- a/powerline/segments/common/mail.py +++ b/powerline/segments/common/mail.py @@ -1,6 +1,8 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) +import os + import re from imaplib import IMAP4_SSL_PORT, IMAP4_SSL, IMAP4 @@ -17,9 +19,19 @@ class EmailIMAPSegment(KwThreadedSegment): interval = 60 @staticmethod - def key(username, password, server='imap.gmail.com', port=IMAP4_SSL_PORT, folder='INBOX', use_ssl=None, **kwargs): + def key(username='', password='', server='imap.gmail.com', port=IMAP4_SSL_PORT, username_variable='', password_variable='', server_variable='', port_variable='', folder='INBOX', use_ssl=None, **kwargs): if use_ssl is None: use_ssl = (port == IMAP4_SSL_PORT) + # catch if user set custom mail credential env variables + if username_variable: + username = os.environ[username_variable] + if password_variable: + password = os.environ[password_variable] + if server_variable: + server = os.environ[server_variable] + if port_variable: + port = os.environ[port_variable] + return _IMAPKey(username, password, server, port, folder, use_ssl) def compute_state(self, key): @@ -64,6 +76,14 @@ email_imap_alert = with_docstring(EmailIMAPSegment(), e-mail server :param int port: e-mail server port +:param str username_variable: + name of environment variable to check for login username +:param str password_variable: + name of environment variable to check for login password +:param str server_variable: + name of environment variable to check for email server +:param str port_variable: + name of environment variable to check for email server port :param str folder: folder to check for e-mails :param int max_msgs: From 14cc0d2df8e85fd88aad0e1152c4b1f998f4a7b0 Mon Sep 17 00:00:00 2001 From: wanderboessenkool <31434459+wanderboessenkool@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:05:59 +0100 Subject: [PATCH 12/17] Change TMUJX_VAR_RE re.compile to use raw string (#2245) --- powerline/bindings/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/bindings/config.py b/powerline/bindings/config.py index 31006330..eb6693b9 100644 --- a/powerline/bindings/config.py +++ b/powerline/bindings/config.py @@ -176,7 +176,7 @@ def init_tmux_environment(pl, args, set_tmux_environment=set_tmux_environment): ' ' * powerline.renderer.strwidth(left_dividers['hard']))) -TMUX_VAR_RE = re.compile('\$(_POWERLINE_\w+)') +TMUX_VAR_RE = re.compile(r'\$(_POWERLINE_\w+)') def tmux_setup(pl, args): From c04a8dd5a1fd197c5abbb761dfa23207da16e9fc Mon Sep 17 00:00:00 2001 From: Robin Candau <53110319+Antiz96@users.noreply.github.com> Date: Tue, 27 Feb 2024 09:59:10 +0000 Subject: [PATCH 13/17] Remove imp module from tests to make them compatible with python 3.12 (#2251) --- tests/modules/lib/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/modules/lib/__init__.py b/tests/modules/lib/__init__.py index f6df9204..9e3dfef7 100644 --- a/tests/modules/lib/__init__.py +++ b/tests/modules/lib/__init__.py @@ -1,7 +1,6 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) -import imp import sys @@ -100,7 +99,7 @@ def replace_module(name, new=None, **kwargs): def new_module(name, **kwargs): - module = imp.new_module(name) + module = types.ModuleType(name) for k, v in kwargs.items(): setattr(module, k, v) return module From beca2d6e87f37f15117a700d624c35e712877a6b Mon Sep 17 00:00:00 2001 From: Philip Wellnitz Date: Wed, 28 Feb 2024 16:33:59 +0900 Subject: [PATCH 14/17] Update main.yaml Update supported python versions. --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ea7f7f95..41c52d48 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + python-version: [3.7, 3.8, 3.9, 3.10, 3.11, 3.12] steps: - name: Checkout code From a34abe325a9ca94e020dc2f717731d92466d6037 Mon Sep 17 00:00:00 2001 From: Philip Wellnitz Date: Wed, 28 Feb 2024 16:35:37 +0900 Subject: [PATCH 15/17] Update main.yaml 3.10 seems to be parsed as 3.1, remove it for now. --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 41c52d48..1d820a35 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, 3.10, 3.11, 3.12] + python-version: [3.7, 3.8, 3.9, 3.11, 3.12] steps: - name: Checkout code From 6b36ba8ae9cf0c3419122e1bdf7d93969b803faa Mon Sep 17 00:00:00 2001 From: "Philip Wellnitz [10]" Date: Sun, 12 May 2024 04:45:27 +0900 Subject: [PATCH 16/17] fix #2257 --- powerline/segments/common/time.py | 23 +++++++++++------------ tests/test_python/test_segments.py | 17 ++++++++--------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/powerline/segments/common/time.py b/powerline/segments/common/time.py index be727c9e..b7aaa0c9 100644 --- a/powerline/segments/common/time.py +++ b/powerline/segments/common/time.py @@ -1,7 +1,5 @@ # vim:fileencoding=utf-8:noet -from __future__ import (unicode_literals, division, absolute_import, print_function) - -from datetime import datetime +from datetime import datetime, timezone def date(pl, format='%Y-%m-%d', istime=False, timezone=None): @@ -45,11 +43,11 @@ UNICODE_TEXT_TRANSLATION = { } -def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezone=None, hour_str=['twelve', 'one', 'two', 'three', 'four', +def fuzzy_time(pl, format=None, 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' + '0': '{hour_str} o\'clock', '5': 'five past {hour_str}', '10': 'ten past {hour_str}','15': 'quarter past {hour_str}', + '20': 'twenty past {hour_str}', '25': 'twenty-five past {hour_str}', '30': 'half past {hour_str}', '35': 'twenty-five to {hour_str}', + '40': 'twenty to {hour_str}', '45': 'quarter to {hour_str}', '50': 'ten to {hour_str}', '55': 'five to {hour_str}' }, special_case_str = { '(23, 58)': 'round about midnight', '(23, 59)': 'round about midnight', @@ -62,8 +60,7 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon '''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.) + (unused) :param bool unicode_text: If true then hyphenminuses (regular ASCII ``-``) and single quotes are replaced with unicode dashes and apostrophes. @@ -74,7 +71,9 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon 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. + Dictionary mapping minutes to strings to be used to display them. Each entry may + optionally come with a format field "{hour_str}" to indicate the position of the + string used for the current hour. :param dict special_case_str: Special strings for special times. @@ -100,7 +99,7 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon pass hour = now.hour - if now.minute >= 30: + if now.minute >= 32: hour = hour + 1 hour = hour % len(hour_str) @@ -115,7 +114,7 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon 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]) + result = minute_str[str(min_pos)].format(hour_str=hour_str[hour]) if unicode_text: result = result.translate(UNICODE_TEXT_TRANSLATION) diff --git a/tests/test_python/test_segments.py b/tests/test_python/test_segments.py index c7a9870a..cc39fbac 100644 --- a/tests/test_python/test_segments.py +++ b/tests/test_python/test_segments.py @@ -843,17 +843,16 @@ class TestTime(TestCommon): time = Args(hour=0, minute=45) pl = Pl() hour_str = ['12', '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'} + minute_str = {'0': '{hour_str} o\'clock', '5': 'five past {hour_str}', '10': 'ten past {hour_str}', + '15': 'quarter past {hour_str}', '20': 'twenty past {hour_str}', '25': 'twenty-five past {hour_str}', + '30': 'half past {hour_str}', '35': 'twenty-five to {hour_str}', '40': 'twenty to {hour_str}', + '45': 'quarter to {hour_str}', '50': 'ten to {hour_str}', '55': 'five to {hour_str}'} special_case_str = { '(23, 58)': '~ midnight', '(23, 59)': '~ midnight', '(0, 0)': 'midnight', '(0, 1)': '~ midnight', - '(0, 2)': '~ midnight', - '(12, 0)': 'twelve o\'clock'} + '(0, 2)': '~ midnight'} with replace_attr(self.module, 'datetime', Args(strptime=lambda timezone, fmt: Args(tzinfo=timezone), now=lambda tz: time)): self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'quarter to one') self.assertEqual(self.module.fuzzy_time(pl=pl), 'quarter to one') @@ -867,7 +866,7 @@ class TestTime(TestCommon): self.assertEqual(self.module.fuzzy_time(pl=pl), 'twenty-five to twelve') time.hour = 12 time.minute = 0 - self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o\'clock') + self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '12 o\'clock') self.assertEqual(self.module.fuzzy_time(pl=pl), 'noon') time.hour = 11 time.minute = 33 @@ -875,7 +874,7 @@ class TestTime(TestCommon): self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twenty-five to twelve') time.hour = 12 time.minute = 0 - self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o\'clock') + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '12 o\'clock') self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'noon') time.hour = 11 time.minute = 33 @@ -883,7 +882,7 @@ class TestTime(TestCommon): self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twenty‐five to twelve') time.hour = 12 time.minute = 0 - self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o’clock') + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '12 o’clock') self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True),'noon') From 8484023b7b672b46c76d9c453bdc4213625952b5 Mon Sep 17 00:00:00 2001 From: Carl Smedstad Date: Thu, 29 Aug 2024 13:00:48 +0200 Subject: [PATCH 17/17] Fix Python deprecations (#2262) * Add missing import 'types' The commit 6950c04614d0bd1fc9c61b79ec7e2be0d16abdc4 did the following, but missed to import the 'types' module: - module = imp.new_module(name) + module = types.ModuleType(name) * Remove usage of 'imp' module - removed in Python 3.12 * Remove usage of deprecated method locale.getdefaultlocale() Fixes the following deprecation warning: powerline/lib/encoding.py:49: DeprecationWarning: 'locale.getdefaultlocale' is deprecated and slated for removal in Python 3.15. Use setlocale(), getencoding() and getlocale() instead. or locale.getdefaultlocale()[1] * Fix invalid escape sequences Specifically: powerline/bindings/config.py:179: SyntaxWarning: invalid escape sequence '\$' powerline/lint/__init__.py:208: SyntaxWarning: invalid escape sequence '\w' powerline/lint/__init__.py:226: SyntaxWarning: invalid escape sequence '\w' powerline/lint/__init__.py:44: SyntaxWarning: invalid escape sequence '\w' powerline/lint/spec.py:24: SyntaxWarning: invalid escape sequence '\w' powerline/lint/spec.py:588: SyntaxWarning: invalid escape sequence '\w' powerline/segments/common/mail.py:36: SyntaxWarning: invalid escape sequence '\d' --- powerline/lib/encoding.py | 10 +++++----- powerline/lint/__init__.py | 6 +++--- powerline/lint/spec.py | 4 ++-- powerline/segments/common/mail.py | 2 +- tests/modules/lib/__init__.py | 3 ++- tests/test_python/test_provided_config_files.py | 4 ++-- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/powerline/lib/encoding.py b/powerline/lib/encoding.py index 76a51d81..d5f42605 100644 --- a/powerline/lib/encoding.py +++ b/powerline/lib/encoding.py @@ -46,12 +46,12 @@ def get_preferred_output_encoding(): if hasattr(locale, 'LC_MESSAGES'): return ( locale.getlocale(locale.LC_MESSAGES)[1] - or locale.getdefaultlocale()[1] + or locale.getlocale()[1] or 'ascii' ) return ( - locale.getdefaultlocale()[1] + locale.getlocale()[1] or 'ascii' ) @@ -66,12 +66,12 @@ def get_preferred_input_encoding(): if hasattr(locale, 'LC_MESSAGES'): return ( locale.getlocale(locale.LC_MESSAGES)[1] - or locale.getdefaultlocale()[1] + or locale.getlocale()[1] or 'latin1' ) return ( - locale.getdefaultlocale()[1] + locale.getlocale()[1] or 'latin1' ) @@ -86,7 +86,7 @@ def get_preferred_arguments_encoding(): a problem. ''' return ( - locale.getdefaultlocale()[1] + locale.getlocale()[1] or 'latin1' ) diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 8c682718..76ba0fd5 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -41,7 +41,7 @@ def generate_json_config_loader(lhadproblem): return load_json_config -function_name_re = '^(\w+\.)*[a-zA-Z_]\w*$' +function_name_re = r'^(\w+\.)*[a-zA-Z_]\w*$' divider_spec = Spec().printable().len( @@ -205,7 +205,7 @@ vim_colorscheme_spec = (Spec( mode_translations_value_spec(), ).optional().context_message('Error while loading mode translations (key {key})'), ).context_message('Error while loading vim colorscheme')) -shell_mode_spec = Spec().re('^(?:[\w\-]+|\.safe)$').copy +shell_mode_spec = Spec().re(r'^(?:[\w\-]+|\.safe)$').copy shell_colorscheme_spec = (Spec( name=name_spec(), groups=groups_spec(), @@ -223,7 +223,7 @@ args_spec = Spec( segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy exinclude_spec = Spec().re(function_name_re).func(check_exinclude_function).copy segment_spec_base = Spec( - name=Spec().re('^[a-zA-Z_]\w*$').optional(), + name=Spec().re(r'^[a-zA-Z_]\w*$').optional(), function=Spec().re(function_name_re).func(check_segment_function).optional(), exclude_modes=Spec().list(vim_mode_spec()).optional(), include_modes=Spec().list(vim_mode_spec()).optional(), diff --git a/powerline/lint/spec.py b/powerline/lint/spec.py index 6a54441c..e39bfde0 100644 --- a/powerline/lint/spec.py +++ b/powerline/lint/spec.py @@ -34,7 +34,7 @@ class Spec(object): .. note:: Methods that create the specifications return ``self``, so calls to them - may be chained: ``Spec().type(unicode).re('^\w+$')``. This does not + may be chained: ``Spec().type(unicode).re('^\\w+$')``. This does not apply to functions that *apply* specification like :py:meth`Spec.match`. .. note:: @@ -585,7 +585,7 @@ class Spec(object): msg_func or (lambda value: 'String "{0}" is not an alphanumeric/underscore colon-separated identifier'.format(value)) ) - return self.re('^\w+(?::\w+)?$', msg_func) + return self.re(r'^\w+(?::\w+)?$', msg_func) def oneof(self, collection, msg_func=None): '''Describe value that is equal to one of the value in the collection diff --git a/powerline/segments/common/mail.py b/powerline/segments/common/mail.py index 9c3d9032..07577a8d 100644 --- a/powerline/segments/common/mail.py +++ b/powerline/segments/common/mail.py @@ -45,7 +45,7 @@ class EmailIMAPSegment(KwThreadedSegment): 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)) + unread_count = int(re.search(r'UNSEEN (\d+)', unread_str).group(1)) return unread_count @staticmethod diff --git a/tests/modules/lib/__init__.py b/tests/modules/lib/__init__.py index 9e3dfef7..89790584 100644 --- a/tests/modules/lib/__init__.py +++ b/tests/modules/lib/__init__.py @@ -1,6 +1,7 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) +import types import sys @@ -47,7 +48,7 @@ def urllib_read(query_url): elif query_url.startswith('https://freegeoip.app/json/'): return '{"ip":"82.145.55.16","country_code":"DE","country_name":"Germany","region_code":"NI","region_name":"Lower Saxony","city":"Meppen","zip_code":"49716","time_zone":"Europe/Berlin","latitude":52.6833,"longitude":7.3167,"metro_code":0}' elif query_url.startswith('http://geoip.nekudo.com/api/'): - return '{"city":"Meppen","country":{"name":"Germany", "code":"DE"},"location":{"accuracy_radius":100,"latitude":52.6833,"longitude":7.3167,"time_zone":"Europe\/Berlin"},"ip":"82.145.55.16"}' + return r'{"city":"Meppen","country":{"name":"Germany", "code":"DE"},"location":{"accuracy_radius":100,"latitude":52.6833,"longitude":7.3167,"time_zone":"Europe\/Berlin"},"ip":"82.145.55.16"}' elif query_url.startswith('http://query.yahooapis.com/v1/public/'): if 'Meppen' in query_url or '52.6833' in query_url: return r'{"query":{"count":1,"created":"2016-05-13T19:43:18Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Meppen, NI, DE","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","description":"Yahoo! Weather for Meppen, NI, DE","language":"en-us","lastBuildDate":"Fri, 13 May 2016 09:43 PM CEST","ttl":"60","location":{"city":"Meppen","country":"Germany","region":" NI"},"wind":{"chill":"55","direction":"350","speed":"25"},"atmosphere":{"humidity":"57","pressure":"1004.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"5:35 am","sunset":"9:21 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Meppen, NI, DE at 08:00 PM CEST","lat":"52.68993","long":"7.29115","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","pubDate":"Fri, 13 May 2016 08:00 PM CEST","condition":{"code":"23","date":"Fri, 13 May 2016 08:00 PM CEST","temp":"14","text":"Breezy"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"71","low":"48","text":"Partly Cloudy"},{"code":"28","date":"14 May 2016","day":"Sat","high":"54","low":"44","text":"Mostly Cloudy"},{"code":"11","date":"15 May 2016","day":"Sun","high":"55","low":"43","text":"Showers"},{"code":"28","date":"16 May 2016","day":"Mon","high":"54","low":"42","text":"Mostly Cloudy"},{"code":"28","date":"17 May 2016","day":"Tue","high":"57","low":"43","text":"Mostly Cloudy"},{"code":"12","date":"18 May 2016","day":"Wed","high":"62","low":"45","text":"Rain"},{"code":"28","date":"19 May 2016","day":"Thu","high":"63","low":"48","text":"Mostly Cloudy"},{"code":"28","date":"20 May 2016","day":"Fri","high":"67","low":"50","text":"Mostly Cloudy"},{"code":"30","date":"21 May 2016","day":"Sat","high":"71","low":"50","text":"Partly Cloudy"},{"code":"30","date":"22 May 2016","day":"Sun","high":"74","low":"54","text":"Partly Cloudy"}],"description":"\n
\nCurrent Conditions:\n
Breezy\n
\n
\nForecast:\n
Fri - Partly Cloudy. High: 71Low: 48\n
Sat - Mostly Cloudy. High: 54Low: 44\n
Sun - Showers. High: 55Low: 43\n
Mon - Mostly Cloudy. High: 54Low: 42\n
Tue - Mostly Cloudy. High: 57Low: 43\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n(provided by The Weather Channel)\n
\n]]>","guid":{"isPermaLink":"false"}}}}}}' diff --git a/tests/test_python/test_provided_config_files.py b/tests/test_python/test_provided_config_files.py index fd8b16e6..ca08a6d5 100644 --- a/tests/test_python/test_provided_config_files.py +++ b/tests/test_python/test_provided_config_files.py @@ -111,7 +111,7 @@ class TestVimConfig(TestCase): class TestConfig(TestCase): def test_tmux(self): from powerline.segments import common - from imp import reload + from importlib import reload reload(common) from powerline.shell import ShellPowerline with replace_attr(common, 'urllib_read', urllib_read): @@ -165,7 +165,7 @@ class TestConfig(TestCase): def test_wm(self): from powerline.segments import common - from imp import reload + from importlib import reload reload(common) from powerline import Powerline with replace_attr(wthr, 'urllib_read', urllib_read):