layout.py 8.7 KB

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