diff --git a/kitty/rc/ls.py b/kitty/rc/ls.py index 5880a21fd90..174355911ca 100644 --- a/kitty/rc/ls.py +++ b/kitty/rc/ls.py @@ -2,12 +2,16 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal import json +import os from collections.abc import Callable -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Sequence from kitty.constants import appname from .base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Tab, Window +from ..boss import OSWindowDict +from ..child import ProcessDesc +from ..launch import is_excluded_env_var if TYPE_CHECKING: from kitty.cli_stub import LSRCOptions as CLIOptions @@ -19,6 +23,7 @@ class LS(RemoteCommand): match/str: Window to change colors in match_tab/str: Tab to change colors in self/bool: Boolean indicating whether to list only the window the command is run in + output_format/str: Output in json or session format ''' short_desc = 'List tabs/windows' @@ -41,6 +46,13 @@ class LS(RemoteCommand): --self type=bool-set Only list the window this command is run in. + + +--output-format +type=choices +choices=json,session +default=json +Output in json or session format ''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t', 1) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: @@ -49,6 +61,7 @@ def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: Arg def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: tab_filter: Callable[[Tab], bool] | None = None window_filter: Callable[[Window], bool] | None = None + output_session: bool = False if payload_get('self'): def wf(w: Window) -> bool: @@ -59,26 +72,32 @@ def wf(w: Window) -> bool: def wf(w: Window) -> bool: return w.id in window_ids window_filter = wf - data = list(boss.list_os_windows(window, tab_filter, window_filter)) - if not payload_get('all_env_vars'): - all_env_blocks: list[dict[str, str]] = [] - common_env_vars: set[tuple[str, str]] = set() - for osw in data: - for tab in osw.get('tabs', ()): - for w in tab.get('windows', ()): - env: dict[str, str] = w.get('env', {}) - frozen_env = set(env.items()) - if all_env_blocks: - common_env_vars &= frozen_env - else: - common_env_vars = frozen_env - all_env_blocks.append(env) - if common_env_vars and len(all_env_blocks) > 1: - remove_env_vars = {k for k, v in common_env_vars} - for env in all_env_blocks: - for r in remove_env_vars: - env.pop(r, None) - return json.dumps(data, indent=2, sort_keys=True) + elif payload_get('output_format') == 'session': + output_session = True + + if not output_session: + data = list(boss.list_os_windows(window, tab_filter, window_filter)) + if not payload_get('all_env_vars'): + all_env_blocks: list[dict[str, str]] = [] + common_env_vars: set[tuple[str, str]] = set() + for osw in data: + for tab in osw.get('tabs', ()): + for w in tab.get('windows', ()): + env: dict[str, str] = w.get('env', {}) + frozen_env = set(env.items()) + if all_env_blocks: + common_env_vars &= frozen_env + else: + common_env_vars = frozen_env + all_env_blocks.append(env) + if common_env_vars and len(all_env_blocks) > 1: + remove_env_vars = {k for k, v in common_env_vars} + for env in all_env_blocks: + for r in remove_env_vars: + env.pop(r, None) + return json.dumps(data, indent=2, sort_keys=True) + else: + return "\n".join(boss.serialize_state_as_session()) ls = LS() diff --git a/kitty/tabs.py b/kitty/tabs.py index d79b2939937..e5d25e5fccf 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal +import json import os import re import stat @@ -304,6 +305,7 @@ def serialize_state_as_session(self) -> list[str]: if lc: gw.append(shlex.join(lc)) launch_cmds.extend(gw) + launch_cmds.append(f'set_layout_state {json.dumps(self.serialize_state()["layout_state"])}\n') return (ans + launch_cmds) if launch_cmds else [] def active_window_changed(self) -> None: @@ -1170,7 +1172,7 @@ def list_tabs( 'is_active': tab is active_tab, 'title': tab.name or tab.title, 'layout': str(tab.current_layout.name), - 'layout_state': tab.current_layout.layout_state(), + 'layout_state': tab.current_layout.serialize(tab.windows), 'layout_opts': tab.current_layout.layout_opts.serialized(), 'enabled_layouts': tab.enabled_layouts, 'windows': windows, diff --git a/kitty/window.py b/kitty/window.py index fb64eeb0786..6e622491f42 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -233,6 +233,9 @@ class WindowDict(TypedDict): id: int is_focused: bool is_active: bool + is_actions_on_close: bool + is_actions_on_focus_change: bool + is_actions_on_removal: bool title: str pid: int | None cwd: str @@ -821,6 +824,9 @@ def as_dict(self, is_focused: bool = False, is_self: bool = False, is_active: bo 'id': self.id, 'is_focused': is_focused, 'is_active': is_active, + 'is_actions_on_close': self in self.actions_on_close, + 'is_actions_on_focus_change': self in self.actions_on_focus_change, + 'is_actions_on_removal': self in self.actions_on_removal, 'title': self.title, 'pid': self.child.pid, 'cwd': self.child.current_cwd or self.child.cwd, @@ -1978,6 +1984,8 @@ def as_launch_command(self, is_overlay: bool = False) -> list[str]: ans.append('--hold-after-ssh') for k, v in self.user_vars.items(): ans.append(f'--var={k}={v}') + if 'kitty_serialize_window_id' not in self.user_vars: + ans.append(f'--var=kitty_serialize_window_id={self.id}') ans.extend(self.padding.as_launch_args()) ans.extend(self.margin.as_launch_args('margin')) if self.override_title: