From 497e2c91632358cf6c503fc7670a2059c40a828a Mon Sep 17 00:00:00 2001 From: Manuel Herrmann <0@0x17.de> Date: Thu, 10 Oct 2019 16:59:53 +0200 Subject: [PATCH] #1310 move windows into separate tabs --- kitty/boss.py | 16 +++++++++++++++- kitty/config_data.py | 6 ++++++ kitty/state.c | 27 +++++++++++++++++++++++++++ kitty/tabs.py | 43 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 82 insertions(+), 10 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 76b4a6825..6393d5fea 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -39,6 +39,7 @@ from .utils import ( log_error, open_url, parse_address_spec, remove_socket_file, safe_print, set_primary_selection, single_instance, startup_notification_handler ) +from .window import Window def listen_on(spec): @@ -960,14 +961,20 @@ class Boss: def _new_tab(self, args, cwd_from=None, as_neighbor=False): special_window = None + existing_window = None if args: if isinstance(args, SpecialWindowInstance): special_window = args + elif isinstance(args, Window): + existing_window = args else: special_window = self.args_to_special_window(args, cwd_from=cwd_from) tm = self.active_tab_manager if tm is not None: - return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor) + return tm.new_tab(special_window=special_window, + existing_window=existing_window, + cwd_from=cwd_from, + as_neighbor=as_neighbor) def _create_tab(self, args, cwd_from=None): as_neighbor = False @@ -1020,6 +1027,13 @@ class Boss: if tm is not None: tm.move_tab(-1) + def _move_window_to_tab(self, args, cmd_from=None): + window = self.active_window + self._new_tab(window) + + def move_window_to_tab(self, *args): + self._move_window_to_tab(args) + def disable_ligatures_in(self, where, strategy): if isinstance(where, str): windows = () diff --git a/kitty/config_data.py b/kitty/config_data.py index 39ecd0bac..1afdedb47 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -1074,6 +1074,12 @@ see :doc:`pipe`. # }}} g('shortcuts.window') # {{{ +k('move_window_to_tab', 'kitty_mod+x', 'move_window_to_tab', _(''), long_text=_(''' +You can move a window into a separate tab, for example:: + + map kitty_mod+x move_window_to_tab + +''')) k('new_window', 'kitty_mod+enter', 'new_window', _(''), long_text=_(''' You can open a new window running an arbitrary program, for example:: diff --git a/kitty/state.c b/kitty/state.c index c066c29e0..1c2dd32fe 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -116,6 +116,31 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) { return 0; } +static inline id_type +change_window_tab(id_type window_id, id_type old_tab_os_window_id, id_type old_tab_id, id_type new_tab_os_window_id, id_type new_tab_id) { + // move the window description from one tab to another + Window* new_window_description = 0; + WITH_TAB(new_tab_os_window_id, new_tab_id); + ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true); + make_os_window_context_current(osw); + zero_at_i(tab->windows, tab->num_windows); + new_window_description = &tab->windows[tab->num_windows]; + ++tab->num_windows; + END_WITH_TAB; + if (new_window_description == 0) return 0; // could not find the window inside the old tab + WITH_TAB(old_tab_os_window_id, old_tab_id); + for (size_t i = 0; i < tab->num_windows; i++) { + if (tab->windows[i].id == window_id) { + memcpy(new_window_description, &tab->windows[i], sizeof(Window)); + zero_at_i(tab->windows, i); + remove_i_from_array(tab->windows, i, tab->num_windows); + break; + } + } + END_WITH_TAB; + return new_window_description->id; +} + static inline void update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) { WITH_TAB(os_window_id, tab_id); @@ -756,6 +781,7 @@ THREE_ID(remove_window) PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); } PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); } PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); } +PYWRAP1(change_window_tab) { id_type a, b, c, d, e; PA("KKKKK", &a, &b, &c, &d, &e); return PyLong_FromUnsignedLongLong(change_window_tab(a, b, c, d, e)); } PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); } TWO_ID(remove_tab) KI(set_active_tab) @@ -777,6 +803,7 @@ static PyMethodDef module_methods[] = { MW(pt_to_px, METH_VARARGS), MW(add_tab, METH_O), MW(add_window, METH_VARARGS), + MW(change_window_tab, METH_VARARGS), MW(update_window_title, METH_VARARGS), MW(remove_tab, METH_VARARGS), MW(remove_window, METH_VARARGS), diff --git a/kitty/tabs.py b/kitty/tabs.py index 65bbcc8fc..93d912e72 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -12,7 +12,7 @@ from .child import Child from .constants import appname, get_boss, is_macos, is_wayland from .fast_data_types import ( add_tab, mark_tab_bar_dirty, next_window_id, - pt_to_px, remove_tab, remove_window, ring_bell, set_active_tab, swap_tabs, + pt_to_px, remove_tab, remove_window, ring_bell, set_active_tab, swap_tabs, change_window_tab, x11_window_id ) from .layout import create_layout_object_for, evict_cached_layouts @@ -38,7 +38,7 @@ def add_active_id_to_history(items, item_id, maxlen=64): class Tab: # {{{ - def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None): + def __init__(self, tab_manager, session_tab=None, special_window=None, existing_window=None, cwd_from=None): self._active_window_idx = 0 self.tab_manager_ref = weakref.ref(tab_manager) self.os_window_id = tab_manager.os_window_id @@ -61,7 +61,9 @@ class Tab: # {{{ self.cwd = self.args.directory sl = self.enabled_layouts[0] self._set_current_layout(sl) - if special_window is None: + if existing_window is not None: + self.add_window(existing_window) + elif special_window is None: self.new_window(cwd_from=cwd_from) else: self.new_special_window(special_window) @@ -232,6 +234,11 @@ class Tab: # {{{ ans.fork() return ans + def add_window(self, window, location=None): + self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location) + self.relayout_borders() + return window + def new_window( self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None, location=None, @@ -245,8 +252,7 @@ class Tab: # {{{ overlaid.overlay_window_id = window.id # Must add child before laying out so that resize_pty succeeds get_boss().add_child(window) - self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location) - self.relayout_borders() + self.add_window(window, location=location) return window def new_special_window(self, special_window, location=None, copy_colors_from=None): @@ -265,13 +271,14 @@ class Tab: # {{{ if w.id == old_window_id: return idx - def remove_window(self, window): + def remove_window(self, window, destroy=True): idx = self.previous_active_window_idx(1) next_window_id = None if idx is not None: next_window_id = self.windows[idx].id active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx) - remove_window(self.os_window_id, self.id, window.id) + if destroy: + remove_window(self.os_window_id, self.id, window.id) if window.overlay_for is not None: for idx, q in enumerate(self.windows): if q.id == window.overlay_for: @@ -545,11 +552,29 @@ class TabManager: # {{{ self._set_active_tab(nidx) self.mark_tab_bar_dirty() - def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False): + def new_tab(self, special_window=None, existing_window=None, cwd_from=None, as_neighbor=False): nidx = self.active_tab_idx + 1 idx = len(self.tabs) - self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from)) + if existing_window is not None: + for tab in self.tabs: + if existing_window not in tab: + continue + if len(tab) == 1: + return tab + tab.remove_window(existing_window, destroy=False) + old_tab = tab + break + self._add_tab(Tab(self, special_window=special_window, existing_window=existing_window, cwd_from=cwd_from)) self._set_active_tab(idx) + + if existing_window is not None: + active_tab = self.active_tab + change_window_tab(existing_window.id, + old_tab.os_window_id, old_tab.id, + active_tab.os_window_id, active_tab.id) + existing_window.os_window_id = active_tab.os_window_id + existing_window.tab_id = active_tab.id + if len(self.tabs) > 2 and as_neighbor and idx != nidx: for i in range(idx, nidx, -1): self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i]