mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-21 20:52:19 +00:00
misc layout code robustness fixes
This commit is contained in:
parent
3d02563153
commit
cb4cefee5a
4 changed files with 129 additions and 2 deletions
|
|
@ -584,7 +584,9 @@ class Layout:
|
|||
"""
|
||||
if s.get('class') != self.__class__.__name__:
|
||||
return False
|
||||
window_id_map = create_window_id_map_for_unserialize(all_windows)
|
||||
if 'all_windows' not in s:
|
||||
return False
|
||||
window_id_map = window_id_mapper(all_windows)
|
||||
m = all_windows.unserialize_layout_state(s['all_windows'], window_id_map, apply=apply_to_window_list)
|
||||
if m is None:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -382,13 +382,15 @@ class Tab: # {{{
|
|||
# saved, so that switching to them gives back their saved arrangement
|
||||
# rather than a freshly built one.
|
||||
for name, state in (layout_state.get('other_layouts') or {}).items():
|
||||
if name == self._current_layout_name:
|
||||
continue
|
||||
if name.partition(':')[0] not in all_layouts:
|
||||
continue
|
||||
layout = self.create_layout_object(name)
|
||||
if layout.unserialize(state, self.windows, apply_to_window_list=False):
|
||||
self._used_layouts[name] = layout
|
||||
last_used = layout_state.get('last_used_layout')
|
||||
if last_used and last_used != self._current_layout_name:
|
||||
if last_used and last_used != self._current_layout_name and last_used in self.enabled_layouts:
|
||||
self._last_used_layout = last_used
|
||||
|
||||
def serialize_state(self) -> dict[str, Any]:
|
||||
|
|
|
|||
|
|
@ -251,7 +251,13 @@ class WindowList:
|
|||
if new_window_id := window_id_map.get(old_window_id):
|
||||
new_window_ids.append(new_window_id)
|
||||
g.unserialize_layout_state(new_window_ids)
|
||||
active_group = self.active_group
|
||||
self.groups = groups
|
||||
if active_group is not None:
|
||||
for i, g in enumerate(self.groups):
|
||||
if g is active_group:
|
||||
self._active_group_idx = i
|
||||
break
|
||||
history = []
|
||||
for old_wid in state['active_group_history']:
|
||||
if new_wid := window_id_map.get(old_wid):
|
||||
|
|
|
|||
|
|
@ -296,6 +296,123 @@ class TestLayout(BaseTest):
|
|||
self.ae(fresh.pairs_root.serialize(), expected)
|
||||
self.ae([g.id for g in all_windows.groups], groups_before)
|
||||
|
||||
def test_unserialize_malformed_state(self):
|
||||
# unserialize() must return False gracefully for bad session data rather
|
||||
# than raising KeyError or similar.
|
||||
q = create_layout(Tall)
|
||||
all_windows = create_windows(q, num=3)
|
||||
for w in all_windows.all_windows:
|
||||
w.serialized_id = w.id
|
||||
q(all_windows)
|
||||
|
||||
# Missing 'all_windows' key
|
||||
state = q.serialize(all_windows)
|
||||
del state['all_windows']
|
||||
self.assertFalse(q.unserialize(state, all_windows))
|
||||
|
||||
# Wrong 'class' value
|
||||
state = q.serialize(all_windows)
|
||||
state['class'] = 'NonExistentLayout'
|
||||
self.assertFalse(q.unserialize(state, all_windows))
|
||||
|
||||
# Mismatched layout type (Tall state passed to Splits)
|
||||
state = q.serialize(all_windows)
|
||||
other = create_layout(Splits)
|
||||
self.assertFalse(other.unserialize(state, all_windows))
|
||||
|
||||
def test_unserialize_inactive_layout_non_splits(self):
|
||||
# apply_to_window_list=False must not reorder windows for non-Splits layouts.
|
||||
q = create_layout(Tall)
|
||||
all_windows = create_windows(q, num=3)
|
||||
for w in all_windows.all_windows:
|
||||
w.serialized_id = w.id
|
||||
q(all_windows)
|
||||
state = q.serialize(all_windows)
|
||||
groups_before = [g.id for g in all_windows.groups]
|
||||
active_before = all_windows.active_group_idx
|
||||
|
||||
fresh = create_layout(Tall)
|
||||
self.assertTrue(fresh.unserialize(state, all_windows, apply_to_window_list=False))
|
||||
self.ae([g.id for g in all_windows.groups], groups_before)
|
||||
self.ae(all_windows.active_group_idx, active_before)
|
||||
|
||||
def test_active_group_idx_preserved_after_unserialize(self):
|
||||
# When unserialize reorders the window list, active_group_idx must be
|
||||
# updated so it still points to the same group object.
|
||||
#
|
||||
# Simulate a session restore where windows were created in a different
|
||||
# order than the serialised state expects. The original session had
|
||||
# windows [1,2,3] in that order with window 3 active. The fresh list
|
||||
# has windows with the same serialised ids but inserted in reverse
|
||||
# order, so after unserialize the groups array is shuffled.
|
||||
original = create_layout(Stack)
|
||||
orig_windows = create_windows(original, num=0)
|
||||
for wid in (1, 2, 3):
|
||||
win = Window(wid)
|
||||
win.serialized_id = wid
|
||||
orig_windows.add_window(win)
|
||||
original(orig_windows)
|
||||
orig_windows.set_active_group_idx(2) # window 3 active
|
||||
state = original.serialize(orig_windows)
|
||||
|
||||
# Fresh window list: windows added in reverse serialised-id order so
|
||||
# their group positions differ from the serialised group order.
|
||||
fresh = create_layout(Stack)
|
||||
fresh_windows = create_windows(fresh, num=0)
|
||||
for actual_id, serialized_id in ((10, 3), (20, 2), (30, 1)):
|
||||
win = Window(actual_id)
|
||||
win.serialized_id = serialized_id
|
||||
fresh_windows.add_window(win)
|
||||
fresh(fresh_windows)
|
||||
# Simulate _startup: set active to the window whose serialised id is 3
|
||||
# (i.e. the window with actual_id=10, which is at index 0).
|
||||
fresh_windows.set_active_group_idx(0)
|
||||
active_group = fresh_windows.active_group
|
||||
|
||||
self.assertTrue(fresh.unserialize(state, fresh_windows))
|
||||
# active_group_idx must still point to the same group object after reorder.
|
||||
self.assertIs(fresh_windows.groups[fresh_windows.active_group_idx], active_group)
|
||||
|
||||
def test_unserialize_session_roundtrip_splits(self):
|
||||
# Full serialize → unserialize round-trip for Splits: tree structure and
|
||||
# window ordering are both restored correctly.
|
||||
q = create_layout(Splits)
|
||||
all_windows = create_windows(q, num=0)
|
||||
for wid, loc in ((1, None), (2, 'vsplit'), (3, 'hsplit')):
|
||||
win = Window(wid)
|
||||
win.serialized_id = wid
|
||||
q.add_window(all_windows, win, location=loc)
|
||||
q(all_windows)
|
||||
tree_before = q.pairs_root.serialize()
|
||||
state = q.serialize(all_windows)
|
||||
expected_groups = [g.id for g in all_windows.groups]
|
||||
|
||||
# Restore into the same window list (simulating a session with identical
|
||||
# windows but a fresh layout object, as happens on restart).
|
||||
fresh = create_layout(Splits)
|
||||
self.assertTrue(fresh.unserialize(state, all_windows))
|
||||
self.ae(fresh.pairs_root.serialize(), tree_before)
|
||||
self.ae([g.id for g in all_windows.groups], expected_groups)
|
||||
|
||||
def test_unserialize_session_roundtrip_tall(self):
|
||||
# Full serialize → unserialize round-trip for the Tall layout: custom
|
||||
# bias values and window ordering are preserved.
|
||||
q = create_layout(Tall)
|
||||
all_windows = create_windows(q, num=3)
|
||||
for w in all_windows.all_windows:
|
||||
w.serialized_id = w.id
|
||||
q(all_windows)
|
||||
# Skew the main bias so we have something non-default to verify.
|
||||
q.main_bias = list(q.main_bias)
|
||||
q.main_bias[0] = 0.7
|
||||
state = q.serialize(all_windows)
|
||||
expected_groups = [g.id for g in all_windows.groups]
|
||||
|
||||
fresh = create_layout(Tall)
|
||||
self.assertTrue(fresh.unserialize(state, all_windows))
|
||||
self.ae(fresh.main_bias[0], 0.7)
|
||||
self.ae([g.id for g in all_windows.groups], expected_groups)
|
||||
|
||||
def test_splits_maximize(self):
|
||||
q = create_layout(Splits)
|
||||
all_windows = create_windows(q, num=0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue