This commit is contained in:
Kovid Goyal
2022-05-01 21:23:58 +05:30
20 changed files with 80 additions and 90 deletions

View File

@@ -342,7 +342,8 @@ class Boss:
def match_windows(self, match: str) -> Iterator[Window]: def match_windows(self, match: str) -> Iterator[Window]:
if match == 'all': if match == 'all':
return self.all_windows yield from self.all_windows
return
from .search_query_parser import search from .search_query_parser import search
tab = self.active_tab tab = self.active_tab

View File

@@ -46,7 +46,8 @@ Apply marker to the window this command is run in, rather than the active window
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:
args = payload_get('marker_spec') args = payload_get('marker_spec')
for window in self.windows_for_match_payload(boss, window, payload_get): for window in self.windows_for_match_payload(boss, window, payload_get):
window.set_marker(args) if window:
window.set_marker(args)
return None return None

View File

@@ -36,27 +36,18 @@ Detach the tab this command is run in, rather than the active tab.
return {'match': opts.match, 'target_tab': opts.target_tab, 'self': opts.self} return {'match': opts.match, 'target_tab': opts.target_tab, 'self': opts.self}
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:
match = payload_get('match')
if match:
tabs = list(boss.match_tabs(match))
if not tabs:
raise MatchError(match)
else:
if payload_get('self') and window:
tab = window.tabref() or boss.active_tab
else:
tab = boss.active_tab
tabs = [tab] if tab else []
match = payload_get('target_tab') match = payload_get('target_tab')
kwargs = {} kwargs = {}
if match: if match:
targets = tuple(boss.match_tabs(match)) targets = tuple(boss.match_tabs(match))
if not targets: if not targets:
raise MatchError(match, 'tabs') raise MatchError(match, 'tabs')
kwargs['target_os_window_id'] = targets[0].os_window_id if targets[0]:
kwargs['target_os_window_id'] = targets[0].os_window_id
for tab in tabs: for tab in self.tabs_for_match_payload(boss, window, payload_get):
boss._move_tab_to(tab=tab, **kwargs) if tab:
boss._move_tab_to(tab=tab, **kwargs)
return None return None

View File

@@ -54,7 +54,8 @@ Detach the window this command is run in, rather than the active window.
target_tab_id = tabs[0].id target_tab_id = tabs[0].id
kwargs = {'target_os_window_id': newval} if target_tab_id is None else {'target_tab_id': target_tab_id} kwargs = {'target_os_window_id': newval} if target_tab_id is None else {'target_tab_id': target_tab_id}
for window in windows: for window in windows:
boss._move_window_to(window=window, **kwargs) if window:
boss._move_window_to(window=window, **kwargs)
return None return None

View File

@@ -45,11 +45,12 @@ configured colors.
ans = {k: getattr(opts, k) for k in opts if isinstance(getattr(opts, k), Color)} ans = {k: getattr(opts, k) for k in opts if isinstance(getattr(opts, k), Color)}
if not payload_get('configured'): if not payload_get('configured'):
windows = self.windows_for_match_payload(boss, window, payload_get) windows = self.windows_for_match_payload(boss, window, payload_get)
for k, v in windows[0].current_colors.items(): if windows and windows[0]:
if v is None: for k, v in windows[0].current_colors.items():
ans.pop(k, None) if v is None:
else: ans.pop(k, None)
ans[k] = color_from_int(v) else:
ans[k] = color_from_int(v)
all_keys = natsort_ints(ans) all_keys = natsort_ints(ans)
maxlen = max(map(len, all_keys)) maxlen = max(map(len, all_keys))
return '\n'.join(('{:%ds} {}' % maxlen).format(key, color_as_sharp(ans[key])) for key in all_keys) return '\n'.join(('{:%ds} {}' % maxlen).format(key, color_as_sharp(ans[key])) for key in all_keys)

View File

@@ -80,7 +80,11 @@ Get text from the window this command is run in, rather than the active window.
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:
from kitty.window import CommandOutput from kitty.window import CommandOutput
window = self.windows_for_match_payload(boss, window, payload_get)[0] windows = self.windows_for_match_payload(boss, window, payload_get)
if windows and windows[0]:
window = windows[0]
else:
return None
if payload_get('extent') == 'selection': if payload_get('extent') == 'selection':
ans = window.text_for_selection(as_ansi=payload_get('ansi')) ans = window.text_for_selection(as_ansi=payload_get('ansi'))
elif payload_get('extent') == 'first_cmd_output_on_screen': elif payload_get('extent') == 'first_cmd_output_on_screen':

View File

@@ -4,8 +4,8 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from .base import ( from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
PayloadType, RCOptions, RemoteCommand, ResponseType, Window RemoteCommand, ResponseType, Window
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -38,14 +38,8 @@ class Kitten(RemoteCommand):
return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]} return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]}
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:
windows = [window or boss.active_window]
match = payload_get('match')
if match:
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
retval = None retval = None
for window in windows: for window in self.windows_for_match_payload(boss, window, payload_get):
if window: if window:
retval = boss._run_kitten(payload_get('kitten'), args=tuple(payload_get('args') or ()), window=window) retval = boss._run_kitten(payload_get('kitten'), args=tuple(payload_get('args') or ()), window=window)
break break

View File

@@ -81,9 +81,11 @@ instead of the active tab
if val is None: if val is None:
val = default_value val = default_value
setattr(opts, key, val) setattr(opts, key, val)
tab = self.tabs_for_match_payload(boss, window, payload_get)[0] tabs = self.tabs_for_match_payload(boss, window, payload_get)
w = do_launch(boss, opts, payload_get('args') or [], target_tab=tab) if tabs and tabs[0]:
return None if payload_get('no_response') else str(getattr(w, 'id', 0)) w = do_launch(boss, opts, payload_get('args') or [], target_tab=tabs[0])
return None if payload_get('no_response') else str(getattr(w, 'id', 0))
return None
launch = Launch() launch = Launch()

View File

@@ -105,11 +105,13 @@ the id of the new window will not be printed out.
focus_os_window(os_window_id) focus_os_window(os_window_id)
return None if not aw or payload_get('no_response') else str(aw.id) return None if not aw or payload_get('no_response') else str(aw.id)
tab = self.tabs_for_match_payload(boss, window, payload_get)[0] tabs = self.tabs_for_match_payload(boss, window, payload_get)
ans = tab.new_special_window(w) if tabs and tabs[0]:
if payload_get('keep_focus') and old_window: ans = tabs[0].new_special_window(w)
boss.set_active_window(old_window) if payload_get('keep_focus') and old_window:
return None if payload_get('no_response') else str(ans.id) boss.set_active_window(old_window)
return None if payload_get('no_response') else str(ans.id)
return None
new_window = NewWindow() new_window = NewWindow()

View File

@@ -33,7 +33,8 @@ Apply marker to the window this command is run in, rather than the active window
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:
for window in self.windows_for_match_payload(boss, window, payload_get): for window in self.windows_for_match_payload(boss, window, payload_get):
window.remove_marker() if window:
window.remove_marker()
return None return None

View File

@@ -84,7 +84,7 @@ using this option means that you will not be notified of failures.
windows = self.windows_for_match_payload(boss, window, payload_get) windows = self.windows_for_match_payload(boss, window, payload_get)
if windows: if windows:
ac = payload_get('action') ac = payload_get('action')
for os_window_id in {w.os_window_id for w in windows}: for os_window_id in {w.os_window_id for w in windows if w}:
if ac == 'resize': if ac == 'resize':
boss.resize_os_window( boss.resize_os_window(
os_window_id, width=payload_get('width'), height=payload_get('height'), os_window_id, width=payload_get('width'), height=payload_get('height'),

View File

@@ -5,8 +5,8 @@
from typing import TYPE_CHECKING, Optional, Tuple, Union from typing import TYPE_CHECKING, Optional, Tuple, Union
from .base import ( from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
PayloadType, RCOptions, RemoteCommand, ResponseType, Window RemoteCommand, ResponseType, Window
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -46,17 +46,12 @@ class ScrollWindow(RemoteCommand):
q = int(amt.rstrip('+-plu')) q = int(amt.rstrip('+-plu'))
amount = q * mult, 'p' if pages else ('u' if unscroll else 'l') amount = q * mult, 'p' if pages else ('u' if unscroll else 'l')
return {'match': opts.match, 'amount': amount} # defaults to scroll the window this command is run in
return {'match': opts.match, 'amount': amount, 'self': True}
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:
windows = [window or boss.active_window]
match = payload_get('match')
amt = payload_get('amount') amt = payload_get('amount')
if match: for window in self.windows_for_match_payload(boss, window, payload_get):
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:
if window: if window:
if amt[0] in ('start', 'end'): if amt[0] in ('start', 'end'):
getattr(window, {'start': 'scroll_home'}.get(amt[0], 'scroll_end'))() getattr(window, {'start': 'scroll_home'}.get(amt[0], 'scroll_end'))()

View File

@@ -85,6 +85,11 @@ type=bool-set
Match all windows. Match all windows.
--exclude-active
type=bool-set
Do not send text to the active window, even if it is one of the matched windows.
--stdin --stdin
type=bool-set type=bool-set
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is, Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
@@ -94,11 +99,6 @@ not interpreted for escapes. If stdin is a terminal, you can press :kbd:`Ctrl-D`
--from-file --from-file
Path to a file whose contents you wish to send. Note that in this case the file contents Path to a file whose contents you wish to send. Note that in this case the file contents
are sent as is, not interpreted for escapes. are sent as is, not interpreted for escapes.
--exclude-active
type=bool-set
Do not send text to the active window, even if it is one of the matched windows.
''' '''
no_response = True no_response = True
argspec = '[TEXT TO SEND]' argspec = '[TEXT TO SEND]'
@@ -165,6 +165,7 @@ Do not send text to the active window, even if it is one of the matched windows.
return chain() return chain()
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:
sid = payload_get('session_id', '')
if payload_get('all'): if payload_get('all'):
windows: List[Optional[Window]] = list(boss.all_windows) windows: List[Optional[Window]] = list(boss.all_windows)
else: else:
@@ -172,14 +173,17 @@ Do not send text to the active window, even if it is one of the matched windows.
match = payload_get('match') match = payload_get('match')
if match: if match:
windows = list(boss.match_windows(match)) windows = list(boss.match_windows(match))
if not windows and not sid:
raise MatchError(payload_get('match'))
mt = payload_get('match_tab') mt = payload_get('match_tab')
if mt: if mt:
windows = [] windows = []
tabs = tuple(boss.match_tabs(mt)) tabs = tuple(boss.match_tabs(mt))
if not tabs: if not tabs and not sid:
raise MatchError(payload_get('match_tab'), 'tabs') raise MatchError(payload_get('match_tab'), 'tabs')
for tab in tabs: for tab in tabs:
windows += tuple(tab) if tab:
windows += tuple(tab)
pdata: str = payload_get('data') pdata: str = payload_get('data')
encoding, _, q = pdata.partition(':') encoding, _, q = pdata.partition(':')
session = '' session = ''
@@ -199,7 +203,6 @@ Do not send text to the active window, even if it is one of the matched windows.
raise TypeError(f'Invalid encoding for send-text data: {encoding}') raise TypeError(f'Invalid encoding for send-text data: {encoding}')
exclude_active = payload_get('exclude_active') exclude_active = payload_get('exclude_active')
actual_windows = (w for w in windows if w is not None and (not exclude_active or w is not boss.active_window)) actual_windows = (w for w in windows if w is not None and (not exclude_active or w is not boss.active_window))
sid = payload_get('session_id', '')
def create_or_update_session() -> Session: def create_or_update_session() -> Session:
s = sessions_map.setdefault(sid, Session(sid)) s = sessions_map.setdefault(sid, Session(sid))

View File

@@ -104,7 +104,7 @@ failed, the command will exit with a success code.
return AsyncResponse() return AsyncResponse()
windows = self.windows_for_payload(boss, window, payload_get) windows = self.windows_for_payload(boss, window, payload_get)
os_windows = tuple({w.os_window_id for w in windows}) os_windows = tuple({w.os_window_id for w in windows if w})
layout = payload_get('layout') layout = payload_get('layout')
if data == '-': if data == '-':
path = None path = None

View File

@@ -51,7 +51,7 @@ cause background opacity to be changed in all windows.
if not get_options().dynamic_background_opacity: if not get_options().dynamic_background_opacity:
raise OpacityError('You must turn on the dynamic_background_opacity option in kitty.conf to be able to set background opacity') raise OpacityError('You must turn on the dynamic_background_opacity option in kitty.conf to be able to set background opacity')
windows = self.windows_for_payload(boss, window, payload_get) windows = self.windows_for_payload(boss, window, payload_get)
for os_window_id in {w.os_window_id for w in windows}: for os_window_id in {w.os_window_id for w in windows if w}:
boss._set_os_window_background_opacity(os_window_id, payload_get('opacity')) boss._set_os_window_background_opacity(os_window_id, payload_get('opacity'))
return None return None

View File

@@ -113,14 +113,15 @@ this option, any color arguments are ignored and :option:`kitty @ set-colors --c
if payload_get('reset'): if payload_get('reset'):
colors = {k: int(v) for k, v in boss.startup_colors.items()} colors = {k: int(v) for k, v in boss.startup_colors.items()}
colors['cursor_text_color'] = None if boss.startup_cursor_text_color is None else int(boss.startup_cursor_text_color) colors['cursor_text_color'] = None if boss.startup_cursor_text_color is None else int(boss.startup_cursor_text_color)
profiles = tuple(w.screen.color_profile for w in windows) profiles = tuple(w.screen.color_profile for w in windows if w)
patch_color_profiles(colors, profiles, payload_get('configured')) patch_color_profiles(colors, profiles, payload_get('configured'))
boss.patch_colors(colors, payload_get('configured')) boss.patch_colors(colors, payload_get('configured'))
default_bg_changed = 'background' in colors default_bg_changed = 'background' in colors
for w in windows: for w in windows:
if default_bg_changed: if w:
boss.default_bg_changed_for(w.id) if default_bg_changed:
w.refresh() boss.default_bg_changed_for(w.id)
w.refresh()
return None return None

View File

@@ -112,10 +112,11 @@ windows).
patch_configured_edges(get_options(), settings) patch_configured_edges(get_options(), settings)
for w in windows: for w in windows:
patch_window_edges(w, settings) if w:
tab = w.tabref() patch_window_edges(w, settings)
if tab is not None: tab = w.tabref()
dirtied_tabs[tab.id] = tab if tab is not None:
dirtied_tabs[tab.id] = tab
for tab in dirtied_tabs.values(): for tab in dirtied_tabs.values():
tab.relayout() tab.relayout()

View File

@@ -113,7 +113,8 @@ failed, the command will exit with a success code.
alpha = float(payload_get('alpha', '-1')) alpha = float(payload_get('alpha', '-1'))
position = payload_get('position') or '' position = payload_get('position') or ''
for window in self.windows_for_match_payload(boss, window, payload_get): for window in self.windows_for_match_payload(boss, window, payload_get):
window.set_logo(path, position, alpha) if window:
window.set_logo(path, position, alpha)
return None return None

View File

@@ -4,8 +4,8 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from .base import ( from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
PayloadType, RCOptions, RemoteCommand, ResponseType, Window RemoteCommand, ResponseType, Window
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -40,16 +40,12 @@ again. If you want to allow other programs to change it afterwards, use this opt
title = ' '.join(args) title = ' '.join(args)
if title: if title:
ans['title'] = title ans['title'] = title
# defaults to set the window title this command is run in
ans['self'] = True
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:
windows = [window or boss.active_window] for window in self.windows_for_match_payload(boss, window, payload_get):
match = payload_get('match')
if match:
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:
if window: if window:
if payload_get('temporary'): if payload_get('temporary'):
window.override_title = None window.override_title = None

View File

@@ -4,8 +4,8 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from .base import ( from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
PayloadType, RCOptions, RemoteCommand, ResponseType, Window RemoteCommand, ResponseType, Window
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -33,18 +33,13 @@ class SignalChild(RemoteCommand):
argspec = '[SIGNAL_NAME ...]' argspec = '[SIGNAL_NAME ...]'
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:
return {'match': opts.match, 'signals': [x.upper() for x in args] or ['SIGINT']} # defaults to signal the window this command is run in
return {'match': opts.match, 'self': True, 'signals': [x.upper() for x in args] or ['SIGINT']}
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:
import signal import signal
windows = [window or boss.active_window]
match = payload_get('match')
if match:
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
signals = tuple(getattr(signal, x) for x in payload_get('signals')) signals = tuple(getattr(signal, x) for x in payload_get('signals'))
for window in windows: for window in self.windows_for_match_payload(boss, window, payload_get):
if window: if window:
window.signal_child(*signals) window.signal_child(*signals)
return None return None