diff --git a/kitty/boss.py b/kitty/boss.py index 71ac7062c..05a593907 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -106,12 +106,36 @@ class Boss: def match_windows(self, match): field, exp = match.split(':', 1) pat = re.compile(exp) - for os_window_id, tm in self.os_window_map.items(): + for tm in self.os_window_map.values(): for tab in tm: for window in tab: if window.matches(field, pat): yield window + def tab_for_window(self, window): + for tm in self.os_window_map.values(): + for tab in tm: + for w in tab: + if w.id == window.id: + return tab + + def match_tabs(self, match): + field, exp = match.split(':', 1) + pat = re.compile(exp) + tms = tuple(self.os_window_map.values()) + found = False + if field in ('title', 'id'): + for tm in tms: + for tab in tm: + if tab.matches(field, pat): + yield tab + found = True + if not found: + tabs = {self.tab_for_window(w) for w in self.match_windows(match)} + for tab in tabs: + if tab: + yield tab + def _new_os_window(self, args, cwd_from=None): sw = self.args_to_special_window(args, cwd_from) if args else None startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from) diff --git a/kitty/remote_control.py b/kitty/remote_control.py index 700d751aa..0dc4cc764 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -52,13 +52,23 @@ def ls(boss, window): MATCH_WINDOW_OPTION = '''\ ---match +--match -m The window to match. Match specifications are of the form: |_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline. You can use the |_ ls| command to get a list of windows. Note that for numeric fields such as id and pid the expression is interpreted as a number, not a regular expression. ''' +MATCH_TAB_OPTION = '''\ +--match -m +The tab to match. Match specifications are of the form: +|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline. +You can use the |_ ls| command to get a list of tabs. Note that for +numeric fields such as id and pid the expression is interpreted as a number, +not a regular expression. When using title or id, first a matching tab is +looked for and if not found a matching window is looked for, and the tab +for that window is used. +''' @cmd( @@ -67,7 +77,8 @@ not a regular expression. ' escaping rules. So you can use escapes like |_ \\x1b| to send control codes' ' and |_ \\u21fa| to send unicode characters. If you use the |_ --match| option' ' the text will be sent to all matched windows. By default, text is sent to' - ' only the currently active window.', + ' only the currently active window. Note that sending more than ~ 2KB of text' + ' will not work, so split up large texts into multiple invocations.', options_spec=MATCH_WINDOW_OPTION, no_response=True ) @@ -109,6 +120,31 @@ def set_window_title(boss, window, payload): window.set_title(payload['title']) +@cmd( + 'Set the tab title', + 'Set the title for the specified tab(s). If you use the |_ --match| option' + ' the title will be set for all matched tabs. By default, only the tab' + ' in which the command is run is affected. If you do not specify a title, the' + ' title of the currently active window in the tab is used.', + options_spec=MATCH_TAB_OPTION +) +def cmd_set_tab_title(global_opts, opts, args): + return {'title': ' '.join(args), 'match': opts.match} + + +def set_tab_title(boss, window, payload): + match = payload['match'] + if match: + tabs = tuple(boss.match_tabs(match)) + if not tabs: + raise ValueError('No matching windows for expression: {}'.format(match)) + else: + tabs = [boss.tab_for_window(window) if window else boss.active_tab] + for tab in tabs: + if tab: + tab.set_title(payload['title']) + + cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')} diff --git a/kitty/tabs.py b/kitty/tabs.py index 4adc7d902..0a39182ce 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -75,6 +75,12 @@ class Tab: # {{{ def title(self): return getattr(self.active_window, 'title', appname) + def set_title(self, title): + self.name = title or '' + tm = self.tab_manager_ref() + if tm is not None: + tm.title_changed(self.name) + def title_changed(self, window): if window is self.active_window: tm = self.tab_manager_ref() @@ -206,6 +212,13 @@ class Tab: # {{{ for w in self: yield w.as_dict() + def matches(self, field, pat): + if field == 'id': + return pat.pattern == str(self.id) + if field == 'title': + return pat.search(self.name or self.title) is not None + return False + def __iter__(self): yield from iter(self.windows)