From fb36e9e323dbd7020dd6dda9d303c9b33b94a58d Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sat, 25 Apr 2020 00:03:53 +0200 Subject: [PATCH] Black, removed six usage. --- pymux/__init__.py | 1 - pymux/__main__.py | 3 +- pymux/arrangement.py | 182 ++++--- pymux/client/__init__.py | 1 - pymux/client/base.py | 9 +- pymux/client/defaults.py | 10 +- pymux/client/posix.py | 105 ++-- pymux/client/windows.py | 62 +-- pymux/commands/aliases.py | 76 ++- pymux/commands/commands.py | 356 +++++++------- pymux/commands/completer.py | 74 +-- pymux/commands/utils.py | 10 +- pymux/entry_points/run_pymux.py | 83 ++-- pymux/enums.py | 10 +- pymux/filters.py | 25 +- pymux/format.py | 54 +- pymux/key_bindings.py | 102 ++-- pymux/key_mappings.py | 344 +++++++------ pymux/layout.py | 848 ++++++++++++++++++++------------ pymux/log.py | 5 +- pymux/main.py | 190 +++---- pymux/options.py | 121 ++--- pymux/pipes/__init__.py | 13 +- pymux/pipes/base.py | 11 +- pymux/pipes/posix.py | 56 +-- pymux/pipes/win32.py | 105 ++-- pymux/pipes/win32_client.py | 24 +- pymux/pipes/win32_server.py | 64 ++- pymux/rc.py | 6 +- pymux/server.py | 95 ++-- pymux/style.py | 129 +++-- pymux/utils.py | 32 +- pyproject.toml | 13 + setup.py | 3 +- 34 files changed, 1734 insertions(+), 1488 deletions(-) create mode 100644 pyproject.toml diff --git a/pymux/__init__.py b/pymux/__init__.py index baffc48..e69de29 100644 --- a/pymux/__init__.py +++ b/pymux/__init__.py @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/pymux/__main__.py b/pymux/__main__.py index a95c19f..fb64663 100644 --- a/pymux/__main__.py +++ b/pymux/__main__.py @@ -1,8 +1,7 @@ """ Make sure `python -m pymux` works. """ -from __future__ import unicode_literals from .entry_points.run_pymux import run -if __name__ == '__main__': +if __name__ == "__main__": run() diff --git a/pymux/arrangement.py b/pymux/arrangement.py index 81e9942..bd07ba8 100644 --- a/pymux/arrangement.py +++ b/pymux/arrangement.py @@ -7,49 +7,48 @@ An arrangement consists of a list of windows. And a window has a list of panes, arranged by ordering them in HSplit/VSplit instances. """ -from __future__ import unicode_literals - -from ptterm import Terminal -from prompt_toolkit.application.current import get_app, get_app_or_none, set_app -from prompt_toolkit.buffer import Buffer - import math import os import weakref -import six +from typing import Optional + +from prompt_toolkit.application.current import get_app, get_app_or_none, set_app +from prompt_toolkit.buffer import Buffer +from ptterm import Terminal __all__ = ( - 'LayoutTypes', - 'Pane', - 'HSplit', - 'VSplit', - 'Window', - 'Arrangement', + "LayoutTypes", + "Pane", + "HSplit", + "VSplit", + "Window", + "Arrangement", ) class LayoutTypes: # The values are in lowercase with dashes, because that is what users can # use at the command line. - EVEN_HORIZONTAL = 'even-horizontal' - EVEN_VERTICAL = 'even-vertical' - MAIN_HORIZONTAL = 'main-horizontal' - MAIN_VERTICAL = 'main-vertical' - TILED = 'tiled' + EVEN_HORIZONTAL = "even-horizontal" + EVEN_VERTICAL = "even-vertical" + MAIN_HORIZONTAL = "main-horizontal" + MAIN_VERTICAL = "main-vertical" + TILED = "tiled" _ALL = [EVEN_HORIZONTAL, EVEN_VERTICAL, MAIN_HORIZONTAL, MAIN_VERTICAL, TILED] -class Pane(object): +class Pane: """ One pane, containing one process and a search buffer for going into copy mode or displaying the help. """ - _pane_counter = 1000 # Start at 1000, to be sure to not confuse this with pane indexes. - def __init__(self, terminal=None): - assert isinstance(terminal, Terminal) + _pane_counter = ( + 1000 # Start at 1000, to be sure to not confuse this with pane indexes. + ) + def __init__(self, terminal: Terminal) -> None: self.terminal = terminal self.chosen_name = None @@ -66,16 +65,15 @@ def __init__(self, terminal=None): # get_tokens_for_line, that returns the token list with color # information for each line. self.scroll_buffer = Buffer(read_only=True) - self.copy_get_tokens_for_line = lambda lineno: [] self.display_scroll_buffer = False - self.scroll_buffer_title = '' + self.scroll_buffer_title = "" @property def process(self): return self.terminal.process @property - def name(self): + def name(self) -> str: """ The name for the window as displayed in the title bar and status bar. """ @@ -88,16 +86,16 @@ def name(self): if name: return os.path.basename(name) - return '' + return "" - def enter_copy_mode(self): + def enter_copy_mode(self) -> None: """ Suspend the process, and copy the screen content to the `scroll_buffer`. That way the user can search through the history and copy/paste. """ self.terminal.enter_copy_mode() - def focus(self): + def focus(self) -> None: """ Focus this pane. """ @@ -114,6 +112,7 @@ class _WeightsDictionary(weakref.WeakKeyDictionary): This dictionary maps the child (another HSplit/VSplit or Pane), to the size. (Integer.) """ + def __getitem__(self, key): try: # (Don't use 'super' here. This is a classobj in Python2.) @@ -127,6 +126,7 @@ class _Split(list): Base class for horizontal and vertical splits. (This is a higher level split than prompt_toolkit.layout.HSplit.) """ + def __init__(self, *a, **kw): list.__init__(self, *a, **kw) @@ -138,7 +138,7 @@ def __hash__(self): return id(self) def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, list.__repr__(self)) + return "%s(%s)" % (self.__class__.__name__, list.__repr__(self)) class HSplit(_Split): @@ -149,10 +149,11 @@ class VSplit(_Split): """ Horizontal split. """ -class Window(object): +class Window: """ Pymux window. """ + _window_counter = 1000 # Start here, to avoid confusion with window index. def __init__(self, index=0): @@ -178,8 +179,8 @@ def invalidation_hash(self): Return a hash (string) that can be used to determine when the layout has to be rebuild. """ -# if not self.root: -# return '' + # if not self.root: + # return '' def _hash_for_split(split): result = [] @@ -187,15 +188,18 @@ def _hash_for_split(split): if isinstance(item, (VSplit, HSplit)): result.append(_hash_for_split(item)) elif isinstance(item, Pane): - result.append('p%s' % item.pane_id) + result.append("p%s" % item.pane_id) if isinstance(split, HSplit): - return 'HSplit(%s)' % (','.join(result)) + return "HSplit(%s)" % (",".join(result)) else: - return 'VSplit(%s)' % (','.join(result)) + return "VSplit(%s)" % (",".join(result)) - return '' % ( - self.window_id, self.zoom, _hash_for_split(self.root)) + return "" % ( + self.window_id, + self.zoom, + _hash_for_split(self.root), + ) @property def active_pane(self): @@ -205,9 +209,7 @@ def active_pane(self): return self._active_pane @active_pane.setter - def active_pane(self, value): - assert isinstance(value, Pane) - + def active_pane(self, value: Pane): # Remember previous active pane. if self._active_pane: self._prev_active_pane = weakref.ref(self._active_pane) @@ -240,15 +242,12 @@ def name(self): if pane: return pane.name - return '' + return "" - def add_pane(self, pane, vsplit=False): + def add_pane(self, pane: Pane, vsplit: bool = False) -> None: """ Add another pane to this Window. """ - assert isinstance(pane, Pane) - assert isinstance(vsplit, bool) - split_cls = VSplit if vsplit else HSplit if self.active_pane is None: @@ -272,12 +271,10 @@ def add_pane(self, pane, vsplit=False): self.active_pane = pane self.zoom = False - def remove_pane(self, pane): + def remove_pane(self, pane: Pane) -> None: """ Remove pane from this Window. """ - assert isinstance(pane, Pane) - if pane in self.panes: # When this pane was focused, switch to previous active or next in order. if pane == self.active_pane: @@ -354,7 +351,9 @@ def focus_next(self, count=1): " Focus the next pane. " panes = self.panes if panes: - self.active_pane = panes[(panes.index(self.active_pane) + count) % len(panes)] + self.active_pane = panes[ + (panes.index(self.active_pane) + count) % len(panes) + ] else: self.active_pane = None # No panes left. @@ -381,10 +380,10 @@ def rotate(self, count=1, with_pane_before_only=False, with_pane_after_only=Fals # Only before after? Reduce list of panes. if with_pane_before_only: - items = items[current_pane_index - 1:current_pane_index + 1] + items = items[current_pane_index - 1 : current_pane_index + 1] elif with_pane_after_only: - items = items[current_pane_index:current_pane_index + 2] + items = items[current_pane_index : current_pane_index + 2] # Rotate positions. for i, triple in enumerate(items): @@ -417,22 +416,26 @@ def select_layout(self, layout_type): # main-horizontal. elif layout_type == LayoutTypes.MAIN_HORIZONTAL: - self.root = HSplit([ - self.active_pane, - VSplit([p for p in self.panes if p != self.active_pane]) - ]) + self.root = HSplit( + [ + self.active_pane, + VSplit([p for p in self.panes if p != self.active_pane]), + ] + ) # main-vertical. elif layout_type == LayoutTypes.MAIN_VERTICAL: - self.root = VSplit([ - self.active_pane, - HSplit([p for p in self.panes if p != self.active_pane]) - ]) + self.root = VSplit( + [ + self.active_pane, + HSplit([p for p in self.panes if p != self.active_pane]), + ] + ) # tiled. elif layout_type == LayoutTypes.TILED: panes = self.panes - column_count = math.ceil(len(panes) ** .5) + column_count = math.ceil(len(panes) ** 0.5) rows = HSplit() current_row = VSplit() @@ -482,12 +485,11 @@ def change_size_for_active_pane(self, up=0, right=0, down=0, left=0): child = self.active_pane self.change_size_for_pane(child, up=up, right=right, down=down, left=left) - def change_size_for_pane(self, pane, up=0, right=0, down=0, left=0): + def change_size_for_pane(self, pane: Pane, up=0, right=0, down=0, left=0): """ Increase the size of the current pane in any of the four directions. Positive values indicate an increase, negative values a decrease. """ - assert isinstance(pane, Pane) def find_split_and_child(split_cls, is_before): " Find the split for which we will have to update the weights. " @@ -495,9 +497,11 @@ def find_split_and_child(split_cls, is_before): split = self._get_parent(child) def found(): - return isinstance(split, split_cls) and ( - not is_before or split.index(child) > 0) and ( - is_before or split.index(child) < len(split) - 1) + return ( + isinstance(split, split_cls) + and (not is_before or split.index(child) > 0) + and (is_before or split.index(child) < len(split) - 1) + ) while split and not found(): child = split @@ -532,27 +536,28 @@ def handle_side(split_cls, is_before, amount, trying_other_side=False): # case it's logical to move the left border to the right # instead. if not trying_other_side: - handle_side(split_cls, not is_before, -amount, - trying_other_side=True) + handle_side( + split_cls, not is_before, -amount, trying_other_side=True + ) handle_side(VSplit, True, left) handle_side(VSplit, False, right) handle_side(HSplit, True, up) handle_side(HSplit, False, down) - def get_pane_index(self, pane): + def get_pane_index(self, pane: Pane): " Return the index of the given pane. ValueError if not found. " - assert isinstance(pane, Pane) return self.panes.index(pane) -class Arrangement(object): +class Arrangement: """ Arrangement class for one Pymux session. This contains the list of windows and the layout of the panes for each window. All the clients share the same Arrangement instance, but they can have different windows active. """ + def __init__(self): self.windows = [] self.base_index = 0 @@ -569,7 +574,7 @@ def invalidation_hash(self): When this changes, the layout needs to be rebuild. """ if not self.windows: - return '' + return "" w = self.get_active_window() return w.invalidation_hash() @@ -583,11 +588,12 @@ def get_active_window(self): try: return self._active_window_for_cli[app] except KeyError: - self._active_window_for_cli[app] = self._last_active_window or self.windows[0] + self._active_window_for_cli[app] = ( + self._last_active_window or self.windows[0] + ) return self.windows[0] - def set_active_window(self, window): - assert isinstance(window, Window) + def set_active_window(self, window: Window): app = get_app() previous = self.get_active_window() @@ -595,12 +601,10 @@ def set_active_window(self, window): self._active_window_for_cli[app] = window self._last_active_window = window - def set_active_window_from_pane_id(self, pane_id): + def set_active_window_from_pane_id(self, pane_id: int): """ Make the window with this pane ID the active Window. """ - assert isinstance(pane_id, int) - for w in self.windows: for p in w.panes: if p.pane_id == pane_id: @@ -621,7 +625,7 @@ def get_window_by_index(self, index): if w.index == index: return w - def create_window(self, pane, name=None, set_active=True): + def create_window(self, pane: Pane, name: Optional[str] = None, set_active=True): """ Create a new window that contains just this pane. @@ -629,9 +633,6 @@ def create_window(self, pane, name=None, set_active=True): :param name: If given, name for the new window. :param set_active: When True, focus the new window. """ - assert isinstance(pane, Pane) - assert name is None or isinstance(name, six.text_type) - # Take the first available index. taken_indexes = [w.index for w in self.windows] @@ -658,13 +659,10 @@ def create_window(self, pane, name=None, set_active=True): assert w.active_pane == pane assert w._get_parent(pane) - def move_window(self, window, new_index): + def move_window(self, window: Window, new_index: int): """ Move window to a new index. """ - assert isinstance(window, Window) - assert isinstance(new_index, int) - window.index = new_index # Sort windows by index. @@ -678,12 +676,10 @@ def get_active_pane(self): if w is not None: return w.active_pane - def remove_pane(self, pane): + def remove_pane(self, pane: Pane): """ Remove a :class:`.Pane`. (Look in all windows.) """ - assert isinstance(pane, Pane) - for w in self.windows: w.remove_pane(pane) @@ -700,14 +696,16 @@ def remove_pane(self, pane): def focus_previous_window(self): w = self.get_active_window() - self.set_active_window(self.windows[ - (self.windows.index(w) - 1) % len(self.windows)]) + self.set_active_window( + self.windows[(self.windows.index(w) - 1) % len(self.windows)] + ) def focus_next_window(self): w = self.get_active_window() - self.set_active_window(self.windows[ - (self.windows.index(w) + 1) % len(self.windows)]) + self.set_active_window( + self.windows[(self.windows.index(w) + 1) % len(self.windows)] + ) def break_pane(self, set_active=True): """ diff --git a/pymux/client/__init__.py b/pymux/client/__init__.py index dacbf9e..a602a09 100644 --- a/pymux/client/__init__.py +++ b/pymux/client/__init__.py @@ -1,3 +1,2 @@ -from __future__ import unicode_literals from .base import Client from .defaults import create_client, list_clients diff --git a/pymux/client/base.py b/pymux/client/base.py index 8443360..b9635a4 100644 --- a/pymux/client/base.py +++ b/pymux/client/base.py @@ -1,16 +1,13 @@ -from __future__ import unicode_literals +from abc import ABC from prompt_toolkit.output import ColorDepth -from abc import ABCMeta -from six import with_metaclass - __all__ = [ - 'Client', + "Client", ] -class Client(with_metaclass(ABCMeta, object)): +class Client(ABC): def run_command(self, command, pane_id=None): """ Ask the server to run this command. diff --git a/pymux/client/defaults.py b/pymux/client/defaults.py index 929e874..d236f77 100644 --- a/pymux/client/defaults.py +++ b/pymux/client/defaults.py @@ -1,24 +1,28 @@ -from __future__ import unicode_literals from prompt_toolkit.utils import is_windows + __all__ = [ - 'create_client', - 'list_clients', + "create_client", + "list_clients", ] def create_client(socket_name): if is_windows(): from .windows import WindowsClient + return WindowsClient(socket_name) else: from .posix import PosixClient + return PosixClient(socket_name) def list_clients(): if is_windows(): from .windows import list_clients + return list_clients() else: from .posix import list_clients + return list_clients() diff --git a/pymux/client/posix.py b/pymux/client/posix.py index 2b68986..535de65 100644 --- a/pymux/client/posix.py +++ b/pymux/client/posix.py @@ -1,13 +1,3 @@ -from __future__ import unicode_literals - -from select import select -from prompt_toolkit.input.posix_utils import PosixStdinReader -from prompt_toolkit.input.vt100 import raw_mode, cooked_mode -from prompt_toolkit.output.vt100 import _get_size, Vt100_Output -from prompt_toolkit.output import ColorDepth - -from pymux.utils import nonblocking - import getpass import glob import json @@ -16,13 +6,21 @@ import socket import sys import tempfile +from select import select + +from prompt_toolkit.input.posix_utils import PosixStdinReader +from prompt_toolkit.input.vt100 import cooked_mode, raw_mode +from prompt_toolkit.output import ColorDepth +from prompt_toolkit.output.vt100 import Vt100_Output, _get_size +from pymux.utils import nonblocking + from .base import Client -INPUT_TIMEOUT = .5 +INPUT_TIMEOUT = 0.5 __all__ = ( - 'PosixClient', - 'list_clients', + "PosixClient", + "list_clients", ) @@ -46,7 +44,7 @@ def __init__(self, socket_name): # decoding otherwise. (Also don't pass errors='ignore', because # that doesn't work for parsing mouse input escape sequences, which # consist of a fixed number of bytes.) - self._stdin_reader = PosixStdinReader(sys.stdin.fileno(), errors='replace') + self._stdin_reader = PosixStdinReader(sys.stdin.fileno(), errors="replace") def run_command(self, command, pane_id=None): """ @@ -54,35 +52,34 @@ def run_command(self, command, pane_id=None): :param pane_id: Optional identifier of the current pane. """ - self._send_packet({ - 'cmd': 'run-command', - 'data': command, - 'pane_id': pane_id - }) + self._send_packet({"cmd": "run-command", "data": command, "pane_id": pane_id}) - def attach(self, detach_other_clients=False, color_depth=ColorDepth.DEPTH_8_BIT): + def attach( + self, detach_other_clients: bool = False, color_depth=ColorDepth.DEPTH_8_BIT + ): """ Attach client user interface. """ - assert isinstance(detach_other_clients, bool) - self._send_size() - self._send_packet({ - 'cmd': 'start-gui', - 'detach-others': detach_other_clients, - 'color-depth': color_depth, - 'term': os.environ.get('TERM', ''), - 'data': '' - }) + self._send_packet( + { + "cmd": "start-gui", + "detach-others": detach_other_clients, + "color-depth": color_depth, + "term": os.environ.get("TERM", ""), + "data": "", + } + ) with raw_mode(sys.stdin.fileno()): - data_buffer = b'' + data_buffer = b"" stdin_fd = sys.stdin.fileno() socket_fd = self.socket.fileno() current_timeout = INPUT_TIMEOUT # Timeout, used to flush escape sequences. try: + def winch_handler(signum, frame): self._send_size() @@ -94,7 +91,7 @@ def winch_handler(signum, frame): # Received packet from server. data = self.socket.recv(1024) - if data == b'': + if data == b"": # End of file. Connection closed. # Reset terminal o = Vt100_Output.from_pty(sys.stdout) @@ -107,10 +104,10 @@ def winch_handler(signum, frame): else: data_buffer += data - while b'\0' in data_buffer: - pos = data_buffer.index(b'\0') + while b"\0" in data_buffer: + pos = data_buffer.index(b"\0") self._process(data_buffer[:pos]) - data_buffer = data_buffer[pos + 1:] + data_buffer = data_buffer[pos + 1 :] elif stdin_fd in r: # Got user input. @@ -119,7 +116,7 @@ def winch_handler(signum, frame): else: # Timeout. (Tell the server to flush the vt100 Escape.) - self._send_packet({'cmd': 'flush-input'}) + self._send_packet({"cmd": "flush-input"}) current_timeout = 0 finally: signal.signal(signal.SIGWINCH, signal.SIG_IGN) @@ -128,32 +125,32 @@ def _process(self, data_buffer): """ Handle incoming packet from server. """ - packet = json.loads(data_buffer.decode('utf-8')) + packet = json.loads(data_buffer.decode("utf-8")) - if packet['cmd'] == 'out': + if packet["cmd"] == "out": # Call os.write manually. In Python2.6, sys.stdout.write doesn't use UTF-8. - os.write(sys.stdout.fileno(), packet['data'].encode('utf-8')) + os.write(sys.stdout.fileno(), packet["data"].encode("utf-8")) - elif packet['cmd'] == 'suspend': + elif packet["cmd"] == "suspend": # Suspend client process to background. - if hasattr(signal, 'SIGTSTP'): + if hasattr(signal, "SIGTSTP"): os.kill(os.getpid(), signal.SIGTSTP) - elif packet['cmd'] == 'mode': + elif packet["cmd"] == "mode": # Set terminal to raw/cooked. - action = packet['data'] + action = packet["data"] - if action == 'raw': + if action == "raw": cm = raw_mode(sys.stdin.fileno()) cm.__enter__() self._mode_context_managers.append(cm) - elif action == 'cooked': + elif action == "cooked": cm = cooked_mode(sys.stdin.fileno()) cm.__enter__() self._mode_context_managers.append(cm) - elif action == 'restore' and self._mode_context_managers: + elif action == "restore" and self._mode_context_managers: cm = self._mode_context_managers.pop() cm.__exit__() @@ -167,35 +164,31 @@ def _process_stdin(self): # Send input in chunks of 4k. step = 4056 for i in range(0, len(data), step): - self._send_packet({ - 'cmd': 'in', - 'data': data[i:i + step], - }) + self._send_packet( + {"cmd": "in", "data": data[i : i + step],} + ) def _send_packet(self, data): " Send to server. " - data = json.dumps(data).encode('utf-8') + data = json.dumps(data).encode("utf-8") # Be sure that our socket is blocking, otherwise, the send() call could # raise `BlockingIOError` if the buffer is full. self.socket.setblocking(1) - self.socket.send(data + b'\0') + self.socket.send(data + b"\0") def _send_size(self): " Report terminal size to server. " rows, cols = _get_size(sys.stdout.fileno()) - self._send_packet({ - 'cmd': 'size', - 'data': [rows, cols] - }) + self._send_packet({"cmd": "size", "data": [rows, cols]}) def list_clients(): """ List all the servers that are running. """ - p = '%s/pymux.sock.%s.*' % (tempfile.gettempdir(), getpass.getuser()) + p = "%s/pymux.sock.%s.*" % (tempfile.gettempdir(), getpass.getuser()) for path in glob.glob(p): try: yield PosixClient(path) diff --git a/pymux/client/windows.py b/pymux/client/windows.py index f206fe3..de2ef22 100644 --- a/pymux/client/windows.py +++ b/pymux/client/windows.py @@ -1,22 +1,21 @@ -from __future__ import unicode_literals - +import json +import os +import sys from asyncio import ensure_future, get_event_loop from ctypes import byref, windll from ctypes.wintypes import DWORD + from prompt_toolkit.input.win32 import Win32Input from prompt_toolkit.output import ColorDepth from prompt_toolkit.output.win32 import Win32Output from prompt_toolkit.win32_types import STD_OUTPUT_HANDLE -import json -import os -import sys from ..pipes.win32_client import PipeClient from .base import Client __all__ = [ - 'WindowsClient', - 'list_clients', + "WindowsClient", + "list_clients", ] # See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx @@ -28,20 +27,23 @@ class WindowsClient(Client): def __init__(self, pipe_name): self._input = Win32Input() self._hconsole = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) - self._data_buffer = b'' + self._data_buffer = b"" self.pipe = PipeClient(pipe_name) - def attach(self, detach_other_clients=False, color_depth=ColorDepth.DEPTH_8_BIT): - assert isinstance(detach_other_clients, bool) + def attach( + self, detach_other_clients: bool = False, color_depth=ColorDepth.DEPTH_8_BIT + ): self._send_size() - self._send_packet({ - 'cmd': 'start-gui', - 'detach-others': detach_other_clients, - 'color-depth': color_depth, - 'term': os.environ.get('TERM', ''), - 'data': '' - }) + self._send_packet( + { + "cmd": "start-gui", + "detach-others": detach_other_clients, + "color-depth": color_depth, + "term": os.environ.get("TERM", ""), + "data": "", + } + ) f = ensure_future(self._start_reader()) with self._input.attach(self._input_ready): @@ -62,24 +64,26 @@ def _process(self, data_buffer): """ packet = json.loads(data_buffer) - if packet['cmd'] == 'out': + if packet["cmd"] == "out": # Call os.write manually. In Python2.6, sys.stdout.write doesn't use UTF-8. original_mode = DWORD(0) windll.kernel32.GetConsoleMode(self._hconsole, byref(original_mode)) - windll.kernel32.SetConsoleMode(self._hconsole, DWORD( - ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) + windll.kernel32.SetConsoleMode( + self._hconsole, + DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING), + ) try: - os.write(sys.stdout.fileno(), packet['data'].encode('utf-8')) + os.write(sys.stdout.fileno(), packet["data"].encode("utf-8")) finally: windll.kernel32.SetConsoleMode(self._hconsole, original_mode) - elif packet['cmd'] == 'suspend': + elif packet["cmd"] == "suspend": # Suspend client process to background. pass - elif packet['cmd'] == 'mode': + elif packet["cmd"] == "mode": pass # # Set terminal to raw/cooked. @@ -102,10 +106,9 @@ def _process(self, data_buffer): def _input_ready(self): keys = self._input.read_keys() if keys: - self._send_packet({ - 'cmd': 'in', - 'data': ''.join(key_press.data for key_press in keys), - }) + self._send_packet( + {"cmd": "in", "data": "".join(key_press.data for key_press in keys),} + ) def _send_packet(self, data): " Send to server. " @@ -117,10 +120,7 @@ def _send_size(self): output = Win32Output(sys.stdout) rows, cols = output.get_size() - self._send_packet({ - 'cmd': 'size', - 'data': [rows, cols] - }) + self._send_packet({"cmd": "size", "data": [rows, cols]}) def list_clients(): diff --git a/pymux/commands/aliases.py b/pymux/commands/aliases.py index 9984641..91f2359 100644 --- a/pymux/commands/aliases.py +++ b/pymux/commands/aliases.py @@ -2,47 +2,43 @@ Aliases for all commands. (On purpose kept compatible with tmux.) """ -from __future__ import unicode_literals - -__all__ = ( - 'ALIASES', -) +__all__ = ("ALIASES",) ALIASES = { - 'bind': 'bind-key', - 'breakp': 'break-pane', - 'clearhist': 'clear-history', - 'confirm': 'confirm-before', - 'detach': 'detach-client', - 'display': 'display-message', - 'displayp': 'display-panes', - 'killp': 'kill-pane', - 'killw': 'kill-window', - 'last': 'last-window', - 'lastp': 'last-pane', - 'lextl': 'next-layout', - 'lsk': 'list-keys', - 'lsp': 'list-panes', - 'movew': 'move-window', - 'neww': 'new-window', - 'next': 'next-window', - 'pasteb': 'paste-buffer', - 'prev': 'previous-window', - 'prevl': 'previous-layout', - 'rename': 'rename-session', - 'renamew': 'rename-window', - 'resizep': 'resize-pane', - 'rotatew': 'rotate-window', - 'selectl': 'select-layout', - 'selectp': 'select-pane', - 'selectw': 'select-window', - 'send': 'send-keys', - 'set': 'set-option', - 'setw': 'set-window-option', - 'source': 'source-file', - 'splitw': 'split-window', - 'suspendc': 'suspend-client', - 'swapp': 'swap-pane', - 'unbind': 'unbind-key', + "bind": "bind-key", + "breakp": "break-pane", + "clearhist": "clear-history", + "confirm": "confirm-before", + "detach": "detach-client", + "display": "display-message", + "displayp": "display-panes", + "killp": "kill-pane", + "killw": "kill-window", + "last": "last-window", + "lastp": "last-pane", + "lextl": "next-layout", + "lsk": "list-keys", + "lsp": "list-panes", + "movew": "move-window", + "neww": "new-window", + "next": "next-window", + "pasteb": "paste-buffer", + "prev": "previous-window", + "prevl": "previous-layout", + "rename": "rename-session", + "renamew": "rename-window", + "resizep": "resize-pane", + "rotatew": "rotate-window", + "selectl": "select-layout", + "selectp": "select-pane", + "selectw": "select-window", + "send": "send-keys", + "set": "set-option", + "setw": "set-window-option", + "source": "source-file", + "splitw": "split-window", + "suspendc": "suspend-client", + "swapp": "swap-pane", + "unbind": "unbind-key", } diff --git a/pymux/commands/commands.py b/pymux/commands/commands.py index 6169aad..ef93a9d 100644 --- a/pymux/commands/commands.py +++ b/pymux/commands/commands.py @@ -1,29 +1,29 @@ -from __future__ import unicode_literals -import docopt import os import re import shlex -import six +import docopt from prompt_toolkit.application.current import get_app from prompt_toolkit.document import Document from prompt_toolkit.key_binding.vi_state import InputMode - from pymux.arrangement import LayoutTypes from pymux.commands.aliases import ALIASES from pymux.commands.utils import wrap_argument from pymux.format import format_pymux_string -from pymux.key_mappings import pymux_key_to_prompt_toolkit_key_sequence, prompt_toolkit_key_to_vt100_key -from pymux.layout import focus_right, focus_left, focus_up, focus_down +from pymux.key_mappings import ( + prompt_toolkit_key_to_vt100_key, + pymux_key_to_prompt_toolkit_key_sequence, +) +from pymux.layout import focus_down, focus_left, focus_right, focus_up from pymux.log import logger from pymux.options import SetOptionError __all__ = ( - 'call_command_handler', - 'get_documentation_for_command', - 'get_option_flags_for_command', - 'handle_command', - 'has_command_handler', + "call_command_handler", + "get_documentation_for_command", + "get_option_flags_for_command", + "handle_command", + "has_command_handler", ) COMMANDS_TO_HANDLERS = {} # Global mapping of pymux commands to their handlers. @@ -31,15 +31,16 @@ COMMANDS_TO_OPTION_FLAGS = {} -def has_command_handler(command): +def has_command_handler(command: str) -> bool: return command in COMMANDS_TO_HANDLERS def get_documentation_for_command(command): - """ Return the help text for this command, or None if the command is not - known. """ + """ + Return the help text for this command, or None if the command is not known. + """ if command in COMMANDS_TO_HELP: - return 'Usage: %s %s' % (command, COMMANDS_TO_HELP.get(command, '')) + return "Usage: %s %s" % (command, COMMANDS_TO_HELP.get(command, "")) def get_option_flags_for_command(command): @@ -47,46 +48,36 @@ def get_option_flags_for_command(command): return COMMANDS_TO_OPTION_FLAGS.get(command, []) -def handle_command(pymux, input_string): +def handle_command(pymux, input_string: str): """ Handle command. """ - assert isinstance(input_string, six.text_type) - input_string = input_string.strip() - logger.info('handle command: %s %s.', input_string, type(input_string)) + logger.info("handle command: %s %s.", input_string, type(input_string)) - if input_string and not input_string.startswith('#'): # Ignore comments. + if input_string and not input_string.startswith("#"): # Ignore comments. try: - if six.PY2: - # In Python2.6, shlex doesn't work with unicode input at all. - # In Python2.7, shlex tries to encode using ASCII. - parts = shlex.split(input_string.encode('utf-8')) - parts = [p.decode('utf-8') for p in parts] - else: - parts = shlex.split(input_string) + parts = shlex.split(input_string) except ValueError as e: # E.g. missing closing quote. - pymux.show_message('Invalid command %s: %s' % (input_string, e)) + pymux.show_message("Invalid command %s: %s" % (input_string, e)) else: call_command_handler(parts[0], pymux, parts[1:]) -def call_command_handler(command, pymux, arguments): +def call_command_handler(command, pymux, arguments: list): """ Execute command. :param arguments: List of options. """ - assert isinstance(arguments, list) - # Resolve aliases. command = ALIASES.get(command, command) try: handler = COMMANDS_TO_HANDLERS[command] except KeyError: - pymux.show_message('Invalid command: %s' % (command,)) + pymux.show_message("Invalid command: %s" % (command,)) else: try: handler(pymux, arguments) @@ -94,7 +85,7 @@ def call_command_handler(command, pymux, arguments): pymux.show_message(e.message) -def cmd(name, options=''): +def cmd(name: str, options: str = ""): """ Decorator for all commands. @@ -104,7 +95,7 @@ def cmd(name, options=''): # Validate options. if options: try: - docopt.docopt('Usage:\n %s %s' % (name, options, ), []) + docopt.docopt("Usage:\n %s %s" % (name, options,), []) except SystemExit: pass @@ -112,37 +103,26 @@ def decorator(func): def command_wrapper(pymux, arguments): # Hack to make the 'bind-key' option work. # (bind-key expects a variable number of arguments.) - if name == 'bind-key' and '--' not in arguments: + if name == "bind-key" and "--" not in arguments: # Insert a double dash after the first non-option. for i, p in enumerate(arguments): - if not p.startswith('-'): - arguments.insert(i + 1, '--') + if not p.startswith("-"): + arguments.insert(i + 1, "--") break # Parse options. try: - # Python 2 workaround: pass bytes to docopt. - # From the following, only the bytes version returns the right - # output in Python 2: - # docopt.docopt('Usage:\n app ...', [b'a', b'b']) - # docopt.docopt('Usage:\n app ...', [u'a', u'b']) - # https://github.com/docopt/docopt/issues/30 - # (Not sure how reliable this is...) - if six.PY2: - arguments = [a.encode('utf-8') for a in arguments] - received_options = docopt.docopt( - 'Usage:\n %s %s' % (name, options), - arguments, - help=False) # Don't interpret the '-h' option as help. + "Usage:\n %s %s" % (name, options), arguments, help=False + ) # Don't interpret the '-h' option as help. # Make sure that all the received options from docopt are # unicode objects. (Docopt returns 'str' for Python2.) for k, v in received_options.items(): - if isinstance(v, six.binary_type): - received_options[k] = v.decode('utf-8') + if isinstance(v, bytes): + received_options[k] = v.decode("utf-8") except SystemExit: - raise CommandException('Usage: %s %s' % (name, options)) + raise CommandException("Usage: %s %s" % (name, options)) # Call handler. func(pymux, received_options) @@ -154,41 +134,44 @@ def command_wrapper(pymux, arguments): COMMANDS_TO_HELP[name] = options # Get list of option flags. - flags = re.findall(r'-[a-zA-Z0-9]\b', options) + flags = re.findall(r"-[a-zA-Z0-9]\b", options) COMMANDS_TO_OPTION_FLAGS[name] = flags return func + return decorator class CommandException(Exception): " When raised from a command handler, this message will be shown. " + def __init__(self, message): self.message = message + # # The actual commands. # -@cmd('break-pane', options='[-d]') +@cmd("break-pane", options="[-d]") def break_pane(pymux, variables): - dont_focus_window = variables['-d'] + dont_focus_window = variables["-d"] pymux.arrangement.break_pane(set_active=not dont_focus_window) pymux.invalidate() -@cmd('select-pane', options='(-L|-R|-U|-D|-t )') +@cmd("select-pane", options="(-L|-R|-U|-D|-t )") def select_pane(pymux, variables): - if variables['-t']: - pane_id = variables[''] + if variables["-t"]: + pane_id = variables[""] w = pymux.arrangement.get_active_window() - if pane_id == ':.+': + if pane_id == ":.+": w.focus_next() - elif pane_id == ':.-': + elif pane_id == ":.-": w.focus_previous() else: # Select pane by index. @@ -196,28 +179,32 @@ def select_pane(pymux, variables): pane_id = int(pane_id[1:]) w.active_pane = w.panes[pane_id] except (IndexError, ValueError): - raise CommandException('Invalid pane.') + raise CommandException("Invalid pane.") else: - if variables['-L']: h = focus_left - if variables['-U']: h = focus_up - if variables['-D']: h = focus_down - if variables['-R']: h = focus_right + if variables["-L"]: + h = focus_left + if variables["-U"]: + h = focus_up + if variables["-D"]: + h = focus_down + if variables["-R"]: + h = focus_right h(pymux) -@cmd('select-window', options='(-t )') +@cmd("select-window", options="(-t )") def select_window(pymux, variables): """ Select a window. E.g: select-window -t :3 """ - window_id = variables[''] + window_id = variables[""] def invalid_window(): - raise CommandException('Invalid window: %s' % window_id) + raise CommandException("Invalid window: %s" % window_id) - if window_id.startswith(':'): + if window_id.startswith(":"): try: number = int(window_id[1:]) except ValueError: @@ -232,16 +219,16 @@ def invalid_window(): invalid_window() -@cmd('move-window', options='(-t )') +@cmd("move-window", options="(-t )") def move_window(pymux, variables): """ Move window to a new index. """ - dst_window = variables[''] + dst_window = variables[""] try: new_index = int(dst_window) except ValueError: - raise CommandException('Invalid window index: %r' % (dst_window, )) + raise CommandException("Invalid window index: %r" % (dst_window,)) # Check first whether the index was not yet taken. if pymux.arrangement.get_window_by_index(new_index): @@ -252,33 +239,33 @@ def move_window(pymux, variables): pymux.arrangement.move_window(w, new_index) -@cmd('rotate-window', options='[-D|-U]') +@cmd("rotate-window", options="[-D|-U]") def rotate_window(pymux, variables): - if variables['-D']: + if variables["-D"]: pymux.arrangement.rotate_window(count=-1) else: pymux.arrangement.rotate_window() -@cmd('swap-pane', options='(-D|-U)') +@cmd("swap-pane", options="(-D|-U)") def swap_pane(pymux, variables): - pymux.arrangement.get_active_window().rotate(with_pane_after_only=variables['-U']) + pymux.arrangement.get_active_window().rotate(with_pane_after_only=variables["-U"]) -@cmd('kill-pane') +@cmd("kill-pane") def kill_pane(pymux, variables): pane = pymux.arrangement.get_active_pane() pymux.kill_pane(pane) -@cmd('kill-window') +@cmd("kill-window") def kill_window(pymux, variables): " Kill all panes in the current window. " for pane in pymux.arrangement.get_active_window().panes: pymux.kill_pane(pane) -@cmd('suspend-client') +@cmd("suspend-client") def suspend_client(pymux, variables): connection = pymux.get_connection() @@ -286,14 +273,14 @@ def suspend_client(pymux, variables): connection.suspend_client_to_background() -@cmd('clock-mode') +@cmd("clock-mode") def clock_mode(pymux, variables): pane = pymux.arrangement.get_active_pane() if pane: pane.clock_mode = not pane.clock_mode -@cmd('last-pane') +@cmd("last-pane") def last_pane(pymux, variables): w = pymux.arrangement.get_active_window() prev_active_pane = w.previous_active_pane @@ -302,7 +289,7 @@ def last_pane(pymux, variables): w.active_pane = prev_active_pane -@cmd('next-layout') +@cmd("next-layout") def next_layout(pymux, variables): " Select next layout. " pane = pymux.arrangement.get_active_window() @@ -310,7 +297,7 @@ def next_layout(pymux, variables): pane.select_next_layout() -@cmd('previous-layout') +@cmd("previous-layout") def previous_layout(pymux, variables): " Select previous layout. " pane = pymux.arrangement.get_active_window() @@ -318,22 +305,22 @@ def previous_layout(pymux, variables): pane.select_previous_layout() -@cmd('new-window', options='[(-n )] [(-c )] []') +@cmd("new-window", options="[(-n )] [(-c )] []") def new_window(pymux, variables): - executable = variables[''] - start_directory = variables[''] - name = variables[''] + executable = variables[""] + start_directory = variables[""] + name = variables[""] pymux.create_window(executable, start_directory=start_directory, name=name) -@cmd('next-window') +@cmd("next-window") def next_window(pymux, variables): " Focus the next window. " pymux.arrangement.focus_next_window() -@cmd('last-window') +@cmd("last-window") def _(pymux, variables): " Go to previous active window. " w = pymux.arrangement.get_previous_active_window() @@ -342,71 +329,74 @@ def _(pymux, variables): pymux.arrangement.set_active_window(w) -@cmd('previous-window') +@cmd("previous-window") def previous_window(pymux, variables): " Focus the previous window. " pymux.arrangement.focus_previous_window() -@cmd('select-layout', options='') +@cmd("select-layout", options="") def select_layout(pymux, variables): - layout_type = variables[''] + layout_type = variables[""] if layout_type in LayoutTypes._ALL: pymux.arrangement.get_active_window().select_layout(layout_type) else: - raise CommandException('Invalid layout type.') + raise CommandException("Invalid layout type.") -@cmd('rename-window', options='') +@cmd("rename-window", options="") def rename_window(pymux, variables): """ Rename the active window. """ - pymux.arrangement.get_active_window().chosen_name = variables[''] + pymux.arrangement.get_active_window().chosen_name = variables[""] -@cmd('rename-pane', options='') +@cmd("rename-pane", options="") def rename_pane(pymux, variables): """ Rename the active pane. """ - pymux.arrangement.get_active_pane().chosen_name = variables[''] + pymux.arrangement.get_active_pane().chosen_name = variables[""] -@cmd('rename-session', options='') +@cmd("rename-session", options="") def rename_session(pymux, variables): """ Rename this session. """ - pymux.session_name = variables[''] + pymux.session_name = variables[""] -@cmd('split-window', options='[-v|-h] [(-c )] []') +@cmd("split-window", options="[-v|-h] [(-c )] []") def split_window(pymux, variables): """ Split horizontally or vertically. """ - executable = variables[''] - start_directory = variables[''] + executable = variables[""] + start_directory = variables[""] # The tmux definition of horizontal is the opposite of prompt_toolkit. - pymux.add_process(executable, vsplit=variables['-h'], - start_directory=start_directory) + pymux.add_process( + executable, vsplit=variables["-h"], start_directory=start_directory + ) -@cmd('resize-pane', options="[(-L )] [(-U )] [(-D )] [(-R )] [-Z]") +@cmd( + "resize-pane", options="[(-L )] [(-U )] [(-D )] [(-R )] [-Z]" +) def resize_pane(pymux, variables): """ Resize/zoom the active pane. """ try: - left = int(variables[''] or 0) - right = int(variables[''] or 0) - up = int(variables[''] or 0) - down = int(variables[''] or 0) + left = int(variables[""] or 0) + right = int(variables[""] or 0) + up = int(variables[""] or 0) + down = int(variables[""] or 0) except ValueError: - raise CommandException('Expecting an integer.') + raise CommandException("Expecting an integer.") w = pymux.arrangement.get_active_window() @@ -414,11 +404,11 @@ def resize_pane(pymux, variables): w.change_size_for_active_pane(up=up, right=right, down=down, left=left) # Zoom in/out. - if variables['-Z']: + if variables["-Z"]: w.zoom = not w.zoom -@cmd('detach-client') +@cmd("detach-client") def detach_client(pymux, variables): """ Detach client. @@ -426,35 +416,38 @@ def detach_client(pymux, variables): pymux.detach_client(get_app()) -@cmd('confirm-before', options='[(-p )] ') +@cmd("confirm-before", options="[(-p )] ") def confirm_before(pymux, variables): client_state = pymux.get_client_state() - client_state.confirm_text = variables[''] or '' - client_state.confirm_command = variables[''] + client_state.confirm_text = variables[""] or "" + client_state.confirm_command = variables[""] -@cmd('command-prompt', options='[(-p )] [(-I )] []') +@cmd("command-prompt", options="[(-p )] [(-I )] []") def command_prompt(pymux, variables): """ Enter command prompt. """ client_state = pymux.get_client_state() - if variables['']: + if variables[""]: # When a 'command' has been given. - client_state.prompt_text = variables[''] or '(%s)' % variables[''].split()[0] - client_state.prompt_command = variables[''] + client_state.prompt_text = ( + variables[""] or "(%s)" % variables[""].split()[0] + ) + client_state.prompt_command = variables[""] client_state.prompt_mode = True - client_state.prompt_buffer.reset(Document( - format_pymux_string(pymux, variables[''] or ''))) + client_state.prompt_buffer.reset( + Document(format_pymux_string(pymux, variables[""] or "")) + ) get_app().layout.focus(client_state.prompt_buffer) else: # Show the ':' prompt. - client_state.prompt_text = '' - client_state.prompt_command = '' + client_state.prompt_text = "" + client_state.prompt_command = "" get_app().layout.focus(client_state.command_buffer) @@ -462,7 +455,7 @@ def command_prompt(pymux, variables): get_app().vi_state.input_mode = InputMode.INSERT -@cmd('send-prefix') +@cmd("send-prefix") def send_prefix(pymux, variables): """ Send prefix to active pane. @@ -474,37 +467,37 @@ def send_prefix(pymux, variables): process.write_input(vt100_data) -@cmd('bind-key', options='[-n] [--] [...]') +@cmd("bind-key", options="[-n] [--] [...]") def bind_key(pymux, variables): """ Bind a key sequence. -n: Not necessary to use the prefix. """ - key = variables[''] - command = variables[''] - arguments = variables[''] - needs_prefix = not variables['-n'] + key = variables[""] + command = variables[""] + arguments = variables[""] + needs_prefix = not variables["-n"] try: pymux.key_bindings_manager.add_custom_binding( - key, command, arguments, needs_prefix=needs_prefix) + key, command, arguments, needs_prefix=needs_prefix + ) except ValueError: - raise CommandException('Invalid key: %r' % (key, )) + raise CommandException("Invalid key: %r" % (key,)) -@cmd('unbind-key', options='[-n] ') +@cmd("unbind-key", options="[-n] ") def unbind_key(pymux, variables): """ Remove key binding. """ - key = variables[''] - needs_prefix = not variables['-n'] + key = variables[""] + needs_prefix = not variables["-n"] - pymux.key_bindings_manager.remove_custom_binding( - key, needs_prefix=needs_prefix) + pymux.key_bindings_manager.remove_custom_binding(key, needs_prefix=needs_prefix) -@cmd('send-keys', options='...') +@cmd("send-keys", options="...") def send_keys(pymux, variables): """ Send key strokes to the active process. @@ -512,33 +505,33 @@ def send_keys(pymux, variables): pane = pymux.arrangement.get_active_pane() if pane.display_scroll_buffer: - raise CommandException('Cannot send keys. Pane is in copy mode.') + raise CommandException("Cannot send keys. Pane is in copy mode.") - for key in variables['']: + for key in variables[""]: # Translate key from pymux key to prompt_toolkit key. try: keys_sequence = pymux_key_to_prompt_toolkit_key_sequence(key) except ValueError: - raise CommandException('Invalid key: %r' % (key, )) + raise CommandException("Invalid key: %r" % (key,)) # Translate prompt_toolkit key to VT100 key. for k in keys_sequence: pane.process.write_key(k) -@cmd('copy-mode', options='[-u]') +@cmd("copy-mode", options="[-u]") def copy_mode(pymux, variables): """ Enter copy mode. """ - go_up = variables['-u'] # Go in copy mode and page-up directly. + go_up = variables["-u"] # Go in copy mode and page-up directly. # TODO: handle '-u' pane = pymux.arrangement.get_active_pane() pane.enter_copy_mode() -@cmd('paste-buffer') +@cmd("paste-buffer") def paste_buffer(pymux, variables): """ Paste clipboard content into buffer. @@ -547,25 +540,25 @@ def paste_buffer(pymux, variables): pane.process.write_input(get_app().clipboard.get_data().text, paste=True) -@cmd('source-file', options='') +@cmd("source-file", options="") def source_file(pymux, variables): """ Source configuration file. """ - filename = os.path.expanduser(variables['']) + filename = os.path.expanduser(variables[""]) try: - with open(filename, 'rb') as f: + with open(filename, "rb") as f: for line in f: - line = line.decode('utf-8') + line = line.decode("utf-8") handle_command(pymux, line) except IOError as e: - raise CommandException('IOError: %s' % (e, )) + raise CommandException("IOError: %s" % (e,)) -@cmd('set-option', options='