Proper handling of async responses to peers

This commit is contained in:
Kovid Goyal 2021-10-31 21:09:24 +05:30
parent 218582ced8
commit d53d92b890
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 23 additions and 13 deletions

View File

@ -53,7 +53,7 @@ from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import ( from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
) )
from .types import _T, SingleKey, WindowSystemMouseEvent, ac from .types import _T, AsyncResponse, SingleKey, WindowSystemMouseEvent, ac
from .typing import PopenType, TypedDict from .typing import PopenType, TypedDict
from .utils import ( from .utils import (
func_name, get_editor, get_new_os_window_size, get_primary_selection, func_name, get_editor, get_new_os_window_size, get_primary_selection,
@ -435,7 +435,7 @@ class Boss:
self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen)
self.window_id_map[window.id] = window self.window_id_map[window.id] = window
def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> Optional[Dict[str, Any]]: def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> Union[Dict[str, Any], None, AsyncResponse]:
from .remote_control import handle_cmd from .remote_control import handle_cmd
response = None response = None
window = window or None window = window or None
@ -491,7 +491,7 @@ class Boss:
tb = traceback.format_exc() tb = traceback.format_exc()
self.show_error(_('remote_control mapping failed'), tb) self.show_error(_('remote_control mapping failed'), tb)
def peer_message_received(self, msg_bytes: bytes, peer_id: int) -> Optional[bytes]: def peer_message_received(self, msg_bytes: bytes, peer_id: int) -> Union[bytes, bool, None]:
cmd_prefix = b'\x1bP@kitty-cmd' cmd_prefix = b'\x1bP@kitty-cmd'
terminator = b'\x1b\\' terminator = b'\x1b\\'
if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator): if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator):
@ -499,6 +499,8 @@ class Boss:
response = self._handle_remote_command(cmd, peer_id=peer_id) response = self._handle_remote_command(cmd, peer_id=peer_id)
if response is None: if response is None:
return None return None
if isinstance(response, AsyncResponse):
return True
from kitty.remote_control import encode_response_for_peer from kitty.remote_control import encode_response_for_peer
return encode_response_for_peer(response) return encode_response_for_peer(response)
@ -528,8 +530,7 @@ class Boss:
def handle_remote_cmd(self, cmd: str, window: Optional[Window] = None) -> None: def handle_remote_cmd(self, cmd: str, window: Optional[Window] = None) -> None:
response = self._handle_remote_command(cmd, window) response = self._handle_remote_command(cmd, window)
if response is not None: if response is not None and not isinstance(response, AsyncResponse) and window is not None:
if window is not None:
window.send_cmd_response(response) window.send_cmd_response(response)
def _cleanup_tab_after_window_removal(self, src_tab: Tab) -> None: def _cleanup_tab_after_window_removal(self, src_tab: Tab) -> None:

View File

@ -424,7 +424,7 @@ parse_input(ChildMonitor *self) {
} }
if (resp) { if (resp) {
if (PyBytes_Check(resp)) send_response_to_peer(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp)); if (PyBytes_Check(resp)) send_response_to_peer(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
else send_response_to_peer(msg->peer_id, NULL, 0); else if (resp == Py_None) send_response_to_peer(msg->peer_id, NULL, 0);
Py_CLEAR(resp); Py_CLEAR(resp);
} else send_response_to_peer(msg->peer_id, NULL, 0); } else send_response_to_peer(msg->peer_id, NULL, 0);
} }

View File

@ -10,6 +10,7 @@ from typing import (
from kitty.cli import get_defaults_from_seq, parse_args, parse_option_spec from kitty.cli import get_defaults_from_seq, parse_args, parse_option_spec
from kitty.cli_stub import RCOptions as R from kitty.cli_stub import RCOptions as R
from kitty.constants import appname, list_kitty_resources, running_in_kitty from kitty.constants import appname, list_kitty_resources, running_in_kitty
from kitty.types import AsyncResponse
if TYPE_CHECKING: if TYPE_CHECKING:
from kitty.boss import Boss as B from kitty.boss import Boss as B
@ -64,7 +65,7 @@ class PayloadGetter:
no_response = NoResponse() no_response = NoResponse()
payload_get = object() payload_get = object()
ResponseType = Union[bool, str, None, NoResponse] ResponseType = Union[bool, str, None, NoResponse, AsyncResponse]
CmdReturnType = Union[Dict[str, Any], List[Any], Tuple[Any, ...], str, int, float, bool] CmdReturnType = Union[Dict[str, Any], List[Any], Tuple[Any, ...], str, int, float, bool]
CmdGenerator = Iterator[CmdReturnType] CmdGenerator = Iterator[CmdReturnType]
PayloadType = Optional[Union[CmdReturnType, CmdGenerator]] PayloadType = Optional[Union[CmdReturnType, CmdGenerator]]

View File

@ -3,9 +3,11 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from kitty.types import AsyncResponse
from .base import ( from .base import (
MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
RemoteCommand, ResponseType, Window, no_response RemoteCommand, ResponseType, Window
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -70,7 +72,7 @@ Exclude the currently active window from the list of windows to pick
wids = set() wids = set()
boss.visual_window_select_action(tab, callback, payload_get('title') or 'Choose window', only_window_ids=wids) boss.visual_window_select_action(tab, callback, payload_get('title') or 'Choose window', only_window_ids=wids)
break break
return no_response return AsyncResponse()
def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None: def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None:
boss.cancel_current_visual_select() boss.cancel_current_visual_select()

View File

@ -6,9 +6,9 @@ import os
import re import re
import sys import sys
import types import types
from time import monotonic
from contextlib import suppress from contextlib import suppress
from functools import partial from functools import partial
from time import monotonic
from typing import ( from typing import (
Any, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast Any, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
) )
@ -21,10 +21,10 @@ from .rc.base import (
NoResponse, ParsingOfArgsFailed, PayloadGetter, all_command_names, NoResponse, ParsingOfArgsFailed, PayloadGetter, all_command_names,
command_for_name, parse_subcommand_cli command_for_name, parse_subcommand_cli
) )
from .types import AsyncResponse
from .typing import BossType, WindowType from .typing import BossType, WindowType
from .utils import TTYIO, parse_address_spec from .utils import TTYIO, parse_address_spec
active_async_requests: Dict[str, float] = {} active_async_requests: Dict[str, float] = {}
@ -33,7 +33,7 @@ def encode_response_for_peer(response: Any) -> bytes:
return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\' return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\'
def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str, peer_id: int) -> Optional[Dict[str, Any]]: def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str, peer_id: int) -> Union[Dict[str, Any], None, AsyncResponse]:
cmd = json.loads(serialized_cmd) cmd = json.loads(serialized_cmd)
v = cmd['version'] v = cmd['version']
no_response = cmd.get('no_response', False) no_response = cmd.get('no_response', False)
@ -63,6 +63,8 @@ def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str
raise raise
if isinstance(ans, NoResponse): if isinstance(ans, NoResponse):
return None return None
if isinstance(ans, AsyncResponse):
return ans
response: Dict[str, Any] = {'ok': True} response: Dict[str, Any] = {'ok': True}
if ans is not None: if ans is not None:
response['data'] = ans response['data'] = ans

View File

@ -74,6 +74,10 @@ class WindowSystemMouseEvent(NamedTuple):
ConvertibleToNumbers = Union[str, bytes, int, float] ConvertibleToNumbers = Union[str, bytes, int, float]
class AsyncResponse:
pass
if TYPE_CHECKING: if TYPE_CHECKING:
class RunOnce(Generic[_T]): class RunOnce(Generic[_T]):