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