diff --git a/kitty/boss.py b/kitty/boss.py index 2f05599e1..81710f51d 100755 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -34,7 +34,7 @@ from .fast_data_types import ( current_application_quit_request, current_os_window, destroy_global_data, focus_os_window, get_clipboard_string, get_options, get_os_window_size, global_font_size, mark_os_window_for_close, os_window_font_size, - patch_global_colors, safe_pipe, set_application_quit_request, + patch_global_colors, ring_bell, safe_pipe, set_application_quit_request, set_background_image, set_boss, set_clipboard_string, set_in_sequence_mode, set_options, set_os_window_size, thread_write, toggle_fullscreen, toggle_maximized @@ -158,6 +158,7 @@ class Boss: self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)} self.startup_cursor_text_color = opts.cursor_text_color self.pending_sequences: Optional[SubSequenceMap] = None + self.default_pending_action: Optional[KeyAction] = None self.cached_values = cached_values self.os_window_map: Dict[int, TabManager] = {} self.os_window_death_actions: Dict[int, Callable[[], None]] = {} @@ -758,14 +759,18 @@ class Boss: if t is not None: return t.active_window + def set_pending_sequences(self, sequences: SubSequenceMap, default_pending_action: Optional[KeyAction] = None) -> None: + self.pending_sequences = sequences + self.default_pending_action = default_pending_action + set_in_sequence_mode(True) + def dispatch_possible_special_key(self, ev: KeyEvent) -> bool: # Handles shortcuts, return True if the key was consumed key_action = get_shortcut(self.keymap, ev) if key_action is None: sequences = get_shortcut(get_options().sequence_map, ev) if sequences and not isinstance(sequences, KeyAction): - self.pending_sequences = sequences - set_in_sequence_mode(True) + self.set_pending_sequences(sequences) return True if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev): return True @@ -790,11 +795,37 @@ class Boss: if remaining: self.pending_sequences = remaining else: - self.pending_sequences = None + matched_action = matched_action or self.default_pending_action + self.pending_sequences = self.default_pending_action = None set_in_sequence_mode(False) if matched_action is not None: self.dispatch_action(matched_action) + @ac('win', ''' + Focus a visible window by pressing the number of the window. Window numbers are displayed + over the windows for easy selection in this mode. + ''') + def focus_visible_window(self) -> None: + tab = self.active_tab + if tab is not None: + pending_sequences: SubSequenceMap = {} + for idx, window in tab.windows.iter_visible_windows_with_number(): + window.screen.set_window_number(idx + 1) + pending_sequences[(SingleKey(key=ord(str(idx + 1))),)] = KeyAction('focus_visible_window_trigger', (idx,)) + if pending_sequences: + self.set_pending_sequences(pending_sequences, default_pending_action=KeyAction('focus_visible_window_trigger')) + else: + if get_options().enable_audio_bell: + ring_bell() + + def focus_visible_window_trigger(self, idx: int = -1) -> None: + tab = self.active_tab + if tab is not None: + for window in tab: + window.screen.set_window_number() + if idx > -1: + tab.nth_window(idx) + @ac('win', ''' Resize the active window interactively diff --git a/kitty/window_list.py b/kitty/window_list.py index a2a2a33cb..9d8812ae7 100644 --- a/kitty/window_list.py +++ b/kitty/window_list.py @@ -6,9 +6,7 @@ import weakref from collections import deque from contextlib import suppress from itertools import count -from typing import ( - Any, Deque, Dict, Generator, Iterator, List, Optional, Tuple, Union -) +from typing import Any, Deque, Dict, Iterator, List, Optional, Tuple, Union from .types import WindowGeometry from .typing import EdgeLiteral, TabType, WindowType @@ -202,7 +200,7 @@ class WindowList: def change_tab(self, tab: TabType) -> None: self.tabref = weakref.ref(tab) - def iter_windows_with_visibility(self) -> Generator[Tuple[WindowType, bool], None, None]: + def iter_windows_with_visibility(self) -> Iterator[Tuple[WindowType, bool]]: for g in self.groups: aw = g.active_window_id for window in g: @@ -211,6 +209,16 @@ class WindowList: def iter_all_layoutable_groups(self, only_visible: bool = False) -> Iterator[WindowGroup]: return iter(g for g in self.groups if g.is_visible_in_layout) if only_visible else iter(self.groups) + def iter_visible_windows_with_number(self) -> Iterator[Tuple[int, WindowType]]: + for i, g in enumerate(self.groups): + if g.is_visible_in_layout: + aw = g.active_window_id + for window in g: + if window in g: + if window.id == aw: + yield i, window + break + def make_previous_group_active(self, which: int = 1, notify: bool = True) -> None: which = max(1, which) gid_map = {g.id: i for i, g in enumerate(self.groups)}