From 836494a8f011d253270b501c62796fc445b7156d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 3 Dec 2016 12:48:37 +0530 Subject: [PATCH] Implement the basic shortcuts for window management --- kitty/char_grid.py | 9 +++++---- kitty/kitty.conf | 9 +++++++++ kitty/layout.py | 36 +++++++++++++++++++++++---------- kitty/tabs.py | 50 ++++++++++++++++++++++++++++++++++++---------- kitty/window.py | 14 ++++++++++++- 5 files changed, 92 insertions(+), 26 deletions(-) diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 5af9e5536..87b6c67be 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -21,6 +21,11 @@ Cursor = namedtuple('Cursor', 'x y hidden shape color blink') if DATA_CELL_SIZE % 3: raise ValueError('Incorrect data cell size, must be a multiple of 3') + +def color_as_int(val): + return val[0] << 16 | val[1] << 8 | val[2] + + # cell shader {{{ cell_shader = ( @@ -145,10 +150,6 @@ void main() { # }}} -def color_as_int(val): - return val[0] << 16 | val[1] << 8 | val[2] - - class Selection: # {{{ __slots__ = tuple('in_progress start_x start_y start_scrolled_by end_x end_y end_scrolled_by'.split()) diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 6226bd4a7..232cce64c 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -110,12 +110,21 @@ color15 #ffffff # Key mapping # For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html # For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html + +# Clipboard map ctrl+shift+v paste_from_clipboard map ctrl+shift+s paste_from_selection map ctrl+shift+c copy_to_clipboard + +# Scrolling map ctrl+shift+up scroll_line_up map ctrl+shift+down scroll_line_down map ctrl+shift+page_up scroll_page_up map ctrl+shift+page_down scroll_page_down map ctrl+shift+home scroll_home map ctrl+shift+end scroll_end + +# Window management +map ctrl+shift+n new_window +map ctrl+shift+tab next_window +map ctrl+shift+w close_window diff --git a/kitty/layout.py b/kitty/layout.py index 0ae8cfab6..bd5ed6e33 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -35,13 +35,21 @@ class Layout: self.opts = opts self.border_width = border_width - def add_window(self, windows, window): + def next_window(self, windows, active_window_idx): + active_window_idx = (active_window_idx + 1) % len(windows) + self.set_active_window(windows, active_window_idx) + return active_window_idx + + def add_window(self, windows, window, active_window_idx): raise NotImplementedError() - def remove_window(self, windows, window): + def remove_window(self, windows, window, active_window_idx): raise NotImplementedError() - def __call__(self, windows): + def set_active_window(self, windows, active_window_idx): + raise NotImplementedError() + + def __call__(self, windows, active_window_idx): raise NotImplementedError() @@ -50,18 +58,26 @@ class Stack(Layout): name = 'stack' needs_window_borders = False - def add_window(self, windows, window): - windows.appendleft(window) - self(windows) + def add_window(self, windows, window, active_window_idx): + windows.append(window) + active_window_idx = len(windows) - 1 + self(windows, active_window_idx) + return active_window_idx - def remove_window(self, windows, window): + def remove_window(self, windows, window, active_window_idx): windows.remove(window) - self(windows) + active_window_idx = max(0, min(active_window_idx, len(windows) - 1)) + self(windows, active_window_idx) + return active_window_idx - def __call__(self, windows): + def set_active_window(self, windows, active_window_idx): + for i, w in enumerate(windows): + w.is_visible_in_layout = i == active_window_idx + + def __call__(self, windows, active_window_idx): xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width)) ystart, ynum = next(layout_dimension(available_height(), cell_size.height)) wg = WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_size.width * xnum, bottom=ystart + cell_size.height * ynum) for i, w in enumerate(windows): - w.is_visible_in_layout = i == 0 + w.is_visible_in_layout = i == active_window_idx w.set_geometry(wg) diff --git a/kitty/tabs.py b/kitty/tabs.py index 283f8c429..3e49e4338 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -67,6 +67,7 @@ class Tab: def __init__(self, opts, args): self.opts, self.args = opts, args self.windows = deque() + self.active_window_idx = 0 self.borders = Borders(opts) self.current_layout = Stack(opts, self.borders.border_width) @@ -76,7 +77,7 @@ class Tab: @property def active_window(self): - return self.windows[0] if self.windows else None + return self.windows[self.active_window_idx] if self.windows else None @property def title(self): @@ -89,7 +90,7 @@ class Tab: def relayout(self): if self.windows: - self.current_layout(self.windows) + self.current_layout(self.windows, self.active_window_idx) self.borders(self.windows, self.active_window, self.current_layout.needs_window_borders) def launch_child(self, use_shell=False): @@ -101,14 +102,36 @@ class Tab: ans.fork() return ans - def new_window(self, use_shell=False): + def new_window(self, use_shell=True): child = self.launch_child(use_shell=use_shell) window = Window(self, child, self.opts, self.args) tab_manager().add_child_fd(child.child_fd, window.read_ready, window.write_ready) - self.current_layout.add_window(self.windows, window) + self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx) + glfw_post_empty_event() + + def close_window(self): + if self.windows: + self.remove_window(self.windows[self.active_window_idx]) + glfw_post_empty_event() def remove_window(self, window): - self.current_layout.remove_window(self.windows, window) + self.active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx) + glfw_post_empty_event() + + def set_active_window(self, window): + try: + idx = self.windows.index(window) + except ValueError: + return + if idx != self.active_window_idx: + self.current_layout.set_active_window(self.windows, idx) + self.active_window_idx = idx + glfw_post_empty_event() + + def next_window(self): + if len(self.windows) > 1: + self.active_window_idx = self.current_layout.next_window(self.windows, self.active_window_idx) + glfw_post_empty_event() def __iter__(self): yield from iter(self.windows) @@ -227,8 +250,10 @@ class TabManager(Thread): self.pending_ui_thread_calls.put((func, args)) glfw_post_empty_event() - def close_window(self, window): + def close_window(self, window=None): ' Can be called in either thread, will first kill the child (with SIGHUP), then remove the window from the gui ' + if window is None: + window = self.active_window if current_thread() is main_thread: self.queue_action(self.close_window, window) else: @@ -307,8 +332,10 @@ class TabManager(Thread): if action == GLFW_PRESS or action == GLFW_REPEAT: func = get_shortcut(self.opts.keymap, mods, key) tab = self.active_tab + if tab is None: + return if func is not None: - f = getattr(self, func, getattr(tab, func, None)) + f = getattr(self, func, None) if f is not None: passthrough = f() if not passthrough: @@ -317,7 +344,7 @@ class TabManager(Thread): if window is not None: yield window if func is not None: - f = getattr(window, func, None) + f = getattr(tab, func, getattr(window, func, None)) if f is not None: passthrough = f() if not passthrough: @@ -352,10 +379,11 @@ class TabManager(Thread): return focus_moved = False old_focus = self.active_window - if button == GLFW_MOUSE_BUTTON_1 and w is not old_focus: - # TODO: Switch focus to this window - focus_moved = True + tab = self.active_tab yield + if button == GLFW_MOUSE_BUTTON_1 and w is not old_focus: + tab.set_active_window(w) + focus_moved = True if focus_moved: if old_focus is not None and not old_focus.destroyed: old_focus.focus_changed(False) diff --git a/kitty/window.py b/kitty/window.py index 4eca60058..427fdb940 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -33,7 +33,7 @@ class Window: self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0) self.needs_layout = True self.title = appname - self.is_visible_in_layout = True + self._is_visible_in_layout = True self.child, self.opts = child, opts self.child_fd = child.child_fd self.screen = Screen(self, 24, 80, opts.scrollback_lines) @@ -42,6 +42,18 @@ class Window: self.write_buf = memoryview(b'') self.char_grid = CharGrid(self.screen, opts) + @property + def is_visible_in_layout(self): + return self._is_visible_in_layout + + @is_visible_in_layout.setter + def is_visible_in_layout(self, val): + val = bool(val) + if val != self._is_visible_in_layout: + self._is_visible_in_layout = val + if val: + self.refresh() + def refresh(self): self.screen.mark_as_dirty() wakeup()