From 6dc16174296caff25dabbcbc1d52103fa5c5a2cb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 23 Mar 2022 15:55:11 +0530 Subject: [PATCH] Avoid flicker when starting kittens such as the hints kitten Fixes #4674 --- docs/changelog.rst | 2 ++ kittens/hints/main.py | 3 ++- kittens/runner.py | 1 + kittens/tui/handler.py | 11 ++++++++--- kittens/tui/operations.py | 5 +++++ kittens/unicode_input/main.py | 3 ++- kitty/boss.py | 3 ++- kitty/layout/base.py | 7 +++++-- kitty/parser.c | 1 + kitty/screen.c | 5 +++++ kitty/screen.h | 1 + kitty/tabs.py | 20 +++++++++++++------- kitty/window.py | 7 +++++++ kitty/window_list.py | 35 ++++++++++++++++++++++++++++++++--- 14 files changed, 86 insertions(+), 18 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e2fd27b0c..95e8ff0c2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -57,6 +57,8 @@ Detailed list of changes - Wayland: Fix a regression that broke IME when changing windows/tabs (:iss:`4853`) +- Avoid flicker when starting kittens such as the hints kitten (:iss:`4674`) + 0.24.4 [2022-03-03] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kittens/hints/main.py b/kittens/hints/main.py index 5b423f355..1c79b7809 100644 --- a/kittens/hints/main.py +++ b/kittens/hints/main.py @@ -159,6 +159,7 @@ class Hints(Handler): def initialize(self) -> None: self.init_terminal_state() self.draw_screen() + self.cmd.overlay_ready() def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: changed = False @@ -752,7 +753,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i }[action])(*cmd) -@result_handler(type_of_input='screen-ansi') +@result_handler(type_of_input='screen-ansi', has_ready_notification=True) def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None: if data['customize_processing']: m = load_custom_processor(data['customize_processing']) diff --git a/kittens/runner.py b/kittens/runner.py index 22752702c..942d314a5 100644 --- a/kittens/runner.py +++ b/kittens/runner.py @@ -69,6 +69,7 @@ def create_kitten_handler(kitten: str, orig_args: List[str]) -> Any: ans = partial(m['end'], [kitten] + orig_args) setattr(ans, 'type_of_input', getattr(m['end'], 'type_of_input', None)) setattr(ans, 'no_ui', getattr(m['end'], 'no_ui', False)) + setattr(ans, 'has_ready_notification', getattr(m['end'], 'has_ready_notification', False)) return ans diff --git a/kittens/tui/handler.py b/kittens/tui/handler.py index e707cab8c..312a96c1a 100644 --- a/kittens/tui/handler.py +++ b/kittens/tui/handler.py @@ -206,18 +206,23 @@ class HandleResult: type_of_input: Optional[str] = None no_ui: bool = False - def __init__(self, impl: Callable[..., Any], type_of_input: Optional[str], no_ui: bool): + def __init__(self, impl: Callable[..., Any], type_of_input: Optional[str], no_ui: bool, has_ready_notification: bool): self.impl = impl self.no_ui = no_ui self.type_of_input = type_of_input + self.has_ready_notification = has_ready_notification def __call__(self, args: Sequence[str], data: Any, target_window_id: int, boss: BossType) -> Any: return self.impl(args, data, target_window_id, boss) -def result_handler(type_of_input: Optional[str] = None, no_ui: bool = False) -> Callable[[Callable[..., Any]], HandleResult]: +def result_handler( + type_of_input: Optional[str] = None, + no_ui: bool = False, + has_ready_notification: bool = False +) -> Callable[[Callable[..., Any]], HandleResult]: def wrapper(impl: Callable[..., Any]) -> HandleResult: - return HandleResult(impl, type_of_input, no_ui) + return HandleResult(impl, type_of_input, no_ui, has_ready_notification) return wrapper diff --git a/kittens/tui/operations.py b/kittens/tui/operations.py index add55d472..9b613dbac 100644 --- a/kittens/tui/operations.py +++ b/kittens/tui/operations.py @@ -413,6 +413,11 @@ def restore_colors() -> str: return '\x1b[#Q' +@cmd +def overlay_ready() -> str: + return '\x1bP@kitty-overlay-ready|\x1b\\' + + @cmd def write_to_clipboard(data: Union[str, bytes], use_primary: bool = False) -> str: from base64 import standard_b64encode diff --git a/kittens/unicode_input/main.py b/kittens/unicode_input/main.py index d7c996ca5..6738b6836 100644 --- a/kittens/unicode_input/main.py +++ b/kittens/unicode_input/main.py @@ -377,6 +377,7 @@ class UnicodeInput(Handler): def initialize(self) -> None: self.init_terminal_state() self.draw_screen() + self.cmd.overlay_ready() def draw_title_bar(self) -> None: entries = [] @@ -585,7 +586,7 @@ def main(args: List[str]) -> Optional[str]: return None -@result_handler() +@result_handler(has_ready_notification=True) def handle_result(args: List[str], current_char: str, target_window_id: int, boss: BossType) -> None: w = boss.window_id_map.get(target_window_id) if w is not None: diff --git a/kitty/boss.py b/kitty/boss.py index 7cfe0484f..6dbb31c4a 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1427,7 +1427,8 @@ class Boss: 'OVERLAID_WINDOW_COLS': str(w.screen.columns), }, cwd=w.cwd_of_child, - overlay_for=w.id + overlay_for=w.id, + overlay_behind=end_kitten.has_ready_notification, ), copy_colors_from=w ) diff --git a/kitty/layout/base.py b/kitty/layout/base.py index 212f57dcf..450db9df5 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -271,9 +271,12 @@ class Layout: def move_window_to_group(self, all_windows: WindowList, group: int) -> bool: return all_windows.move_window_group(to_group=group) - def add_window(self, all_windows: WindowList, window: WindowType, location: Optional[str] = None, overlay_for: Optional[int] = None) -> None: + def add_window( + self, all_windows: WindowList, window: WindowType, location: Optional[str] = None, + overlay_for: Optional[int] = None, put_overlay_behind: bool = False + ) -> None: if overlay_for is not None and overlay_for in all_windows: - all_windows.add_window(window, group_of=overlay_for) + all_windows.add_window(window, group_of=overlay_for, head_of_group=put_overlay_behind) return if location == 'neighbor': location = 'after' diff --git a/kitty/parser.c b/kitty/parser.c index 3384af656..5b8598fd9 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -1080,6 +1080,7 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { Py_DECREF(msg); \ } else PyErr_Clear(); + } else IF_SIMPLE_PREFIX("overlay-ready|", screen_handle_overlay_ready) } else IF_SIMPLE_PREFIX("kitten-result|", screen_handle_kitten_result) } else IF_SIMPLE_PREFIX("print|", screen_handle_print) } else IF_SIMPLE_PREFIX("echo|", screen_handle_echo) diff --git a/kitty/screen.c b/kitty/screen.c index c8f761c79..78cc845d5 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2134,6 +2134,11 @@ screen_handle_kitten_result(Screen *self, PyObject *msg) { CALLBACK("handle_kitten_result", "O", msg); } +void +screen_handle_overlay_ready(Screen *self, PyObject *msg) { + CALLBACK("handle_overlay_ready", "O", msg); +} + void screen_request_capabilities(Screen *self, char c, PyObject *q) { static char buf[128]; diff --git a/kitty/screen.h b/kitty/screen.h index 8349ecad7..606acadf2 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -213,6 +213,7 @@ void screen_handle_echo(Screen *, PyObject *cmd); void screen_handle_ssh(Screen *, PyObject *cmd); void screen_handle_askpass(Screen *, PyObject *cmd); void screen_handle_kitten_result(Screen *, PyObject *cmd); +void screen_handle_overlay_ready(Screen *, PyObject *cmd); void screen_designate_charset(Screen *, uint32_t which, uint32_t as); void screen_use_latin1(Screen *, bool); void set_title(Screen *self, PyObject*); diff --git a/kitty/tabs.py b/kitty/tabs.py index a906e71aa..c9e0fb43e 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -63,6 +63,7 @@ class SpecialWindowInstance(NamedTuple): overlay_for: Optional[int] env: Optional[Dict[str, str]] watchers: Optional[Watchers] + overlay_behind: bool def SpecialWindow( @@ -73,9 +74,10 @@ def SpecialWindow( cwd: Optional[str] = None, overlay_for: Optional[int] = None, env: Optional[Dict[str, str]] = None, - watchers: Optional[Watchers] = None + watchers: Optional[Watchers] = None, + overlay_behind: bool = False ) -> SpecialWindowInstance: - return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for, env, watchers) + return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for, env, watchers, overlay_behind) def add_active_id_to_history(items: Deque[int], item_id: int, maxlen: int = 64) -> None: @@ -403,8 +405,8 @@ class Tab: # {{{ ans.fork() return ans - def _add_window(self, window: Window, location: Optional[str] = None, overlay_for: Optional[int] = None) -> None: - self.current_layout.add_window(self.windows, window, location, overlay_for) + def _add_window(self, window: Window, location: Optional[str] = None, overlay_for: Optional[int] = None, overlay_behind: bool = False) -> None: + self.current_layout.add_window(self.windows, window, location, overlay_for, put_overlay_behind=overlay_behind) self.mark_tab_bar_dirty() self.relayout() @@ -422,7 +424,8 @@ class Tab: # {{{ copy_colors_from: Optional[Window] = None, allow_remote_control: bool = False, marker: Optional[str] = None, - watchers: Optional[Watchers] = None + watchers: Optional[Watchers] = None, + overlay_behind: bool = False ) -> Window: child = self.launch_child( use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, allow_remote_control=allow_remote_control) @@ -432,7 +435,7 @@ class Tab: # {{{ ) # Must add child before laying out so that resize_pty succeeds get_boss().add_child(window) - self._add_window(window, location=location, overlay_for=overlay_for) + self._add_window(window, location=location, overlay_for=overlay_for, overlay_behind=overlay_behind) if marker: try: window.set_marker(marker) @@ -453,7 +456,7 @@ class Tab: # {{{ override_title=special_window.override_title, cwd_from=special_window.cwd_from, cwd=special_window.cwd, overlay_for=special_window.overlay_for, env=special_window.env, location=location, copy_colors_from=copy_colors_from, - allow_remote_control=allow_remote_control, watchers=special_window.watchers + allow_remote_control=allow_remote_control, watchers=special_window.watchers, overlay_behind=special_window.overlay_behind ) @ac('win', 'Close all windows in the tab other than the currently active window') @@ -464,6 +467,9 @@ class Tab: # {{{ if window is not active_window: self.remove_window(window) + def move_window_to_top_of_group(self, window: Window) -> bool: + return self.windows.move_window_to_top_of_group(window) + def remove_window(self, window: Window, destroy: bool = True) -> None: self.windows.remove_window(window) if destroy: diff --git a/kitty/window.py b/kitty/window.py index 6ec01db6b..18e523c94 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -940,6 +940,13 @@ class Window: import base64 self.kitten_result: Dict[str, Any] = json.loads(base64.b85decode(msg)) + def handle_overlay_ready(self, msg: str) -> None: + boss = get_boss() + tab = boss.tab_for_window(self) + if tab is None: + return + tab.move_window_to_top_of_group(self) + def handle_remote_askpass(self, msg: str) -> None: from .shm import SharedMemory with SharedMemory(name=msg, readonly=True) as shm: diff --git a/kitty/window_list.py b/kitty/window_list.py index baf66f4d7..a07b83b27 100644 --- a/kitty/window_list.py +++ b/kitty/window_list.py @@ -61,8 +61,23 @@ class WindowGroup: def active_window_id(self) -> int: return self.windows[-1].id if self.windows else 0 - def add_window(self, window: WindowType) -> None: + def add_window(self, window: WindowType, head_of_group: bool = False) -> None: + if head_of_group: + self.windows.insert(0, window) + else: + self.windows.append(window) + + def move_window_to_top_of_group(self, window: WindowType) -> bool: + id + try: + idx = self.windows.index(window) + except ValueError: + return False + if idx == len(self.windows) - 1: + return False + del self.windows[idx] self.windows.append(window) + return True def remove_window(self, window: WindowType) -> None: with suppress(ValueError): @@ -252,6 +267,19 @@ class WindowList: return i return None + def move_window_to_top_of_group(self, window: WindowType) -> bool: + g = self.group_for_window(window) + if g is None: + return False + before = self.active_window + if not g.move_window_to_top_of_group(window): + return False + after = self.active_window + changed = before is not after + if changed: + self.notify_on_active_window_change(before, after) + return changed + def windows_in_group_of(self, x: WindowOrId) -> Iterator[WindowType]: g = self.group_for_window(x) if g is not None: @@ -292,7 +320,8 @@ class WindowList: group_of: Optional[WindowOrId] = None, next_to: Optional[WindowOrId] = None, before: bool = False, - make_active: bool = True + make_active: bool = True, + head_of_group: bool = False, ) -> WindowGroup: self.all_windows.append(window) self.id_map[window.id] = window @@ -318,7 +347,7 @@ class WindowList: self.groups.append(target_group) old_active_window = self.active_window - target_group.add_window(window) + target_group.add_window(window, head_of_group=head_of_group) if make_active: for i, g in enumerate(self.groups): if g is target_group: