mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-20 15:35:03 +02:00
More work on refactoring window groups
This commit is contained in:
@@ -2,20 +2,70 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import Dict, Generator, Iterator, List, Optional, Union
|
||||
import weakref
|
||||
from collections import deque
|
||||
from contextlib import suppress
|
||||
from itertools import count
|
||||
from typing import Any, Deque, Dict, Iterator, List, Optional, Union
|
||||
|
||||
from .typing import WindowType
|
||||
from .typing import TabType, WindowType
|
||||
|
||||
WindowOrId = Union[WindowType, int]
|
||||
group_id_counter = count()
|
||||
|
||||
|
||||
class WindowGroup:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.windows: List[WindowType] = []
|
||||
self.id = next(group_id_counter)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.windows)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.windows)
|
||||
|
||||
def __iter__(self) -> Iterator[WindowType]:
|
||||
return iter(self.windows)
|
||||
|
||||
def __contains__(self, window: WindowType) -> bool:
|
||||
for w in self.windows:
|
||||
if w is window:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def base_window_id(self) -> int:
|
||||
return self.windows[0].id if self.windows else 0
|
||||
|
||||
@property
|
||||
def active_window_id(self) -> int:
|
||||
return self.windows[-1].id if self.windows else 0
|
||||
|
||||
def add_window(self, window: WindowType) -> None:
|
||||
self.windows.append(window)
|
||||
|
||||
def remove_window(self, window: WindowType) -> None:
|
||||
with suppress(ValueError):
|
||||
self.windows.remove(window)
|
||||
|
||||
def serialize_state(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'id': self.id,
|
||||
'windows': [w.serialize_state() for w in self.windows]
|
||||
}
|
||||
|
||||
|
||||
class WindowList:
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, tab: TabType) -> None:
|
||||
self.all_windows: List[WindowType] = []
|
||||
self.id_map: Dict[int, WindowType] = {}
|
||||
self.overlay_stacks: Dict[int, List[int]] = {}
|
||||
self.id_to_idx_map: Dict[int, int] = {}
|
||||
self.idx_to_base_id_map: Dict[int, int] = {}
|
||||
self.max_active_idx = 0
|
||||
self.groups: List[WindowGroup] = []
|
||||
self.active_group_idx: int = -1
|
||||
self.active_group_history: Deque[int] = deque((), 64)
|
||||
self.tabref = weakref.ref(tab)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.all_windows)
|
||||
@@ -29,62 +79,144 @@ class WindowList:
|
||||
def __contains__(self, window: WindowType) -> bool:
|
||||
return window.id in self.id_map
|
||||
|
||||
def stack_for_window_id(self, q: int) -> List[int]:
|
||||
' The stack of overlaid windows this window belongs to '
|
||||
w = self.id_map[q]
|
||||
if w.overlay_for is not None and w.overlay_for in self.id_map:
|
||||
q = self.id_map[w.overlay_for].id
|
||||
return self.overlay_stacks[q]
|
||||
def serialize_state(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'active_group_idx': self.active_group_idx,
|
||||
'active_group_history': list(self.active_group_history),
|
||||
'window_groups': [g.serialize_state() for g in self.groups]
|
||||
}
|
||||
|
||||
def iter_top_level_windows(self) -> Generator[WindowType, None, None]:
|
||||
' Iterator over all top level windows '
|
||||
for stack in self.overlay_stacks.values():
|
||||
yield self.id_map[stack[-1]]
|
||||
@property
|
||||
def active_window_history(self) -> List[int]:
|
||||
ans = []
|
||||
seen = set()
|
||||
gid_map = {g.id: g for g in self.groups}
|
||||
for gid in self.active_group_history:
|
||||
g = gid_map[gid]
|
||||
w = g.active_window_id
|
||||
if w > 0 and w not in seen:
|
||||
seen.add(w)
|
||||
ans.append(w)
|
||||
return ans
|
||||
|
||||
def iter_stack_for_window(self, x: Union[WindowType, int], reverse: bool = False) -> Generator[WindowType, None, None]:
|
||||
' Iterator over all windows in the stack for this window '
|
||||
q = x if isinstance(x, int) else x.id
|
||||
stack = self.stack_for_window_id(q)
|
||||
y = reversed(stack) if reverse else iter(stack)
|
||||
for wid in y:
|
||||
yield self.id_map[wid]
|
||||
def set_active_group_idx(self, i: int) -> None:
|
||||
if i != self.active_group_idx and 0 <= i < len(self.groups):
|
||||
old_active_window = self.active_window
|
||||
g = self.active_group
|
||||
if g is not None:
|
||||
with suppress(ValueError):
|
||||
self.active_group_history.remove(g.id)
|
||||
self.active_group_history.append(g.id)
|
||||
self.active_group_idx = i
|
||||
new_active_window = self.active_window
|
||||
if old_active_window is not new_active_window:
|
||||
if old_active_window is not None:
|
||||
old_active_window.focus_changed(False)
|
||||
if new_active_window is not None:
|
||||
new_active_window.focus_changed(True)
|
||||
tab = self.tabref()
|
||||
if tab is not None:
|
||||
tab.active_window_changed()
|
||||
|
||||
def overlay_for(self, x: Union[WindowType, int]) -> int:
|
||||
' id of the top-most window overlaying this window, same as this window id if not overlaid '
|
||||
q = x if isinstance(x, int) else x.id
|
||||
return self.stack_for_window_id(q)[-1]
|
||||
def change_tab(self, tab: TabType) -> None:
|
||||
self.tabref = weakref.ref(tab)
|
||||
|
||||
def overlaid_window_for(self, x: Union[WindowType, int]) -> int:
|
||||
' id of the bottom-most window in this windows overlay stack '
|
||||
q = x if isinstance(x, int) else x.id
|
||||
return self.stack_for_window_id(q)[0]
|
||||
def make_previous_group_active(self, which: int = 1) -> None:
|
||||
which = max(1, which)
|
||||
self.active_group_idx = len(self.groups) - 1
|
||||
gid_map = {g.id: i for i, g in enumerate(self.groups)}
|
||||
num = len(self.active_group_history)
|
||||
for i in range(num):
|
||||
idx = num - i - 1
|
||||
gid = self.active_group_history[idx]
|
||||
x = gid_map.get(gid)
|
||||
if x is not None:
|
||||
which -= 1
|
||||
if which < 1:
|
||||
self.set_active_group_idx(x)
|
||||
return
|
||||
|
||||
def is_overlaid(self, x: Union[WindowType, int]) -> bool:
|
||||
' Return False if there is a window overlaying this one '
|
||||
q = x if isinstance(x, int) else x.id
|
||||
return self.overlay_for(q) != q
|
||||
@property
|
||||
def num_groups(self) -> int:
|
||||
return len(self.groups)
|
||||
|
||||
def idx_for_window(self, x: Union[WindowType, int]) -> Optional[int]:
|
||||
' Return the index of the window in the list of top-level windows '
|
||||
q = x if isinstance(x, int) else x.id
|
||||
return self.id_to_idx_map[q]
|
||||
|
||||
def active_window_for_idx(self, idx: int, clamp: bool = False) -> Optional[WindowType]:
|
||||
' Return the active window at the specified index '
|
||||
if clamp:
|
||||
idx = max(0, min(idx, self.max_active_idx))
|
||||
q = self.idx_to_base_id_map.get(idx)
|
||||
if q is not None:
|
||||
return self.id_map[self.overlay_stacks[q][-1]]
|
||||
def group_for_window(self, x: WindowOrId) -> Optional[WindowGroup]:
|
||||
q = self.id_map[x] if isinstance(x, int) else x
|
||||
for g in self.groups:
|
||||
if q in g:
|
||||
return g
|
||||
return None
|
||||
|
||||
def next_id_in_stack_on_remove(self, x: Union[WindowType, int]) -> Optional[int]:
|
||||
' The id of the window that should become active when this window is removed, or None if there is no other window in the stack '
|
||||
q = x if isinstance(x, int) else x.id
|
||||
stack = self.stack_for_window_id(q)
|
||||
idx = stack.index(q)
|
||||
if idx < len(stack) - 1:
|
||||
return stack[idx + 1]
|
||||
if idx > 0:
|
||||
return stack[idx - 1]
|
||||
def windows_in_group_of(self, x: WindowOrId) -> Iterator[WindowType]:
|
||||
g = self.group_for_window(x)
|
||||
if g is not None:
|
||||
return iter(g)
|
||||
|
||||
@property
|
||||
def active_group(self) -> Optional[WindowGroup]:
|
||||
if self.active_group_idx >= 0:
|
||||
return self.groups[self.active_group_idx]
|
||||
return None
|
||||
|
||||
@property
|
||||
def active_window(self) -> Optional[WindowType]:
|
||||
if self.active_group_idx >= 0:
|
||||
return self.id_map[self.groups[self.active_group_idx].active_window_id]
|
||||
return None
|
||||
|
||||
@active_window.setter
|
||||
def active_window(self, x: WindowOrId) -> None:
|
||||
q = self.id_map[x] if isinstance(x, int) else x
|
||||
for i, group in enumerate(self.groups):
|
||||
if q in group:
|
||||
self.set_active_group_idx(i)
|
||||
break
|
||||
|
||||
def add_window(
|
||||
self,
|
||||
window: WindowType,
|
||||
group_of: Optional[WindowOrId] = None,
|
||||
next_to: Optional[WindowOrId] = None,
|
||||
before: bool = False,
|
||||
make_active: bool = True
|
||||
) -> None:
|
||||
self.all_windows.append(window)
|
||||
self.id_map[window.id] = window
|
||||
target_group: Optional[WindowGroup] = None
|
||||
|
||||
if group_of is not None:
|
||||
target_group = self.group_for_window(group_of)
|
||||
if target_group is None and next_to is not None:
|
||||
q = self.id_map[next_to] if isinstance(next_to, int) else next_to
|
||||
pos = -1
|
||||
for i, g in enumerate(self.groups):
|
||||
if q in g:
|
||||
pos = i
|
||||
break
|
||||
if pos > -1:
|
||||
target_group = WindowGroup()
|
||||
self.groups.insert(pos + (0 if before else 1), target_group)
|
||||
if target_group is None:
|
||||
target_group = WindowGroup()
|
||||
self.groups.append(target_group)
|
||||
|
||||
target_group.add_window(window)
|
||||
if make_active:
|
||||
for i, g in enumerate(self.groups):
|
||||
if g is target_group:
|
||||
self.set_active_group_idx(i)
|
||||
break
|
||||
|
||||
def remove_window(self, x: WindowOrId) -> None:
|
||||
q = self.id_map[x] if isinstance(x, int) else x
|
||||
try:
|
||||
self.all_windows.remove(q)
|
||||
except ValueError:
|
||||
pass
|
||||
self.id_map.pop(q.id, None)
|
||||
for i, g in enumerate(tuple(self.groups)):
|
||||
g.remove_window(q)
|
||||
if not g:
|
||||
del self.groups[i]
|
||||
if self.active_group_idx == i:
|
||||
self.make_previous_group_active()
|
||||
|
||||
Reference in New Issue
Block a user