From 1552df42328ae4e4e4d291e63ad7b62cae59d2b5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 16 May 2022 19:46:26 -0500 Subject: [PATCH 1/7] refactor!: Pane to a dataclass --- src/libtmux/pane.py | 71 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 469a0d9b3..31ed7fcca 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -5,6 +5,7 @@ ~~~~~~~~~~~~ """ +import dataclasses import logging import typing as t from typing import overload @@ -24,8 +25,11 @@ logger = logging.getLogger(__name__) +__all__ = ["Pane"] -class Pane(TmuxMappingObject): + +@dataclasses.dataclass +class Pane(TmuxMappingObject, TmuxRelationalObject): """ A :term:`tmux(1)` :term:`Pane` [pane_manual]_. @@ -68,26 +72,61 @@ class Pane(TmuxMappingObject): Accessed April 1st, 2018. """ + window: "libtmux.window.Window" + session_name: str = dataclasses.field(init=True) + session_id: str = dataclasses.field(init=True) + window_index: str = dataclasses.field(init=True) + window_id: str = dataclasses.field(init=True) + history_size: str + history_limit: str + history_bytes: str + pane_index: str + pane_width: str + pane_height: str + pane_title: str + _pane_id: str = dataclasses.field(init=False) # Legacy, relational + pane_id: str + pane_active: str + pane_dead: str + pane_in_mode: str + pane_synchronized: str + pane_tty: str + pane_pid: str + pane_current_path: str + pane_current_command: str + cursor_x: str + cursor_y: str + scroll_region_upper: str + scroll_region_lower: str + alternate_on: str + alternate_saved_x: str + alternate_saved_y: str + cursor_flag: str + insert_flag: str + keypad_cursor_flag: str + keypad_flag: str + wrap_flag: str + mouse_standard_flag: str + mouse_button_flag: str + mouse_any_flag: str + mouse_utf8_flag: str + session: "libtmux.session.Session" = dataclasses.field(init=False) + server: "libtmux.server.Server" = dataclasses.field(init=False) + window_name: str = dataclasses.field(init=True, default="") + pane_start_command: Optional[str] = dataclasses.field(init=True, default=None) + formatter_prefix = "pane_" """Namespace used for :class:`~libtmux.common.TmuxMappingObject`""" - window: "Window" - """:class:`libtmux.Window` pane is linked to""" - session: "Session" - """:class:`libtmux.Session` pane is linked to""" - server: "Server" - """:class:`libtmux.Server` pane is linked to""" - - def __init__( - self, - window: "Window", - pane_id: t.Union[str, int], - **kwargs: t.Any, - ) -> None: - self.window = window + + def __post_init__(self, **kwargs): + # if not window: + # raise ValueError("Pane must have ``Window`` object") + # + # self.window = window self.session = self.window.session self.server = self.session.server - self._pane_id = pane_id + self._pane_id = self.pane_id self.server._update_panes() From e4a5a3a183a80d83237c672e62970aaae5f6d60b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Tue, 17 May 2022 06:32:34 -0500 Subject: [PATCH 2/7] !squash more pane --- src/libtmux/pane.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 31ed7fcca..c74b420fe 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -5,6 +5,8 @@ ~~~~~~~~~~~~ """ +from __future__ import annotations + import dataclasses import logging import typing as t @@ -12,6 +14,8 @@ from libtmux.common import tmux_cmd +import libtmux + from . import exc from .common import PaneDict, TmuxMappingObject, TmuxRelationalObject @@ -27,6 +31,8 @@ __all__ = ["Pane"] +# class Pane(TmuxMappingObject, TmuxRelationalObject): + @dataclasses.dataclass class Pane(TmuxMappingObject, TmuxRelationalObject): @@ -128,8 +134,25 @@ def __post_init__(self, **kwargs): self._pane_id = self.pane_id + try: + info = self._info + except IndexError: + info = {} + for k, v in info.items(): + if not hasattr(k, v): + setattr(self, k, v) + self.server._update_panes() + def refresh(self): + try: + info = self._info + except IndexError: + info = {} + for k, v in info.items(): + if not hasattr(k, v): + setattr(self, k, v) + @property def _info(self) -> PaneDict: # type: ignore # mypy#1362 attrs = {"pane_id": self._pane_id} @@ -354,6 +377,7 @@ def resize_pane(self, *args: t.Any, **kwargs: t.Any) -> "Pane": raise exc.LibTmuxException(proc.stderr) self.server._update_panes() + self.refresh() return self def enter(self) -> None: From a254123b24c12a52727915579bef5f8b3fbb0079 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Tue, 17 May 2022 06:32:45 -0500 Subject: [PATCH 3/7] tests update for tests_pane --- src/libtmux/pane.py | 15 ++++++--------- tests/test_pane.py | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index c74b420fe..aaec87d99 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -12,12 +12,11 @@ import typing as t from typing import overload -from libtmux.common import tmux_cmd - import libtmux +from libtmux.common import tmux_cmd from . import exc -from .common import PaneDict, TmuxMappingObject, TmuxRelationalObject +from .common import PaneDict, TmuxMappingObject if t.TYPE_CHECKING: from typing_extensions import Literal @@ -31,11 +30,9 @@ __all__ = ["Pane"] -# class Pane(TmuxMappingObject, TmuxRelationalObject): - @dataclasses.dataclass -class Pane(TmuxMappingObject, TmuxRelationalObject): +class Pane(TmuxMappingObject): """ A :term:`tmux(1)` :term:`Pane` [pane_manual]_. @@ -119,12 +116,12 @@ class Pane(TmuxMappingObject, TmuxRelationalObject): session: "libtmux.session.Session" = dataclasses.field(init=False) server: "libtmux.server.Server" = dataclasses.field(init=False) window_name: str = dataclasses.field(init=True, default="") - pane_start_command: Optional[str] = dataclasses.field(init=True, default=None) + pane_start_command: t.Optional[str] = dataclasses.field(init=True, default=None) formatter_prefix = "pane_" """Namespace used for :class:`~libtmux.common.TmuxMappingObject`""" - def __post_init__(self, **kwargs): + def __post_init__(self, **kwargs: t.Any) -> None: # if not window: # raise ValueError("Pane must have ``Window`` object") # @@ -144,7 +141,7 @@ def __post_init__(self, **kwargs): self.server._update_panes() - def refresh(self): + def refresh(self) -> None: try: info = self._info except IndexError: diff --git a/tests/test_pane.py b/tests/test_pane.py index ef33cca48..ce5eb146d 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -14,15 +14,15 @@ def test_resize_pane(session: Session) -> None: pane1 = window.attached_pane assert pane1 is not None - pane1_height = pane1["pane_height"] + pane1_height = pane1.pane_height window.split_window() pane1.resize_pane(height=4) - assert pane1["pane_height"] != pane1_height - assert int(pane1["pane_height"]) == 4 + assert pane1.pane_height != pane1_height + assert int(pane1.pane_height) == 4 pane1.resize_pane(height=3) - assert int(pane1["pane_height"]) == 3 + assert int(pane1.pane_height) == 3 def test_send_keys(session: Session) -> None: @@ -42,11 +42,11 @@ def test_set_height(session: Session) -> None: window.split_window() pane1 = window.attached_pane assert pane1 is not None - pane1_height = pane1["pane_height"] + pane1_height = pane1.pane_height pane1.set_height(4) - assert pane1["pane_height"] != pane1_height - assert int(pane1["pane_height"]) == 4 + assert pane1.pane_height != pane1_height + assert int(pane1.pane_height) == 4 def test_set_width(session: Session) -> None: @@ -56,11 +56,11 @@ def test_set_width(session: Session) -> None: window.select_layout("main-vertical") pane1 = window.attached_pane assert pane1 is not None - pane1_width = pane1["pane_width"] + pane1_width = pane1.pane_width pane1.set_width(10) - assert pane1["pane_width"] != pane1_width - assert int(pane1["pane_width"]) == 10 + assert pane1.pane_width != pane1_width + assert int(pane1.pane_width) == 10 pane1.reset() From 9dc399e563f6c2421dd9cc18039ac38d3709ba1c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Tue, 17 May 2022 06:32:52 -0500 Subject: [PATCH 4/7] !squash fix window --- src/libtmux/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 434dca17c..aaf999a86 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -500,7 +500,7 @@ def split_window( if target: tmux_args += ("-t%s" % target,) else: - tmux_args += ("-t%s" % self.panes[0].get("pane_id"),) + tmux_args += ("-t%s" % self.panes[0].pane_id,) if vertical: tmux_args += ("-v",) From c48364418f454973a4f5c44e7a7cbddb544ad38e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Tue, 17 May 2022 06:37:51 -0500 Subject: [PATCH 5/7] !squash pane: annotate Pane.{window,session,server} --- src/libtmux/pane.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index aaec87d99..a48ff3ceb 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -75,7 +75,7 @@ class Pane(TmuxMappingObject): Accessed April 1st, 2018. """ - window: "libtmux.window.Window" + window: libtmux.window.Window session_name: str = dataclasses.field(init=True) session_id: str = dataclasses.field(init=True) window_index: str = dataclasses.field(init=True) @@ -113,8 +113,8 @@ class Pane(TmuxMappingObject): mouse_button_flag: str mouse_any_flag: str mouse_utf8_flag: str - session: "libtmux.session.Session" = dataclasses.field(init=False) - server: "libtmux.server.Server" = dataclasses.field(init=False) + session: libtmux.session.Session = dataclasses.field(init=False) + server: libtmux.server.Server = dataclasses.field(init=False) window_name: str = dataclasses.field(init=True, default="") pane_start_command: t.Optional[str] = dataclasses.field(init=True, default=None) From a4d21058feb0c585656eee8fc0be017198d31b8a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Tue, 17 May 2022 07:13:03 -0500 Subject: [PATCH 6/7] refactor(pane,session,window): Use object.__getattribute__ See also: - In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name) https://docs.python.org/2.7/reference/datamodel.html#object.__getattribute__ --- src/libtmux/pane.py | 3 ++- src/libtmux/session.py | 3 ++- src/libtmux/window.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index a48ff3ceb..15c4a0e85 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -152,7 +152,8 @@ def refresh(self) -> None: @property def _info(self) -> PaneDict: # type: ignore # mypy#1362 - attrs = {"pane_id": self._pane_id} + # attrs = {"pane_id": self._pane_id} + attrs = {"pane_id": object.__getattribute__(self, "_pane_id")} # from https://github.com/serkanyersen/underscore.py def by(val: PaneDict) -> bool: diff --git a/src/libtmux/session.py b/src/libtmux/session.py index f996286de..99e91936e 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -86,7 +86,8 @@ def __init__(self, server: "Server", session_id: str, **kwargs: t.Any) -> None: @property def _info(self) -> t.Optional[SessionDict]: # type: ignore # mypy#1362 - attrs = {"session_id": str(self._session_id)} + # attrs = {"session_id": str(self._session_id)} + attrs = {"session_id": object.__getattribute__(self, "_session_id")} def by(val: SessionDict) -> bool: for key in attrs.keys(): diff --git a/src/libtmux/window.py b/src/libtmux/window.py index aaf999a86..0edfb686a 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -103,7 +103,8 @@ def __repr__(self) -> str: @property def _info(self) -> WindowDict: # type: ignore # mypy#1362 - attrs = {"window_id": self._window_id} + # attrs = {"window_id": self._window_id} + attrs = {"window_id": object.__getattribute__(self, "_window_id")} # from https://github.com/serkanyersen/underscore.py def by(val: WindowDict) -> bool: From ebbdb613fcbc9ee7dd9414f91a527d3dd437a4af Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Tue, 17 May 2022 07:28:12 -0500 Subject: [PATCH 7/7] !squash latest stash commit (misc and WIP RecursionError) --- src/libtmux/common.py | 15 ++++++++++++++- src/libtmux/pane.py | 28 ++++++++++++++++------------ tests/test_server.py | 2 +- tests/test_tmuxobject.py | 12 ++++++------ 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/libtmux/common.py b/src/libtmux/common.py index d914d536d..48a74eea9 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -5,6 +5,7 @@ ~~~~~~~~~~~~~~ """ +import dataclasses import logging import os import re @@ -320,6 +321,15 @@ def __len__(self) -> int: return len(self._info.keys()) def __getattr__(self, key: str) -> str: + try: + # val = self._info[self.formatter_prefix + key] + val = object.__getattribute__(self, key) + assert val is not None + assert isinstance(val, str) + return val + except AttributeError: + pass + try: val = self._info[self.formatter_prefix + key] assert val is not None @@ -405,9 +415,12 @@ def where(self, attrs: D, first: bool = False) -> t.Union[List[O], O]: # from https://github.com/serkanyersen/underscore.py def by(val: O) -> bool: + val2: t.Dict[str, str] = {} + if dataclasses.is_dataclass(val): + val2 = dataclasses.asdict(val) for key in attrs.keys(): try: - if attrs[key] != val[key]: + if attrs[key] != val[key] and attrs[key] != val2[key]: return False except KeyError: return False diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 15c4a0e85..b4062ffae 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -32,7 +32,10 @@ @dataclasses.dataclass -class Pane(TmuxMappingObject): +# <<<<<<< HEAD:src/libtmux/pane.py +# class Pane(TmuxMappingObject): +# ======= +class Pane: """ A :term:`tmux(1)` :term:`Pane` [pane_manual]_. @@ -118,7 +121,7 @@ class Pane(TmuxMappingObject): window_name: str = dataclasses.field(init=True, default="") pane_start_command: t.Optional[str] = dataclasses.field(init=True, default=None) - formatter_prefix = "pane_" + formatter_prefix: str = "pane_" """Namespace used for :class:`~libtmux.common.TmuxMappingObject`""" def __post_init__(self, **kwargs: t.Any) -> None: @@ -129,7 +132,7 @@ def __post_init__(self, **kwargs: t.Any) -> None: self.session = self.window.session self.server = self.session.server - self._pane_id = self.pane_id + self._pane_id = kwargs.get("pane_id", self.pane_id) try: info = self._info @@ -157,6 +160,8 @@ def _info(self) -> PaneDict: # type: ignore # mypy#1362 # from https://github.com/serkanyersen/underscore.py def by(val: PaneDict) -> bool: + if dataclasses.is_dataclass(val): + val = dataclasses.asdict(val) for key in attrs.keys(): try: if attrs[key] != val[key]: @@ -182,7 +187,7 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: :class:`Server.cmd` """ if not any(arg.startswith("-t") for arg in args): - args = ("-t", self.get("pane_id")) + args + args = ("-t", self.pane_id) + args return self.server.cmd(cmd, *args, **kwargs) @@ -311,7 +316,7 @@ def split_window( :class:`Pane` """ return self.window.split_window( - target=self.get("pane_id"), + target=self.pane_id, start_directory=start_directory, attach=attach, vertical=vertical, @@ -410,12 +415,11 @@ def select_pane(self) -> "Pane": ------- :class:`pane` """ - pane = self.window.select_pane(self._pane_id) - if pane is None: - raise exc.LibTmuxException(f"Pane not found: {self}") - return pane + # pane = self.window.select_pane(self._pane_id) + # if pane is None: + # raise exc.LibTmuxException(f"Pane not found: {self}") + # return pane + return self.window.select_pane(self.pane_id) def __repr__(self) -> str: - return "{}({} {})".format( - self.__class__.__name__, self.get("pane_id"), self.window - ) + return "{}({} {})".format(self.__class__.__name__, self.pane_id, self.window) diff --git a/tests/test_server.py b/tests/test_server.py index 782150086..d75ce6dd1 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -116,7 +116,7 @@ def test_new_session_shell(server: Server) -> None: assert mysession.get("session_name") == "test_new_session" assert server.has_session("test_new_session") - pane_start_command = pane.get("pane_start_command") + pane_start_command = pane.pane_start_command assert pane_start_command is not None if has_gte_version("3.2"): diff --git a/tests/test_tmuxobject.py b/tests/test_tmuxobject.py index 992c73d3f..3824a98c3 100644 --- a/tests/test_tmuxobject.py +++ b/tests/test_tmuxobject.py @@ -34,7 +34,7 @@ def test_find_where(server: Server, session: Session) -> None: # window.find_where for pane in window.panes: pane_id = pane.get("pane_id") - assert pane_id is not None + pane_id = pane.pane_id assert window.find_where({"pane_id": pane_id}) == pane assert isinstance(window.find_where({"pane_id": pane_id}), Pane) @@ -84,9 +84,9 @@ def test_find_where_multiple_infos(server: Server, session: Session) -> None: # window.find_where for pane in window.panes: - pane_id = pane.get("pane_id") + pane_id = pane.pane_id assert pane_id is not None - pane_tty = pane.get("pane_tty") + pane_tty = pane.pane_tty assert pane_tty is not None find_where = window.find_where( @@ -137,10 +137,10 @@ def test_where(server: Server, session: Session) -> None: # window.where for pane in window.panes: - pane_id = pane.get("pane_id") + pane_id = pane.pane_id assert pane_id is not None - pane_tty = pane.get("pane_tty") + pane_tty = pane.pane_tty assert pane_tty is not None window_panes = window.where({"pane_id": pane_id, "pane_tty": pane_tty}) @@ -181,7 +181,7 @@ def test_get_by_id(server: Server, session: Session) -> None: # window.get_by_id for pane in window.panes: - pane_id = pane.get("pane_id") + pane_id = pane.pane_id assert pane_id is not None get_pane_by_id = window.get_by_id(pane_id)