1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> 4 5from kitty.config import defaults 6from kitty.types import WindowGeometry 7from kitty.layout.interface import Grid, Horizontal, Splits, Stack, Tall 8from kitty.window import EdgeWidths 9from kitty.window_list import WindowList, reset_group_id_counter 10 11from . import BaseTest 12 13 14class Window: 15 16 def __init__(self, win_id, overlay_for=None, overlay_window_id=None): 17 self.id = win_id 18 self.overlay_for = overlay_for 19 self.overlay_window_id = overlay_window_id 20 self.is_visible_in_layout = True 21 self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0) 22 self.padding = EdgeWidths() 23 self.margin = EdgeWidths() 24 self.focused = False 25 26 def focus_changed(self, focused): 27 self.focused = focused 28 29 def effective_border(self): 30 return 1 31 32 def effective_padding(self, edge): 33 return 1 34 35 def effective_margin(self, edge, is_single_window=False): 36 return 0 if is_single_window else 1 37 38 def set_visible_in_layout(self, val): 39 self.is_visible_in_layout = bool(val) 40 41 def set_geometry(self, geometry): 42 self.geometry = geometry 43 44 45def create_layout(cls, opts=None, border_width=2): 46 if opts is None: 47 opts = defaults 48 ans = cls(1, 1) 49 ans.set_active_window_in_os_window = lambda idx: None 50 ans.swap_windows_in_os_window = lambda a, b: None 51 return ans 52 53 54class Tab: 55 56 def active_window_changed(self): 57 self.current_layout.update_visibility(self.windows) 58 59 60def create_windows(layout, num=5): 61 t = Tab() 62 t.current_layout = layout 63 t.windows = ans = WindowList(t) 64 ans.tab_mem = t 65 reset_group_id_counter() 66 for i in range(num): 67 ans.add_window(Window(i + 1)) 68 ans.set_active_group_idx(0) 69 return ans 70 71 72def utils(self, q, windows): 73 def ids(): 74 return [w.id for w in windows.groups] 75 76 def visible_ids(): 77 return {gr.id for gr in windows.groups if gr.is_visible_in_layout} 78 79 def expect_ids(*a): 80 self.assertEqual(tuple(ids()), a) 81 82 def check_visible(): 83 if q.only_active_window_visible: 84 self.ae(visible_ids(), {windows.active_group.id}) 85 else: 86 self.ae(visible_ids(), {gr.id for gr in windows.groups}) 87 return ids, visible_ids, expect_ids, check_visible 88 89 90class TestLayout(BaseTest): 91 92 def do_ops_test(self, q): 93 windows = create_windows(q) 94 ids, visible_ids, expect_ids, check_visible = utils(self, q, windows) 95 # Test layout 96 q(windows) 97 self.ae(windows.active_group_idx, 0) 98 expect_ids(*range(1, len(windows)+1)) 99 check_visible() 100 101 # Test nth_window 102 for i in range(windows.num_groups): 103 q.activate_nth_window(windows, i) 104 self.ae(windows.active_group_idx, i) 105 expect_ids(*range(1, len(windows)+1)) 106 check_visible() 107 108 # Test next_window 109 for i in range(2 * windows.num_groups): 110 expected = (windows.active_group_idx + 1) % windows.num_groups 111 q.next_window(windows) 112 self.ae(windows.active_group_idx, expected) 113 expect_ids(*range(1, len(windows)+1)) 114 check_visible() 115 116 # Test move_window 117 windows.set_active_group_idx(0) 118 expect_ids(1, 2, 3, 4, 5) 119 q.move_window(windows, 3) 120 self.ae(windows.active_group_idx, 3) 121 expect_ids(4, 2, 3, 1, 5) 122 check_visible() 123 windows.set_active_group_idx(0) 124 q.move_window(windows, 3) 125 expect_ids(*range(1, len(windows)+1)) 126 check_visible() 127 128 # Test add_window 129 windows.set_active_group_idx(4) 130 q.add_window(windows, Window(6)) 131 self.ae(windows.num_groups, 6) 132 self.ae(windows.active_group_idx, 5) 133 expect_ids(*range(1, windows.num_groups+1)) 134 check_visible() 135 136 # Test remove_window 137 prev_window = windows.active_window 138 windows.set_active_group_idx(3) 139 self.ae(windows.active_group_idx, 3) 140 windows.remove_window(windows.active_window) 141 self.ae(windows.active_window, prev_window) 142 check_visible() 143 expect_ids(1, 2, 3, 5, 6) 144 145 windows.set_active_group_idx(0) 146 to_remove = windows.active_window 147 windows.set_active_group_idx(3) 148 windows.remove_window(to_remove) 149 self.ae(windows.active_group_idx, 3) 150 check_visible() 151 expect_ids(2, 3, 5, 6) 152 153 # Test set_active_window 154 for i in range(windows.num_groups): 155 windows.set_active_group_idx(i) 156 self.ae(i, windows.active_group_idx) 157 check_visible() 158 159 def do_overlay_test(self, q): 160 windows = create_windows(q) 161 ids, visible_ids, expect_ids, check_visible = utils(self, q, windows) 162 163 # Test add_window 164 w = Window(len(windows) + 1) 165 before = windows.active_group_idx 166 overlaid_group = before 167 overlay_window_id = w.id 168 windows.add_window(w, group_of=windows.active_window) 169 self.ae(before, windows.active_group_idx) 170 self.ae(w, windows.active_window) 171 expect_ids(1, 2, 3, 4, 5) 172 check_visible() 173 174 # Test layout 175 q(windows) 176 expect_ids(1, 2, 3, 4, 5) 177 check_visible() 178 w = Window(len(windows) + 1) 179 windows.add_window(w) 180 expect_ids(1, 2, 3, 4, 5, 6) 181 self.ae(windows.active_group_idx, windows.num_groups - 1) 182 183 # Test nth_window 184 for i in range(windows.num_groups): 185 q.activate_nth_window(windows, i) 186 self.ae(windows.active_group_idx, i) 187 if i == overlaid_group: 188 self.ae(windows.active_window.id, overlay_window_id) 189 expect_ids(1, 2, 3, 4, 5, 6) 190 check_visible() 191 192 # Test next_window 193 for i in range(windows.num_groups): 194 expected = (windows.active_group_idx + 1) % windows.num_groups 195 q.next_window(windows) 196 self.ae(windows.active_group_idx, expected) 197 expect_ids(1, 2, 3, 4, 5, 6) 198 check_visible() 199 200 # Test move_window 201 windows.set_active_group_idx(overlaid_group) 202 expect_ids(1, 2, 3, 4, 5, 6) 203 q.move_window(windows, 3) 204 self.ae(windows.active_group_idx, 3) 205 self.ae(windows.active_window.id, overlay_window_id) 206 expect_ids(4, 2, 3, 1, 5, 6) 207 check_visible() 208 windows.set_active_group_idx(0) 209 q.move_window(windows, 3) 210 expect_ids(1, 2, 3, 4, 5, 6) 211 check_visible() 212 213 # Test set_active_window 214 for i in range(windows.num_groups): 215 windows.set_active_group_idx(i) 216 self.ae(i, windows.active_group_idx) 217 if i == overlaid_group: 218 self.ae(windows.active_window.id, overlay_window_id) 219 check_visible() 220 221 # Test remove_window 222 expect_ids(1, 2, 3, 4, 5, 6) 223 windows.set_active_group_idx(overlaid_group) 224 windows.remove_window(overlay_window_id) 225 self.ae(windows.active_group_idx, overlaid_group) 226 self.ae(windows.active_window.id, 1) 227 expect_ids(1, 2, 3, 4, 5, 6) 228 check_visible() 229 230 def test_layout_operations(self): 231 for layout_class in (Stack, Horizontal, Tall, Grid): 232 q = create_layout(layout_class) 233 self.do_ops_test(q) 234 235 def test_overlay_layout_operations(self): 236 for layout_class in (Stack, Horizontal, Tall, Grid): 237 q = create_layout(layout_class) 238 self.do_overlay_test(q) 239 240 def test_splits(self): 241 q = create_layout(Splits) 242 all_windows = create_windows(q, num=0) 243 q.add_window(all_windows, Window(1)) 244 self.ae(all_windows.active_group_idx, 0) 245 q.add_window(all_windows, Window(2), location='vsplit') 246 self.ae(all_windows.active_group_idx, 1) 247 q(all_windows) 248 self.ae(q.pairs_root.pair_for_window(2).horizontal, True) 249 q.add_window(all_windows, Window(3), location='hsplit') 250 self.ae(q.pairs_root.pair_for_window(2).horizontal, False) 251 q.add_window(all_windows, Window(4), location='vsplit') 252 windows = list(all_windows) 253 windows[0].set_geometry(WindowGeometry(0, 0, 10, 20, 0, 0)) 254 windows[1].set_geometry(WindowGeometry(11, 0, 20, 10, 0, 0)) 255 windows[2].set_geometry(WindowGeometry(11, 11, 15, 20, 0, 0)) 256 windows[3].set_geometry(WindowGeometry(16, 11, 20, 20, 0, 0)) 257 self.ae(q.neighbors_for_window(windows[0], all_windows), {'left': [], 'right': [2, 3], 'top': [], 'bottom': []}) 258 self.ae(q.neighbors_for_window(windows[1], all_windows), {'left': [1], 'right': [], 'top': [], 'bottom': [3, 4]}) 259 self.ae(q.neighbors_for_window(windows[2], all_windows), {'left': [1], 'right': [4], 'top': [2], 'bottom': []}) 260 self.ae(q.neighbors_for_window(windows[3], all_windows), {'left': [3], 'right': [], 'top': [2], 'bottom': []}) 261