mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-15 21:17:49 +02:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b65119b8a9 | ||
|
|
86703da917 | ||
|
|
610e9afdf0 | ||
|
|
e3af9f68d3 | ||
|
|
747ac85e7c | ||
|
|
8c23f9e526 | ||
|
|
e6e339fcd3 | ||
|
|
f04680ac47 | ||
|
|
90985cc846 | ||
|
|
63917944f2 | ||
|
|
793605f80f | ||
|
|
93a281c7e3 | ||
|
|
8f1c6c4d74 | ||
|
|
1610dba7ab | ||
|
|
d9d419991c | ||
|
|
f9e86b19aa | ||
|
|
fffb976e43 | ||
|
|
0da566b49f | ||
|
|
da55717d20 | ||
|
|
254836902c | ||
|
|
d01f8d1865 | ||
|
|
a46c3f7007 | ||
|
|
4e58062025 |
@@ -4,6 +4,37 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.17.2 [2020-03-29]
|
||||
--------------------
|
||||
|
||||
- Add a :option:`launch --watcher` option that allows defining callbacks
|
||||
that are called for various events in the window's life-cycle (:iss:`2440`)
|
||||
|
||||
- Fix a regression in 0.17 that broke drawing of borders with non-minimal
|
||||
borders (:iss:`2474`)
|
||||
|
||||
- Hints kitten: Allow copying to primary selection as well as clipboard
|
||||
(:pull:`2487`)
|
||||
|
||||
- Add a new mappable action ``close_other_windows_in_tab`` to close all but the
|
||||
active window (:iss:`2484`)
|
||||
|
||||
- Hints kitten: Adjust the default regex used to detect line numbers to handle
|
||||
line+column numbers (:iss:`2268`)
|
||||
|
||||
- Fix blank space at the start of tab bar in the powerline style when first tab is
|
||||
inactive (:iss:`2478`)
|
||||
|
||||
- Fix regression causing incorrect rendering of separators in tab bar when
|
||||
defining a tab bar background color (:pull:`2480`)
|
||||
|
||||
- Fix a regression in 0.17 that broke the kitty @ launch remote command and
|
||||
also broke the --tab-title option when creating a new tab. (:iss:`2488`)
|
||||
|
||||
- Linux: Fix selection of fonts with multiple width variants not preferring
|
||||
the normal width faces (:iss:`2491`)
|
||||
|
||||
|
||||
0.17.1 [2020-03-24]
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -192,6 +192,12 @@ Similarly, you can detach the current tab, with::
|
||||
# asks which OS Window to move the tab into
|
||||
map ctrl+f4 detach_tab ask
|
||||
|
||||
Finally, you can define a shortcut to close all windows in a tab other than
|
||||
the currently active window::
|
||||
|
||||
map f9 close_other_windows_in_tab
|
||||
|
||||
|
||||
Other keyboard shortcuts
|
||||
----------------------------------
|
||||
|
||||
|
||||
@@ -61,6 +61,35 @@ currently active kitty window. For example::
|
||||
map f1 launch my-program @active-kitty-window-id
|
||||
|
||||
|
||||
Watching launched windows
|
||||
---------------------------
|
||||
|
||||
The :option:`launch --watcher` option allows you to specify python functions
|
||||
that will be called at specific events, such as when the window is resized or
|
||||
closed. Simply specify the path to a python module that specifies callback
|
||||
functions for the events you are interested in, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_resize(boss, window, data):
|
||||
# Here data will contain old_geometry and new_geometry
|
||||
|
||||
def on_close(boss, window, data):
|
||||
# called when window is closed, typically when the program running in
|
||||
# it exits.
|
||||
|
||||
|
||||
Every callback is passed a reference to the global ``Boss`` object as well as
|
||||
the ``Window`` object the action is occurring on. The ``data`` object is
|
||||
mapping that contains event dependent data. Some useful methods and attributes
|
||||
for the ``Window`` object are: ``as_text(as_ans=False, add_history=False,
|
||||
add_wrap_markers=False, alternate_screen=False)`` with which you can get the
|
||||
contents of the window and its scrollback buffer. Similarly,
|
||||
``window.child.pid`` is the PID of the processes that was launched
|
||||
in the window and ``window.id`` is the internal kitty ``id`` of the
|
||||
window.
|
||||
|
||||
|
||||
Syntax reference
|
||||
------------------
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ try:
|
||||
except ImportError:
|
||||
has_highlighter = False
|
||||
|
||||
def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]:
|
||||
return ''
|
||||
|
||||
|
||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||
ESCAPE = K['ESCAPE']
|
||||
|
||||
@@ -21,7 +21,7 @@ from kitty.key_encoding import (
|
||||
KeyEvent, backspace_key, enter_key, key_defs as K
|
||||
)
|
||||
from kitty.typing import BossType, KittyCommonOpts
|
||||
from kitty.utils import ScreenSize, screen_size_function
|
||||
from kitty.utils import ScreenSize, screen_size_function, set_primary_selection
|
||||
|
||||
from ..tui.handler import Handler, result_handler
|
||||
from ..tui.loop import Loop
|
||||
@@ -349,7 +349,7 @@ def parse_input(text: str) -> str:
|
||||
def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
|
||||
regex = args.regex
|
||||
if regex == DEFAULT_REGEX:
|
||||
regex = r'(?P<path>(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
|
||||
regex = r'(?P<path>(?:\S*/\S+?)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
|
||||
yield from mark(regex, [brackets, quotes], text, args)
|
||||
|
||||
|
||||
@@ -360,12 +360,8 @@ def load_custom_processor(customize_processing: str) -> Any:
|
||||
return {k: getattr(m, k) for k in dir(m)}
|
||||
if customize_processing == '::linenum::':
|
||||
return {'mark': linenum_marks, 'handle_result': linenum_handle_result}
|
||||
from kitty.constants import config_dir
|
||||
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing))
|
||||
if os.path.isabs(customize_processing):
|
||||
custom_path = customize_processing
|
||||
else:
|
||||
custom_path = os.path.join(config_dir, customize_processing)
|
||||
from kitty.constants import resolve_custom_file
|
||||
custom_path = resolve_custom_file(customize_processing)
|
||||
import runpy
|
||||
return runpy.run_path(custom_path, run_name='__main__')
|
||||
|
||||
@@ -413,9 +409,11 @@ OPTIONS = r'''
|
||||
type=list
|
||||
What program to use to open matched text. Defaults to the default open program
|
||||
for the operating system. Use a value of :file:`-` to paste the match into the
|
||||
terminal window instead. A value of :file:`@` will copy the match to the clipboard.
|
||||
A value of :file:`default` will run the default open program. Can be specified
|
||||
multiple times to run multiple programs.
|
||||
terminal window instead. A value of :file:`@` will copy the match to the
|
||||
clipboard. A value of :file:`*` will copy the match to the primary selection
|
||||
(on systems that support primary selections). A value of :file:`default` will
|
||||
run the default open program. Can be specified multiple times to run multiple
|
||||
programs.
|
||||
|
||||
|
||||
--type
|
||||
@@ -629,6 +627,8 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
|
||||
w.paste(joined_text())
|
||||
elif program == '@':
|
||||
set_clipboard_string(joined_text())
|
||||
elif program == '*':
|
||||
set_primary_selection(joined_text())
|
||||
else:
|
||||
cwd = None
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
|
||||
@@ -96,7 +96,9 @@ def opt(text: str) -> str:
|
||||
|
||||
|
||||
def option(x: str) -> str:
|
||||
idx = x.find('-')
|
||||
idx = x.rfind('--')
|
||||
if idx < 0:
|
||||
idx = x.find('-')
|
||||
if idx > -1:
|
||||
x = x[idx:]
|
||||
parts = map(bold, x.split())
|
||||
@@ -119,6 +121,10 @@ def file(x: str) -> str:
|
||||
return italic(x)
|
||||
|
||||
|
||||
def doc(x: str) -> str:
|
||||
return f'https://sw.kovidgoyal.net/kitty/{x}.html'
|
||||
|
||||
|
||||
OptionSpecSeq = List[Union[str, OptionDict]]
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import re
|
||||
from functools import partial
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterable, List, Match, Optional, Sequence,
|
||||
Set, Tuple, Union, cast, get_type_hints
|
||||
Set, Tuple, Union, get_type_hints
|
||||
)
|
||||
|
||||
from .utils import to_bool
|
||||
@@ -156,7 +156,7 @@ def remove_markup(text: str) -> str:
|
||||
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
|
||||
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
|
||||
}[m.group(2)]
|
||||
return cast(str, m.group(2))
|
||||
return str(m.group(2))
|
||||
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
||||
|
||||
|
||||
appname: str = 'kitty'
|
||||
version: Version = Version(0, 17, 1)
|
||||
version: Version = Version(0, 17, 2)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
@@ -198,3 +198,10 @@ def running_in_kitty(set_val: Optional[bool] = None) -> bool:
|
||||
if set_val is not None:
|
||||
setattr(running_in_kitty, 'ans', set_val)
|
||||
return bool(getattr(running_in_kitty, 'ans', False))
|
||||
|
||||
|
||||
def resolve_custom_file(path: str) -> str:
|
||||
path = os.path.expandvars(os.path.expanduser(path))
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(config_dir, path)
|
||||
return path
|
||||
|
||||
@@ -323,6 +323,7 @@ FC_MONO: int = 100
|
||||
FC_DUAL: int
|
||||
FC_WEIGHT_REGULAR: int
|
||||
FC_WEIGHT_BOLD: int
|
||||
FC_WIDTH_NORMAL: int
|
||||
FC_SLANT_ROMAN: int
|
||||
FC_SLANT_ITALIC: int
|
||||
BORDERS_PROGRAM: int
|
||||
@@ -402,6 +403,7 @@ class FontConfigPattern(TypedDict):
|
||||
style: str
|
||||
spacing: str
|
||||
weight: int
|
||||
width: int
|
||||
slant: int
|
||||
hint_style: int
|
||||
subpixel: int
|
||||
|
||||
@@ -47,6 +47,7 @@ pattern_as_dict(FcPattern *pat) {
|
||||
S(FC_FULLNAME, full_name);
|
||||
S(FC_POSTSCRIPT_NAME, postscript_name);
|
||||
I(FC_WEIGHT, weight);
|
||||
I(FC_WIDTH, width)
|
||||
I(FC_SLANT, slant);
|
||||
I(FC_HINT_STYLE, hint_style);
|
||||
I(FC_INDEX, index);
|
||||
@@ -247,5 +248,7 @@ init_fontconfig_library(PyObject *module) {
|
||||
PyModule_AddIntMacro(module, FC_DUAL);
|
||||
PyModule_AddIntMacro(module, FC_MONO);
|
||||
PyModule_AddIntMacro(module, FC_CHARCELL);
|
||||
PyModule_AddIntMacro(module, FC_WIDTH_NORMAL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Dict, Generator, List, Optional, Tuple, cast
|
||||
|
||||
from kitty.fast_data_types import (
|
||||
FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD,
|
||||
FC_WEIGHT_REGULAR, fc_list, fc_match as fc_match_impl
|
||||
FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, fc_list, fc_match as fc_match_impl
|
||||
)
|
||||
from kitty.options_stub import Options
|
||||
from kitty.typing import FontConfigPattern
|
||||
@@ -73,11 +73,13 @@ def find_best_match(family: str, bold: bool = False, italic: bool = False, monos
|
||||
q = family_name_to_key(family)
|
||||
font_map = all_fonts_map(monospaced)
|
||||
|
||||
def score(candidate: FontConfigPattern) -> Tuple[int, int]:
|
||||
def score(candidate: FontConfigPattern) -> Tuple[int, int, int]:
|
||||
bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0))
|
||||
italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0))
|
||||
monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1
|
||||
return bold_score + italic_score, monospace_match
|
||||
width_score = abs(candidate.get('width', FC_WIDTH_NORMAL) - FC_WIDTH_NORMAL)
|
||||
|
||||
return bold_score + italic_score, monospace_match, width_score
|
||||
|
||||
# First look for an exact match
|
||||
for selector in ('ps_map', 'full_map', 'family_map'):
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
from functools import lru_cache
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
from .boss import Boss
|
||||
from .child import Child
|
||||
from .cli import parse_args
|
||||
from .cli_stub import LaunchCLIOptions
|
||||
from .constants import resolve_custom_file
|
||||
from .fast_data_types import set_clipboard_string
|
||||
from .utils import set_primary_selection
|
||||
from .window import Window
|
||||
from .boss import Boss
|
||||
from .tabs import Tab
|
||||
from .utils import set_primary_selection
|
||||
from .window import Watchers, Window
|
||||
|
||||
try:
|
||||
from typing import TypedDict
|
||||
@@ -148,6 +149,14 @@ defaults to :code:`kitty`.
|
||||
--os-window-name
|
||||
Set the WM_NAME property on X11 for the newly created OS Window when using
|
||||
:option:`launch --type`=os-window. Defaults to :option:`launch --os-window-class`.
|
||||
|
||||
|
||||
--watcher -w
|
||||
type=list
|
||||
Path to a python file. Appropriately named functions in this file will be called
|
||||
for various events, such as when the window is resized or closed. See the section
|
||||
on watchers in the launch command documentation :doc:`launch`. Relative paths are
|
||||
resolved relative to the kitty config directory.
|
||||
'''
|
||||
|
||||
|
||||
@@ -176,7 +185,7 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab]
|
||||
tm = boss.active_tab_manager
|
||||
if tm:
|
||||
tab: Optional[Tab] = tm.new_tab(empty_tab=True, location=opts.location)
|
||||
if opts.tab_title and tab:
|
||||
if opts.tab_title and tab is not None:
|
||||
tab.set_title(opts.tab_title)
|
||||
else:
|
||||
tab = None
|
||||
@@ -184,7 +193,7 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab]
|
||||
oswid = boss.add_os_window(wclass=opts.os_window_class, wname=opts.os_window_name)
|
||||
tm = boss.os_window_map[oswid]
|
||||
tab = tm.new_tab(empty_tab=True)
|
||||
if opts.tab_title:
|
||||
if opts.tab_title and tab is not None:
|
||||
tab.set_title(opts.tab_title)
|
||||
else:
|
||||
tab = target_tab or boss.active_tab
|
||||
@@ -192,6 +201,23 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab]
|
||||
return tab
|
||||
|
||||
|
||||
def load_watch_modules(opts: LaunchCLIOptions) -> Optional[Watchers]:
|
||||
if not opts.watcher:
|
||||
return None
|
||||
import runpy
|
||||
ans = Watchers()
|
||||
for path in opts.watcher:
|
||||
path = resolve_custom_file(path)
|
||||
m = runpy.run_path(path, run_name='__kitty_watcher__')
|
||||
w = m.get('on_close')
|
||||
if callable(w):
|
||||
ans.on_close.append(w)
|
||||
w = m.get('on_resize')
|
||||
if callable(w):
|
||||
ans.on_resize.append(w)
|
||||
return ans
|
||||
|
||||
|
||||
class LaunchKwds(TypedDict):
|
||||
|
||||
allow_remote_control: bool
|
||||
@@ -274,7 +300,8 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
|
||||
else:
|
||||
tab = tab_for_window(boss, opts, target_tab)
|
||||
if tab is not None:
|
||||
new_window: Window = tab.new_window(env=env or None, **kw)
|
||||
watchers = load_watch_modules(opts)
|
||||
new_window: Window = tab.new_window(env=env or None, watchers=watchers or None, **kw)
|
||||
if opts.keep_focus and active:
|
||||
boss.set_active_window(active, switch_os_window_if_needed=True)
|
||||
return new_window
|
||||
|
||||
@@ -549,7 +549,7 @@ class Layout: # {{{
|
||||
|
||||
def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
|
||||
for w in windows:
|
||||
if (w is active_window and draw_active_borders) or w.needs_attention:
|
||||
if w is not active_window or draw_active_borders or w.needs_attention:
|
||||
yield all_borders
|
||||
else:
|
||||
yield no_borders
|
||||
@@ -1512,6 +1512,13 @@ class Splits(Layout):
|
||||
else:
|
||||
p2.two = w1
|
||||
|
||||
def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
|
||||
for w in windows:
|
||||
if (w is active_window and draw_active_borders) or w.needs_attention:
|
||||
yield all_borders
|
||||
else:
|
||||
yield no_borders
|
||||
|
||||
def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList, active_window_idx: int) -> Optional[Union[bool, int]]:
|
||||
if action_name == 'rotate':
|
||||
args = args or ('90',)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from kitty.cli_stub import LaunchCLIOptions
|
||||
from kitty.launch import (
|
||||
launch as do_launch, options_spec as launch_options_spec,
|
||||
parse_launch_args
|
||||
@@ -16,7 +17,7 @@ from .base import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import LaunchRCOptions as CLIOptions, LaunchCLIOptions
|
||||
from kitty.cli_stub import LaunchRCOptions as CLIOptions
|
||||
|
||||
|
||||
class Launch(RemoteCommand):
|
||||
|
||||
@@ -57,6 +57,7 @@ Restore all colors to the values they had at kitty startup. Note that if you spe
|
||||
this option, any color arguments are ignored and --configured and --all are implied.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
argspec = 'COLOR_OR_FILE ...'
|
||||
args_completion = {'files': ('CONF files', ('*.conf',))}
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
final_colors: Dict[str, int] = {}
|
||||
|
||||
@@ -81,8 +81,11 @@ def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData
|
||||
screen.draw(' ' * trailing_spaces)
|
||||
end = screen.cursor.x
|
||||
screen.cursor.bold = screen.cursor.italic = False
|
||||
screen.cursor.fg = screen.cursor.bg = 0
|
||||
screen.cursor.fg = 0
|
||||
if not is_last:
|
||||
screen.cursor.bg = as_rgb(color_as_int(draw_data.inactive_bg))
|
||||
screen.draw(draw_data.sep)
|
||||
screen.cursor.bg = 0
|
||||
return end
|
||||
|
||||
|
||||
@@ -137,6 +140,7 @@ def draw_tab_with_powerline(draw_data: DrawData, screen: Screen, tab: TabBarData
|
||||
screen.draw(' ')
|
||||
screen.cursor.fg = tab_fg
|
||||
elif screen.cursor.x == 0:
|
||||
screen.cursor.bg = tab_bg
|
||||
screen.draw(' ')
|
||||
start_draw = 1
|
||||
|
||||
@@ -182,7 +186,7 @@ class TabBar:
|
||||
s.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
|
||||
s.color_profile.set_configured_colors(
|
||||
color_as_int(opts.inactive_tab_foreground),
|
||||
color_as_int(opts.background)
|
||||
color_as_int(opts.tab_bar_background or opts.background)
|
||||
)
|
||||
self.blank_rects: Tuple[Rect, ...] = ()
|
||||
sep = opts.tab_separator
|
||||
@@ -226,10 +230,13 @@ class TabBar:
|
||||
self.draw_data = self.draw_data._replace(default_bg=color_from_int(spec['tab_bar_background']))
|
||||
elif 'background' in spec and not self.opts.tab_bar_background:
|
||||
self.draw_data = self.draw_data._replace(default_bg=color_from_int(spec['background']))
|
||||
self.screen.color_profile.set_configured_colors(
|
||||
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)),
|
||||
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background))
|
||||
)
|
||||
fg = spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground))
|
||||
bg = spec.get('tab_bar_background', False)
|
||||
if bg is None:
|
||||
bg = color_as_int(self.opts.background)
|
||||
elif bg is False:
|
||||
bg = color_as_int(self.opts.tab_bar_background or self.opts.background)
|
||||
self.screen.color_profile.set_configured_colors(fg, bg)
|
||||
|
||||
def layout(self) -> None:
|
||||
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
|
||||
|
||||
@@ -26,7 +26,7 @@ from .layout import (
|
||||
from .options_stub import Options
|
||||
from .tab_bar import TabBar, TabBarData
|
||||
from .utils import log_error, resolved_shell
|
||||
from .window import Window, WindowDict
|
||||
from .window import Window, WindowDict, Watchers
|
||||
from .typing import TypedDict, SessionTab, SessionType
|
||||
|
||||
|
||||
@@ -345,11 +345,15 @@ class Tab: # {{{
|
||||
location: Optional[str] = None,
|
||||
copy_colors_from: Optional[Window] = None,
|
||||
allow_remote_control: bool = False,
|
||||
marker: Optional[str] = None
|
||||
marker: Optional[str] = None,
|
||||
watchers: Optional[Watchers] = None
|
||||
) -> 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)
|
||||
window = Window(self, child, self.opts, self.args, override_title=override_title, copy_colors_from=copy_colors_from)
|
||||
window = Window(
|
||||
self, child, self.opts, self.args, override_title=override_title,
|
||||
copy_colors_from=copy_colors_from, watchers=watchers
|
||||
)
|
||||
if overlay_for is not None:
|
||||
overlaid = next(w for w in self.windows if w.id == overlay_for)
|
||||
window.overlay_for = overlay_for
|
||||
@@ -384,6 +388,13 @@ class Tab: # {{{
|
||||
if self.windows:
|
||||
self.remove_window(self.windows[self.active_window_idx])
|
||||
|
||||
def close_other_windows_in_tab(self) -> None:
|
||||
if len(self.windows) > 1:
|
||||
active_window = self.windows[self.active_window_idx]
|
||||
for window in tuple(self.windows):
|
||||
if window is not active_window:
|
||||
self.remove_window(window)
|
||||
|
||||
def previous_active_window_idx(self, num: int) -> Optional[int]:
|
||||
try:
|
||||
old_window_id = self.active_window_history[-num]
|
||||
|
||||
@@ -10,8 +10,8 @@ from collections import deque
|
||||
from enum import IntEnum
|
||||
from itertools import chain
|
||||
from typing import (
|
||||
Any, Callable, Deque, Dict, List, Optional, Pattern, Sequence, Tuple,
|
||||
Union
|
||||
Any, Callable, Deque, Dict, Iterable, List, Optional, Pattern, Sequence,
|
||||
Tuple, Union
|
||||
)
|
||||
|
||||
from .child import ProcessDesc
|
||||
@@ -32,7 +32,7 @@ from .keys import defines, extended_key_event, keyboard_mode_name
|
||||
from .options_stub import Options
|
||||
from .rgb import to_color
|
||||
from .terminfo import get_capabilities
|
||||
from .typing import ChildType, TabType, TypedDict
|
||||
from .typing import BossType, ChildType, TabType, TypedDict
|
||||
from .utils import (
|
||||
color_as_int, get_primary_selection, load_shaders, open_cmd, open_url,
|
||||
parse_color_set, read_shell_environment, sanitize_title,
|
||||
@@ -77,6 +77,19 @@ DYNAMIC_COLOR_CODES = {
|
||||
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
|
||||
|
||||
|
||||
class Watcher:
|
||||
|
||||
def __call__(self, boss: BossType, window: 'Window', data: Dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Watchers:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.on_resize: List[Watcher] = []
|
||||
self.on_close: List[Watcher] = []
|
||||
|
||||
|
||||
def calculate_gl_geometry(window_geometry: WindowGeometry, viewport_width: int, viewport_height: int, cell_width: int, cell_height: int) -> ScreenGeometry:
|
||||
dx, dy = 2 * cell_width / viewport_width, 2 * cell_height / viewport_height
|
||||
xmargin = window_geometry.left / viewport_width
|
||||
@@ -181,8 +194,10 @@ class Window:
|
||||
opts: Options,
|
||||
args: CLIOptions,
|
||||
override_title: Optional[str] = None,
|
||||
copy_colors_from: Optional['Window'] = None
|
||||
copy_colors_from: Optional['Window'] = None,
|
||||
watchers: Optional[Watchers] = None
|
||||
):
|
||||
self.watchers = watchers or Watchers()
|
||||
self.action_on_close: Optional[Callable] = None
|
||||
self.action_on_removal: Optional[Callable] = None
|
||||
self.current_marker_spec: Optional[Tuple[str, Union[str, Tuple[Tuple[int, str], ...]]]] = None
|
||||
@@ -303,6 +318,7 @@ class Window:
|
||||
if not self.pty_resized_once:
|
||||
self.pty_resized_once = True
|
||||
self.child.mark_terminal_ready()
|
||||
self.call_watchers(self.watchers.on_resize, {'old_geometry': self.geometry, 'new_geometry': new_geometry})
|
||||
else:
|
||||
sg = self.update_position(new_geometry)
|
||||
self.geometry = g = new_geometry
|
||||
@@ -536,7 +552,17 @@ class Window:
|
||||
return ''.join((l.rstrip() or '\n') for l in lines)
|
||||
return ''.join(lines)
|
||||
|
||||
def call_watchers(self, which: Iterable[Watcher], data: Dict[str, Any]) -> None:
|
||||
boss = get_boss()
|
||||
for w in which:
|
||||
try:
|
||||
w(boss, self, data)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def destroy(self) -> None:
|
||||
self.call_watchers(self.watchers.on_close, {})
|
||||
self.destroyed = True
|
||||
if hasattr(self, 'screen'):
|
||||
# Remove cycles so that screen is de-allocated immediately
|
||||
|
||||
Reference in New Issue
Block a user