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
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: 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`) - 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] 0.29.2 [2023-07-27]

View File

@@ -428,20 +428,26 @@ class Boss:
self.os_window_map[os_window_id] = tm self.os_window_map[os_window_id] = tm
return os_window_id 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(): with cached_process_data():
active_tab_manager = self.active_tab_manager active_tab_manager = self.active_tab_manager
for os_window_id, tm in self.os_window_map.items(): for os_window_id, tm in self.os_window_map.items():
yield { tabs = list(tm.list_tabs(self_window, tab_filter, window_filter))
'id': os_window_id, if tabs:
'platform_window_id': platform_window_id(os_window_id), yield {
'is_active': tm is active_tab_manager, 'id': os_window_id,
'is_focused': current_focused_os_window_id() == os_window_id, 'platform_window_id': platform_window_id(os_window_id),
'last_focused': os_window_id == last_focused_os_window_id(), 'is_active': tm is active_tab_manager,
'tabs': list(tm.list_tabs(self_window)), 'is_focused': current_focused_os_window_id() == os_window_id,
'wm_class': tm.wm_class, 'last_focused': os_window_id == last_focused_os_window_id(),
'wm_name': tm.wm_name 'tabs': tabs,
} 'wm_class': tm.wm_class,
'wm_name': tm.wm_name
}
@property @property
def all_tab_managers(self) -> Iterator[TabManager]: def all_tab_managers(self) -> Iterator[TabManager]:

View File

@@ -13,11 +13,11 @@ 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
from kitty.tabs import Tab from kitty.tabs import Tab as T
from kitty.window import Window as W from kitty.window import Window as W
Window = W Window = W
Boss = B Boss = B
Tab Tab = T
else: else:
Boss = Window = Tab = None Boss = Window = Tab = None
RCOptions = R RCOptions = R

View File

@@ -2,11 +2,11 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import json 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 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: if TYPE_CHECKING:
from kitty.cli_stub import LSRCOptions as CLIOptions from kitty.cli_stub import LSRCOptions as CLIOptions
@@ -15,29 +15,51 @@ if TYPE_CHECKING:
class LS(RemoteCommand): class LS(RemoteCommand):
protocol_spec = __doc__ = ''' protocol_spec = __doc__ = '''
all_env_vars/bool: Whether to send all environment variables for every window rather than just differing ones 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 = ( 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' 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`.' ' 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)`,' ' 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' ' :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' ' 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 = '''\ options_spec = '''\
--all-env-vars --all-env-vars
type=bool-set type=bool-set
Show all environment variables in output, not just differing ones. 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: 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: 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'): if not payload_get('all_env_vars'):
all_env_blocks: List[Dict[str, str]] = [] all_env_blocks: List[Dict[str, str]] = []
common_env_vars: Set[Tuple[str, str]] = set() common_env_vars: Set[Tuple[str, str]] = set()

View File

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