kitten @ ls: Allow limiting output to matched windows/tabs

Fixes #6520
This commit is contained in:
Kovid Goyal 2023-08-03 11:53:17 +05:30
parent 9678e9fa1b
commit cade8efc25
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 78 additions and 39 deletions

View file

@ -45,6 +45,8 @@ Detailed list of changes
- kitten @ ls: Add user variables set on windows to the output (:iss:`6502`)
- kitten @ ls: Allow limiting output to matched windows/tabs (:iss:`6520`)
- X11: Print an error to STDERR instead of refusing to start when the user sets a custom window icon larger than 128128 (:iss:`6507`)
0.29.2 [2023-07-27]

View file

@ -428,20 +428,26 @@ def add_os_window(
self.os_window_map[os_window_id] = tm
return os_window_id
def list_os_windows(self, self_window: Optional[Window] = None) -> Iterator[OSWindowDict]:
def list_os_windows(
self, self_window: Optional[Window] = None,
tab_filter: Optional[Callable[[Tab], bool]] = None,
window_filter: Optional[Callable[[Window], bool]] = None
) -> Iterator[OSWindowDict]:
with cached_process_data():
active_tab_manager = self.active_tab_manager
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'platform_window_id': platform_window_id(os_window_id),
'is_active': tm is active_tab_manager,
'is_focused': current_focused_os_window_id() == os_window_id,
'last_focused': os_window_id == last_focused_os_window_id(),
'tabs': list(tm.list_tabs(self_window)),
'wm_class': tm.wm_class,
'wm_name': tm.wm_name
}
tabs = list(tm.list_tabs(self_window, tab_filter, window_filter))
if tabs:
yield {
'id': os_window_id,
'platform_window_id': platform_window_id(os_window_id),
'is_active': tm is active_tab_manager,
'is_focused': current_focused_os_window_id() == os_window_id,
'last_focused': os_window_id == last_focused_os_window_id(),
'tabs': tabs,
'wm_class': tm.wm_class,
'wm_name': tm.wm_name
}
@property
def all_tab_managers(self) -> Iterator[TabManager]:

View file

@ -13,11 +13,11 @@
if TYPE_CHECKING:
from kitty.boss import Boss as B
from kitty.tabs import Tab
from kitty.tabs import Tab as T
from kitty.window import Window as W
Window = W
Boss = B
Tab
Tab = T
else:
Boss = Window = Tab = None
RCOptions = R

View file

@ -2,11 +2,11 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import json
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Set, Tuple
from kitty.constants import appname
from .base import ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window
from .base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Tab, Window
if TYPE_CHECKING:
from kitty.cli_stub import LSRCOptions as CLIOptions
@ -15,29 +15,51 @@
class LS(RemoteCommand):
protocol_spec = __doc__ = '''
all_env_vars/bool: Whether to send all environment variables for every window rather than just differing ones
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
'''
short_desc = 'List all tabs/windows'
short_desc = 'List tabs/windows'
desc = (
'List all windows. The list is returned as JSON tree. The top-level is a list of'
'List windows. The list is returned as JSON tree. The top-level is a list of'
f' operating system {appname} windows. Each OS window has an :italic:`id` and a list'
' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.'
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)`,'
' :italic:`command-line` and :italic:`environment` of the process running in the window. Additionally, when'
' running the command inside a kitty window, that window can be identified by the :italic:`is_self` parameter.\n\n'
'You can use these criteria to select windows/tabs for the other commands.'
'You can use these criteria to select windows/tabs for the other commands.\n\n'
'You can limit the windows/tabs in the output by using the :option:`--match` and :option:`--match-tab` options.'
)
options_spec = '''\
--all-env-vars
type=bool-set
Show all environment variables in output, not just differing ones.
'''
--self
type=bool-set
Only list the window this command is run in.
''' + '\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:
return {'all_env_vars': opts.all_env_vars}
return {'all_env_vars': opts.all_env_vars, 'match': opts.match, 'match_tab': opts.match_tab}
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
data = list(boss.list_os_windows(window))
tab_filter: Optional[Callable[[Tab], bool]] = None
window_filter: Optional[Callable[[Window], bool]] = None
if payload_get('match') is not None:
window_ids = frozenset(w.id for w in self.windows_for_match_payload(boss, window, payload_get))
def wf(w: Window) -> bool:
return w.id in window_ids
window_filter = wf
if payload_get('match_tab') is not None:
tab_ids = frozenset(w.id for w in self.tabs_for_match_payload(boss, window, payload_get))
def tf(w: Tab) -> bool:
return w.id in tab_ids
tab_filter = tf
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()

View file

@ -11,6 +11,7 @@
from time import monotonic
from typing import (
Any,
Callable,
Deque,
Dict,
Generator,
@ -770,13 +771,14 @@ def move_window_forward(self) -> None:
def move_window_backward(self) -> None:
self.move_window(-1)
def list_windows(self, self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:
def list_windows(self, self_window: Optional[Window] = None, window_filter: Optional[Callable[[Window], bool]] = None) -> Generator[WindowDict, None, None]:
active_window = self.active_window
for w in self:
yield w.as_dict(
is_active=w is active_window,
is_focused=w.os_window_id == current_focused_os_window_id() and w is active_window,
is_self=w is self_window)
if window_filter is None or window_filter(w):
yield w.as_dict(
is_active=w is active_window,
is_focused=w.os_window_id == current_focused_os_window_id() and w is active_window,
is_self=w is self_window)
def matches_query(self, field: str, query: str, active_tab_manager: Optional['TabManager'] = None) -> bool:
if field == 'title':
@ -1011,21 +1013,28 @@ def __iter__(self) -> Iterator[Tab]:
def __len__(self) -> int:
return len(self.tabs)
def list_tabs(self, self_window: Optional[Window] = None) -> Generator[TabDict, None, None]:
def list_tabs(
self, self_window: Optional[Window] = None,
tab_filter: Optional[Callable[[Tab], bool]] = None,
window_filter: Optional[Callable[[Window], bool]] = None
) -> Generator[TabDict, None, None]:
active_tab = self.active_tab
for tab in self:
yield {
'id': tab.id,
'is_focused': tab is active_tab and tab.os_window_id == current_focused_os_window_id(),
'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_opts': tab.current_layout.layout_opts.serialized(),
'enabled_layouts': tab.enabled_layouts,
'windows': list(tab.list_windows(self_window)),
'active_window_history': list(tab.windows.active_window_history),
}
if tab_filter is None or tab_filter(tab):
windows = list(tab.list_windows(self_window, window_filter))
if windows:
yield {
'id': tab.id,
'is_focused': tab is active_tab and tab.os_window_id == current_focused_os_window_id(),
'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_opts': tab.current_layout.layout_opts.serialized(),
'enabled_layouts': tab.enabled_layouts,
'windows': windows,
'active_window_history': list(tab.windows.active_window_history),
}
def serialize_state(self) -> Dict[str, Any]:
return {