Exclude the currently active window when visually selecting

This commit is contained in:
Kovid Goyal 2021-10-31 11:37:38 +05:30
parent 8458b9e7d6
commit dc09a5183a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 43 additions and 14 deletions

View File

@ -9,7 +9,8 @@ from contextlib import suppress
from functools import partial from functools import partial
from gettext import gettext as _ from gettext import gettext as _
from typing import ( from typing import (
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast Any, Callable, Container, Dict, Iterable, Iterator, List, Optional, Tuple,
Union, cast
) )
from weakref import WeakValueDictionary from weakref import WeakValueDictionary
@ -859,29 +860,38 @@ class Boss:
self.current_visual_select.cancel() self.current_visual_select.cancel()
self.current_visual_select = None self.current_visual_select = None
def visual_window_select_action(self, tab: Tab, callback: Callable[[Optional[Tab], Optional[Window]], None], choose_msg: str) -> None: def visual_window_select_action(
self, tab: Tab,
callback: Callable[[Optional[Tab], Optional[Window]], None],
choose_msg: str,
only_window_ids: Container[int] = ()
) -> None:
self.cancel_current_visual_select() self.cancel_current_visual_select()
tm = tab.tab_manager_ref() tm = tab.tab_manager_ref()
if tm is not None: if tm is not None:
tm.set_active_tab(tab) tm.set_active_tab(tab)
self.current_visual_select = VisualSelect(tab.id, tab.os_window_id, choose_msg, callback) self.current_visual_select = VisualSelect(tab.id, tab.os_window_id, choose_msg, callback)
if tab.current_layout.only_active_window_visible: if tab.current_layout.only_active_window_visible:
self.select_window_in_tab_using_overlay(tab, choose_msg) self.select_window_in_tab_using_overlay(tab, choose_msg, only_window_ids)
return return
pending_sequences: SubSequenceMap = {} pending_sequences: SubSequenceMap = {}
fmap = get_name_to_functional_number_map() fmap = get_name_to_functional_number_map()
num = 0
for idx, window in tab.windows.iter_windows_with_number(only_visible=True): for idx, window in tab.windows.iter_windows_with_number(only_visible=True):
if idx > 9: if only_window_ids and window.id not in only_window_ids:
break continue
num = idx + 1
ac = KeyAction('visual_window_select_action_trigger', (window.id,)) ac = KeyAction('visual_window_select_action_trigger', (window.id,))
if num == 10: num += 1
is_last = num == 10
if is_last:
num = 0 num = 0
window.screen.set_window_number(num) window.screen.set_window_number(num)
self.current_visual_select.window_ids.append(window.id) self.current_visual_select.window_ids.append(window.id)
for mods in (0, GLFW_MOD_CONTROL, GLFW_MOD_CONTROL | GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOD_ALT, GLFW_MOD_SHIFT): for mods in (0, GLFW_MOD_CONTROL, GLFW_MOD_CONTROL | GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOD_ALT, GLFW_MOD_SHIFT):
pending_sequences[(SingleKey(mods=mods, key=ord(str(num))),)] = ac pending_sequences[(SingleKey(mods=mods, key=ord(str(num))),)] = ac
pending_sequences[(SingleKey(mods=mods, key=fmap[f'KP_{num}']),)] = ac pending_sequences[(SingleKey(mods=mods, key=fmap[f'KP_{num}']),)] = ac
if is_last:
break
if len(self.current_visual_select.window_ids) > 1: if len(self.current_visual_select.window_ids) > 1:
self.set_pending_sequences(pending_sequences, default_pending_action=KeyAction('visual_window_select_action_trigger', (0,))) self.set_pending_sequences(pending_sequences, default_pending_action=KeyAction('visual_window_select_action_trigger', (0,)))
redirect_mouse_handling(True) redirect_mouse_handling(True)
@ -914,8 +924,9 @@ class Boss:
ev = WindowSystemMouseEvent(in_tab_bar, window_id, action, modifiers, button, currently_pressed_button, x, y) ev = WindowSystemMouseEvent(in_tab_bar, window_id, action, modifiers, button, currently_pressed_button, x, y)
self.mouse_handler(ev) self.mouse_handler(ev)
def select_window_in_tab_using_overlay(self, tab: Tab, msg: str) -> None: def select_window_in_tab_using_overlay(self, tab: Tab, msg: str, only_window_ids: Container[int] = ()) -> None:
windows = tuple((w.id, w.title) for i, w in tab.windows.iter_windows_with_number(only_visible=False)) windows = tuple((w.id, w.title) for i, w in tab.windows.iter_windows_with_number(only_visible=False)
if not only_window_ids or w.id in only_window_ids)
if len(windows) < 1: if len(windows) < 1:
self.visual_window_select_action_trigger(windows[0][0] if windows else 0) self.visual_window_select_action_trigger(windows[0][0] if windows else 0)
if get_options().enable_audio_bell: if get_options().enable_audio_bell:

View File

@ -19,6 +19,7 @@ class SelectWindow(RemoteCommand):
match: The tab to open the new window in match: The tab to open the new window in
self: Boolean, if True use tab the command was run in self: Boolean, if True use tab the command was run in
title: A title for this selection title: A title for this selection
exclude_active: Exclude the currently active window from the list to pick
''' '''
short_desc = 'Visually select a window in the specified tab' short_desc = 'Visually select a window in the specified tab'
@ -41,11 +42,16 @@ instead of the active tab.
--title --title
A title that will be displayed to the user to describe what this selection is for A title that will be displayed to the user to describe what this selection is for
--exclude-active
type=bool-set
Exclude the currently active window from the list of windows to pick
''' '''
is_asynchronous = True is_asynchronous = True
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
ans = {'self': opts.self, 'match': opts.match, 'title': opts.title} ans = {'self': opts.self, 'match': opts.match, 'title': opts.title, 'exclude_active': opts.exclude_active}
return ans return ans
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType: def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
@ -58,7 +64,11 @@ A title that will be displayed to the user to describe what this selection is fo
responder.send_error('No window selected') responder.send_error('No window selected')
for tab in self.tabs_for_match_payload(boss, window, payload_get): for tab in self.tabs_for_match_payload(boss, window, payload_get):
if tab: if tab:
boss.visual_window_select_action(tab, callback, payload_get('title') or 'Choose window') if payload_get('exclude_active'):
wids = tab.all_window_ids_except_active_window
else:
wids = set()
boss.visual_window_select_action(tab, callback, payload_get('title') or 'Choose window', only_window_ids=wids)
break break
return no_response return no_response

View File

@ -11,7 +11,7 @@ from operator import attrgetter
from time import monotonic from time import monotonic
from typing import ( from typing import (
Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple, Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple,
Optional, Pattern, Sequence, Tuple, Union, cast Optional, Pattern, Sequence, Set, Tuple, Union, cast
) )
from .borders import Border, Borders from .borders import Border, Borders
@ -596,6 +596,14 @@ class Tab: # {{{
if self.current_layout.move_window_to_group(self.windows, group.id): if self.current_layout.move_window_to_group(self.windows, group.id):
self.relayout() self.relayout()
@property
def all_window_ids_except_active_window(self) -> Set[int]:
all_window_ids = {w.id for w in self}
aw = self.active_window
if aw is not None:
all_window_ids.discard(aw.id)
return all_window_ids
@ac('win', ''' @ac('win', '''
Focus a visible window by pressing the number of the window. Window numbers are displayed Focus a visible window by pressing the number of the window. Window numbers are displayed
over the windows for easy selection in this mode. over the windows for easy selection in this mode.
@ -605,14 +613,14 @@ class Tab: # {{{
if tab and window: if tab and window:
tab.set_active_window(window) tab.set_active_window(window)
get_boss().visual_window_select_action(self, callback, 'Choose window to switch to') get_boss().visual_window_select_action(self, callback, 'Choose window to switch to', only_window_ids=self.all_window_ids_except_active_window)
@ac('win', 'Swap the current window with another window in the current tab, selected visually') @ac('win', 'Swap the current window with another window in the current tab, selected visually')
def swap_with_window(self) -> None: def swap_with_window(self) -> None:
def callback(tab: Optional[Tab], window: Optional[Window]) -> None: def callback(tab: Optional[Tab], window: Optional[Window]) -> None:
if tab and window: if tab and window:
tab.swap_active_window_with(window.id) tab.swap_active_window_with(window.id)
get_boss().visual_window_select_action(self, callback, 'Choose window to swap with') get_boss().visual_window_select_action(self, callback, 'Choose window to swap with', only_window_ids=self.all_window_ids_except_active_window)
@ac('win', 'Move active window to the top (make it the first window)') @ac('win', 'Move active window to the top (make it the first window)')
def move_window_to_top(self) -> None: def move_window_to_top(self) -> None: