kitten @ ls: Also output the neighbors for every window

This commit is contained in:
Kovid Goyal 2025-11-16 21:01:55 +05:30
parent e0118e4553
commit e49d940621
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
11 changed files with 63 additions and 40 deletions

View file

@ -163,6 +163,8 @@ Detailed list of changes
- Have reloading config also reload the custom tab bar python modules (:disc:`9221`)
- kitten @ ls: Also output the neighbors for every window (:disc:`9225`)
0.44.0 [2025-11-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -9,8 +9,8 @@ from typing import Any, Callable, NamedTuple
from kitty.borders import BorderColor
from kitty.fast_data_types import Region, get_options, set_active_window, viewport_for_window
from kitty.options.types import Options
from kitty.types import Edges, WindowGeometry, WindowMapper
from kitty.typing_compat import TypedDict, WindowType
from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper
from kitty.typing_compat import WindowType
from kitty.window_list import WindowGroup, WindowList
@ -41,13 +41,6 @@ LayoutDimension = Generator[LayoutData, None, None]
ListOfWindows = list[WindowType]
class NeighborsMap(TypedDict):
left: list[int]
top: list[int]
right: list[int]
bottom: list[int]
class LayoutGlobalData:
draw_minimal_borders: bool = True
draw_active_borders: bool = True
@ -437,7 +430,7 @@ class Layout:
raise NotImplementedError()
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap:
return {'left': [], 'right': [], 'top': [], 'bottom': []}
return {}
def compute_needs_borders_map(self, all_windows: WindowList) -> dict[int, bool]:
return all_windows.compute_needs_borders_map(lgd.draw_active_borders)

View file

@ -8,11 +8,11 @@ from math import ceil, floor
from typing import Any
from kitty.borders import BorderColor
from kitty.types import Edges, WindowMapper
from kitty.types import Edges, NeighborsMap, WindowMapper
from kitty.typing_compat import WindowType
from kitty.window_list import WindowGroup, WindowList
from .base import BorderLine, Layout, LayoutData, LayoutDimension, ListOfWindows, NeighborsMap, layout_dimension, lgd
from .base import BorderLine, Layout, LayoutData, LayoutDimension, ListOfWindows, layout_dimension, lgd
from .tall import neighbors_for_tall_window
@ -302,12 +302,16 @@ class Grid(Layout):
xs.extend(neighbors(neighbor_row, neighbor_col))
return xs
return {
'top': neighbors(row-1, col) if row else [],
'bottom': neighbors(row + 1, col),
'left': side(row, col, -1) if col else [],
'right': side(row, col, 1) if col < ncols - 1 else [],
}
ans: NeighborsMap = {}
if row:
ans['top'] = neighbors(row-1, col)
if bottom := neighbors(row + 1, col):
ans['bottom'] = bottom
if col and (left := side(row, col, -1)):
ans['left'] = left
if col < ncols - 1:
ans['right'] = side(row, col, 1)
return ans
def layout_state(self) -> dict[str, Any]:
return {

View file

@ -5,11 +5,11 @@ from collections.abc import Collection, Generator, Sequence
from typing import Any, NamedTuple, Optional, TypedDict, Union
from kitty.borders import BorderColor
from kitty.types import Edges, WindowGeometry, WindowMapper
from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper
from kitty.typing_compat import EdgeLiteral, WindowType
from kitty.window_list import WindowGroup, WindowList
from .base import BorderLine, Layout, LayoutOpts, NeighborsMap, blank_rects_for_window, lgd, window_geometry_from_layouts
from .base import BorderLine, Layout, LayoutOpts, blank_rects_for_window, lgd, window_geometry_from_layouts
class Extent(NamedTuple):
@ -405,14 +405,14 @@ class Pair:
geometries = {group.id: group.geometry for group in all_windows.groups if group.geometry}
def extend(other: Union[int, 'Pair', None], edge: EdgeLiteral, which: EdgeLiteral) -> None:
if not ans[which] and other:
if not ans.get(which) and other:
if isinstance(other, Pair):
neighbors = (
w for w in other.edge_windows(edge)
if is_neighbouring_geometry(geometries[w], geometries[window_id], which))
ans[which].extend(neighbors)
ans.setdefault(which, []).extend(neighbors)
else:
ans[which].append(other)
ans.setdefault(which, []).append(other)
def is_neighbouring_geometry(a: WindowGeometry, b: WindowGeometry, direction: str) -> bool:
def edges(g: WindowGeometry) -> tuple[int, int]:
@ -610,7 +610,7 @@ class Splits(Layout):
wg = all_windows.group_for_window(window)
assert wg is not None
pair = self.pairs_root.pair_for_window(wg.id)
ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []}
ans: NeighborsMap = {}
if pair is not None:
pair.neighbors_for_window(wg.id, ans, self, all_windows)
return ans

View file

@ -1,10 +1,11 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from kitty.types import NeighborsMap
from kitty.typing_compat import WindowType
from kitty.window_list import WindowList
from .base import Layout, NeighborsMap
from .base import Layout
class Stack(Layout):

View file

@ -7,7 +7,7 @@ from typing import Any
from kitty.borders import BorderColor
from kitty.conf.utils import to_bool
from kitty.types import Edges, WindowMapper
from kitty.types import Edges, NeighborsMap, WindowMapper
from kitty.typing_compat import EdgeLiteral, WindowType
from kitty.window_list import WindowGroup, WindowList
@ -17,7 +17,6 @@ from .base import (
LayoutData,
LayoutDimension,
LayoutOpts,
NeighborsMap,
lgd,
normalize_biases,
safe_increment_bias,
@ -38,7 +37,7 @@ def neighbors_for_tall_window(
idx = groups.index(wg)
prev = None if idx == 0 else groups[idx-1]
nxt = None if idx == len(groups) - 1 else groups[idx+1]
ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []}
ans: NeighborsMap = {}
main_before: EdgeLiteral = 'left' if main_is_horizontal else 'top'
main_after: EdgeLiteral = 'right' if main_is_horizontal else 'bottom'
cross_before: EdgeLiteral = 'top' if main_is_horizontal else 'left'

View file

@ -5,11 +5,11 @@ from collections.abc import Generator, Iterable
from typing import Any
from kitty.borders import BorderColor
from kitty.types import Edges, WindowMapper
from kitty.typing_compat import WindowType
from kitty.types import Edges, NeighborsMap, WindowMapper
from kitty.typing_compat import EdgeLiteral, WindowType
from kitty.window_list import WindowGroup, WindowList
from .base import BorderLine, Layout, LayoutData, LayoutDimension, NeighborsMap, lgd
from .base import BorderLine, Layout, LayoutData, LayoutDimension, lgd
def borders(
@ -133,9 +133,16 @@ class Vertical(Layout):
after = [groups[(idx + 1) % lg].id]
else:
before, after = [], []
ans: NeighborsMap = {}
akey: EdgeLiteral = 'top'
bkey: EdgeLiteral = 'bottom'
if self.main_is_horizontal:
return {'left': before, 'right': after, 'top': [], 'bottom': []}
return {'top': before, 'bottom': after, 'left': [], 'right': []}
akey, bkey = 'left', 'right'
if before:
ans[bkey] = before
if after:
ans[akey] = after
return ans
def layout_state(self) -> dict[str, Any]:
return {'biased_map': self.biased_map}

View file

@ -943,12 +943,15 @@ class Tab: # {{{
def list_windows(self, self_window: Window | None = None, window_filter: Callable[[Window], bool] | None = None) -> Generator[WindowDict, None, None]:
active_window = self.active_window
cl = self.current_layout
for w in self:
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)
is_self=w is self_window,
neighbors_map=cl.neighbors_for_window(w, self.windows)
)
def list_groups(self) -> list[dict[str, Any]]:
return [g.as_simple_dict() for g in self.windows.groups]

View file

@ -234,3 +234,10 @@ def ac(group: ActionGroup, doc: str) -> Callable[[_T], _T]:
WindowMapper = Callable[[int], int | None]
DecoratedFunc = TypeVar('DecoratedFunc', bound=Callable[..., Any])
class NeighborsMap(TypedDict, total=False):
left: list[int]
top: list[int]
right: list[int]
bottom: list[int]

View file

@ -96,7 +96,7 @@ from .options.types import Options
from .progress import Progress
from .rgb import to_color
from .terminfo import get_capabilities
from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once
from .types import MouseEvent, NeighborsMap, OverlayType, WindowGeometry, ac, run_once
from .typing_compat import BossType, ChildType, EdgeLiteral, TabType, TypedDict
from .utils import (
color_as_int,
@ -250,6 +250,7 @@ class WindowDict(TypedDict):
at_prompt: bool
created_at: int
in_alternate_screen: bool
neighbors: NeighborsMap
class PipeData(TypedDict):
@ -1938,7 +1939,12 @@ class Window:
return get_mouse_data_for_window(self.os_window_id, self.tab_id, self.id)
# Serialization {{{
def as_dict(self, is_focused: bool = False, is_self: bool = False, is_active: bool = False) -> WindowDict:
def as_dict(
self, is_focused: bool = False, is_self: bool = False, is_active: bool = False,
neighbors_map: NeighborsMap | None = None,
) -> WindowDict:
if neighbors_map is None:
neighbors_map = {}
return {
'id': self.id,
'is_focused': is_focused,
@ -1958,6 +1964,7 @@ class Window:
'user_vars': self.user_vars,
'created_at': self.created_at,
'in_alternate_screen': self.screen.is_using_alternate_linebuf(),
'neighbors': neighbors_map,
}
def serialize_state(self) -> dict[str, Any]:

View file

@ -267,7 +267,7 @@ class TestLayout(BaseTest):
windows[1].set_geometry(WindowGeometry(11, 0, 20, 10, 0, 0))
windows[2].set_geometry(WindowGeometry(11, 11, 15, 20, 0, 0))
windows[3].set_geometry(WindowGeometry(16, 11, 20, 20, 0, 0))
self.ae(q.neighbors_for_window(windows[0], all_windows), {'left': [], 'right': [2, 3], 'top': [], 'bottom': []})
self.ae(q.neighbors_for_window(windows[1], all_windows), {'left': [1], 'right': [], 'top': [], 'bottom': [3, 4]})
self.ae(q.neighbors_for_window(windows[2], all_windows), {'left': [1], 'right': [4], 'top': [2], 'bottom': []})
self.ae(q.neighbors_for_window(windows[3], all_windows), {'left': [3], 'right': [], 'top': [2], 'bottom': []})
self.ae(q.neighbors_for_window(windows[0], all_windows), {'right': [2, 3]})
self.ae(q.neighbors_for_window(windows[1], all_windows), {'left': [1], 'bottom': [3, 4]})
self.ae(q.neighbors_for_window(windows[2], all_windows), {'left': [1], 'right': [4], 'top': [2]})
self.ae(q.neighbors_for_window(windows[3], all_windows), {'left': [3], 'top': [2]})