Compare commits

...

23 Commits

Author SHA1 Message Date
Kovid Goyal
b65119b8a9 Color separators in the tab bar using inactive bg except for the last one 2020-03-29 09:20:17 +05:30
Kovid Goyal
86703da917 version 0.17.2 2020-03-29 09:09:16 +05:30
Kovid Goyal
610e9afdf0 Add completion of conf filenames to kitty @ set-colors 2020-03-29 08:41:27 +05:30
Kovid Goyal
e3af9f68d3 Linux: Fix selection of fonts with multiple width variants not preferring the normal width faces
Fixes #2491
2020-03-29 01:52:33 +05:30
Kovid Goyal
747ac85e7c Add an :option:launch --watcher option that allows defining callbacks that are called for various events in the window's life-cycle
Fixes #2440
2020-03-28 12:13:42 +05:30
Kovid Goyal
8c23f9e526 DRYer 2020-03-28 09:20:28 +05:30
Kovid Goyal
e6e339fcd3 ... 2020-03-28 08:44:48 +05:30
Kovid Goyal
f04680ac47 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.
Fixes #2488
2020-03-28 08:43:32 +05:30
Kovid Goyal
90985cc846 Merge branch 'hints-copy-to-primary-selection' of https://github.com/trygveaa/kitty 2020-03-28 08:21:08 +05:30
Trygve Aaberge
63917944f2 Add an option to hints for copying to the primary selection 2020-03-27 20:23:02 +01:00
Kovid Goyal
793605f80f Update changelog 2020-03-26 22:06:13 +05:30
Kovid Goyal
93a281c7e3 Add a new mappable action `close_other_windows_in_tab` to close all but the active window
Fixes #2484
2020-03-26 22:05:00 +05:30
Kovid Goyal
8f1c6c4d74 Fix #2482 2020-03-26 00:53:19 +05:30
Kovid Goyal
1610dba7ab patch tab bar colors correctly 2020-03-26 00:46:30 +05:30
Kovid Goyal
d9d419991c Respect tab_bar_background again 2020-03-26 00:41:19 +05:30
Kovid Goyal
f9e86b19aa Fix #2480 2020-03-26 00:20:10 +05:30
Kovid Goyal
fffb976e43 Fix blank space at the start of tab bar in powerline style when first tab is inactive
Fix #2478
2020-03-25 19:20:35 +05:30
Kovid Goyal
0da566b49f Hints kitten: Adjust the default regex used to detect line numbers to handle line+column numbers
See #2268
2020-03-25 10:03:25 +05:30
Kovid Goyal
da55717d20 The splits layout needs its own draw_minimal_borders implementation 2020-03-25 09:54:06 +05:30
Kovid Goyal
254836902c Merge branch 'fix/window-border' of https://github.com/pyrho/kitty 2020-03-25 09:35:46 +05:30
Damien Rajon
d01f8d1865 fix(window): inactive window border fix
Fixes #2474.
2020-03-25 00:09:10 +01:00
Kovid Goyal
a46c3f7007 Replace use of cast() 2020-03-24 22:03:10 +05:30
Kovid Goyal
4e58062025 Improve rendering of :option: in cli docs 2020-03-24 22:03:00 +05:30
18 changed files with 208 additions and 39 deletions

View File

@@ -4,6 +4,37 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator. |kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`. 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] 0.17.1 [2020-03-24]
-------------------- --------------------

View File

@@ -192,6 +192,12 @@ Similarly, you can detach the current tab, with::
# asks which OS Window to move the tab into # asks which OS Window to move the tab into
map ctrl+f4 detach_tab ask 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 Other keyboard shortcuts
---------------------------------- ----------------------------------

View File

@@ -61,6 +61,35 @@ currently active kitty window. For example::
map f1 launch my-program @active-kitty-window-id 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 Syntax reference
------------------ ------------------

View File

@@ -48,6 +48,9 @@ try:
except ImportError: except ImportError:
has_highlighter = False 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) INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
ESCAPE = K['ESCAPE'] ESCAPE = K['ESCAPE']

View File

@@ -21,7 +21,7 @@ from kitty.key_encoding import (
KeyEvent, backspace_key, enter_key, key_defs as K KeyEvent, backspace_key, enter_key, key_defs as K
) )
from kitty.typing import BossType, KittyCommonOpts 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.handler import Handler, result_handler
from ..tui.loop import Loop 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]: def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
regex = args.regex regex = args.regex
if regex == DEFAULT_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) 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)} return {k: getattr(m, k) for k in dir(m)}
if customize_processing == '::linenum::': if customize_processing == '::linenum::':
return {'mark': linenum_marks, 'handle_result': linenum_handle_result} return {'mark': linenum_marks, 'handle_result': linenum_handle_result}
from kitty.constants import config_dir from kitty.constants import resolve_custom_file
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing)) custom_path = resolve_custom_file(customize_processing)
if os.path.isabs(customize_processing):
custom_path = customize_processing
else:
custom_path = os.path.join(config_dir, customize_processing)
import runpy import runpy
return runpy.run_path(custom_path, run_name='__main__') return runpy.run_path(custom_path, run_name='__main__')
@@ -413,9 +409,11 @@ OPTIONS = r'''
type=list type=list
What program to use to open matched text. Defaults to the default open program 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 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. terminal window instead. A value of :file:`@` will copy the match to the
A value of :file:`default` will run the default open program. Can be specified clipboard. A value of :file:`*` will copy the match to the primary selection
multiple times to run multiple programs. (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 --type
@@ -629,6 +627,8 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
w.paste(joined_text()) w.paste(joined_text())
elif program == '@': elif program == '@':
set_clipboard_string(joined_text()) set_clipboard_string(joined_text())
elif program == '*':
set_primary_selection(joined_text())
else: else:
cwd = None cwd = None
w = boss.window_id_map.get(target_window_id) w = boss.window_id_map.get(target_window_id)

View File

@@ -96,7 +96,9 @@ def opt(text: str) -> str:
def option(x: str) -> str: def option(x: str) -> str:
idx = x.find('-') idx = x.rfind('--')
if idx < 0:
idx = x.find('-')
if idx > -1: if idx > -1:
x = x[idx:] x = x[idx:]
parts = map(bold, x.split()) parts = map(bold, x.split())
@@ -119,6 +121,10 @@ def file(x: str) -> str:
return italic(x) return italic(x)
def doc(x: str) -> str:
return f'https://sw.kovidgoyal.net/kitty/{x}.html'
OptionSpecSeq = List[Union[str, OptionDict]] OptionSpecSeq = List[Union[str, OptionDict]]

View File

@@ -6,7 +6,7 @@ import re
from functools import partial from functools import partial
from typing import ( from typing import (
Any, Callable, Dict, Generator, Iterable, List, Match, Optional, Sequence, 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 from .utils import to_bool
@@ -156,7 +156,7 @@ def remove_markup(text: str) -> str:
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts', 'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions', 'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
}[m.group(2)] }[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) return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)

View File

@@ -20,7 +20,7 @@ class Version(NamedTuple):
appname: str = 'kitty' appname: str = 'kitty'
version: Version = Version(0, 17, 1) version: Version = Version(0, 17, 2)
str_version: str = '.'.join(map(str, version)) str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat 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: if set_val is not None:
setattr(running_in_kitty, 'ans', set_val) setattr(running_in_kitty, 'ans', set_val)
return bool(getattr(running_in_kitty, 'ans', False)) 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

View File

@@ -323,6 +323,7 @@ FC_MONO: int = 100
FC_DUAL: int FC_DUAL: int
FC_WEIGHT_REGULAR: int FC_WEIGHT_REGULAR: int
FC_WEIGHT_BOLD: int FC_WEIGHT_BOLD: int
FC_WIDTH_NORMAL: int
FC_SLANT_ROMAN: int FC_SLANT_ROMAN: int
FC_SLANT_ITALIC: int FC_SLANT_ITALIC: int
BORDERS_PROGRAM: int BORDERS_PROGRAM: int
@@ -402,6 +403,7 @@ class FontConfigPattern(TypedDict):
style: str style: str
spacing: str spacing: str
weight: int weight: int
width: int
slant: int slant: int
hint_style: int hint_style: int
subpixel: int subpixel: int

View File

@@ -47,6 +47,7 @@ pattern_as_dict(FcPattern *pat) {
S(FC_FULLNAME, full_name); S(FC_FULLNAME, full_name);
S(FC_POSTSCRIPT_NAME, postscript_name); S(FC_POSTSCRIPT_NAME, postscript_name);
I(FC_WEIGHT, weight); I(FC_WEIGHT, weight);
I(FC_WIDTH, width)
I(FC_SLANT, slant); I(FC_SLANT, slant);
I(FC_HINT_STYLE, hint_style); I(FC_HINT_STYLE, hint_style);
I(FC_INDEX, index); I(FC_INDEX, index);
@@ -247,5 +248,7 @@ init_fontconfig_library(PyObject *module) {
PyModule_AddIntMacro(module, FC_DUAL); PyModule_AddIntMacro(module, FC_DUAL);
PyModule_AddIntMacro(module, FC_MONO); PyModule_AddIntMacro(module, FC_MONO);
PyModule_AddIntMacro(module, FC_CHARCELL); PyModule_AddIntMacro(module, FC_CHARCELL);
PyModule_AddIntMacro(module, FC_WIDTH_NORMAL);
return true; return true;
} }

View File

@@ -8,7 +8,7 @@ from typing import Dict, Generator, List, Optional, Tuple, cast
from kitty.fast_data_types import ( from kitty.fast_data_types import (
FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, 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.options_stub import Options
from kitty.typing import FontConfigPattern 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) q = family_name_to_key(family)
font_map = all_fonts_map(monospaced) 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)) 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)) 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 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 # First look for an exact match
for selector in ('ps_map', 'full_map', 'family_map'): for selector in ('ps_map', 'full_map', 'family_map'):

View File

@@ -6,14 +6,15 @@
from functools import lru_cache from functools import lru_cache
from typing import Any, Dict, List, Optional, Sequence, Tuple from typing import Any, Dict, List, Optional, Sequence, Tuple
from .boss import Boss
from .child import Child from .child import Child
from .cli import parse_args from .cli import parse_args
from .cli_stub import LaunchCLIOptions from .cli_stub import LaunchCLIOptions
from .constants import resolve_custom_file
from .fast_data_types import set_clipboard_string 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 .tabs import Tab
from .utils import set_primary_selection
from .window import Watchers, Window
try: try:
from typing import TypedDict from typing import TypedDict
@@ -148,6 +149,14 @@ defaults to :code:`kitty`.
--os-window-name --os-window-name
Set the WM_NAME property on X11 for the newly created OS Window when using 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`. :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 tm = boss.active_tab_manager
if tm: if tm:
tab: Optional[Tab] = tm.new_tab(empty_tab=True, location=opts.location) 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) tab.set_title(opts.tab_title)
else: else:
tab = None 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) oswid = boss.add_os_window(wclass=opts.os_window_class, wname=opts.os_window_name)
tm = boss.os_window_map[oswid] tm = boss.os_window_map[oswid]
tab = tm.new_tab(empty_tab=True) 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) tab.set_title(opts.tab_title)
else: else:
tab = target_tab or boss.active_tab 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 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): class LaunchKwds(TypedDict):
allow_remote_control: bool allow_remote_control: bool
@@ -274,7 +300,8 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
else: else:
tab = tab_for_window(boss, opts, target_tab) tab = tab_for_window(boss, opts, target_tab)
if tab is not None: 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: if opts.keep_focus and active:
boss.set_active_window(active, switch_os_window_if_needed=True) boss.set_active_window(active, switch_os_window_if_needed=True)
return new_window return new_window

View File

@@ -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]: def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
for w in windows: 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 yield all_borders
else: else:
yield no_borders yield no_borders
@@ -1512,6 +1512,13 @@ class Splits(Layout):
else: else:
p2.two = w1 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]]: 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': if action_name == 'rotate':
args = args or ('90',) args = args or ('90',)

View File

@@ -5,6 +5,7 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from kitty.cli_stub import LaunchCLIOptions
from kitty.launch import ( from kitty.launch import (
launch as do_launch, options_spec as launch_options_spec, launch as do_launch, options_spec as launch_options_spec,
parse_launch_args parse_launch_args
@@ -16,7 +17,7 @@ from .base import (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from kitty.cli_stub import LaunchRCOptions as CLIOptions, LaunchCLIOptions from kitty.cli_stub import LaunchRCOptions as CLIOptions
class Launch(RemoteCommand): class Launch(RemoteCommand):

View File

@@ -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. 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') ''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
argspec = 'COLOR_OR_FILE ...' argspec = 'COLOR_OR_FILE ...'
args_completion = {'files': ('CONF files', ('*.conf',))}
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:
final_colors: Dict[str, int] = {} final_colors: Dict[str, int] = {}

View File

@@ -81,8 +81,11 @@ def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData
screen.draw(' ' * trailing_spaces) screen.draw(' ' * trailing_spaces)
end = screen.cursor.x end = screen.cursor.x
screen.cursor.bold = screen.cursor.italic = False 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.draw(draw_data.sep)
screen.cursor.bg = 0
return end return end
@@ -137,6 +140,7 @@ def draw_tab_with_powerline(draw_data: DrawData, screen: Screen, tab: TabBarData
screen.draw('') screen.draw('')
screen.cursor.fg = tab_fg screen.cursor.fg = tab_fg
elif screen.cursor.x == 0: elif screen.cursor.x == 0:
screen.cursor.bg = tab_bg
screen.draw(' ') screen.draw(' ')
start_draw = 1 start_draw = 1
@@ -182,7 +186,7 @@ class TabBar:
s.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) s.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
s.color_profile.set_configured_colors( s.color_profile.set_configured_colors(
color_as_int(opts.inactive_tab_foreground), 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, ...] = () self.blank_rects: Tuple[Rect, ...] = ()
sep = opts.tab_separator 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'])) 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: 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.draw_data = self.draw_data._replace(default_bg=color_from_int(spec['background']))
self.screen.color_profile.set_configured_colors( fg = spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground))
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)), bg = spec.get('tab_bar_background', False)
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background)) 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: def layout(self) -> None:
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id) central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)

View File

@@ -26,7 +26,7 @@ from .layout import (
from .options_stub import Options from .options_stub import Options
from .tab_bar import TabBar, TabBarData from .tab_bar import TabBar, TabBarData
from .utils import log_error, resolved_shell from .utils import log_error, resolved_shell
from .window import Window, WindowDict from .window import Window, WindowDict, Watchers
from .typing import TypedDict, SessionTab, SessionType from .typing import TypedDict, SessionTab, SessionType
@@ -345,11 +345,15 @@ class Tab: # {{{
location: Optional[str] = None, location: Optional[str] = None,
copy_colors_from: Optional[Window] = None, copy_colors_from: Optional[Window] = None,
allow_remote_control: bool = False, allow_remote_control: bool = False,
marker: Optional[str] = None marker: Optional[str] = None,
watchers: Optional[Watchers] = None
) -> Window: ) -> Window:
child = self.launch_child( 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) 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: if overlay_for is not None:
overlaid = next(w for w in self.windows if w.id == overlay_for) overlaid = next(w for w in self.windows if w.id == overlay_for)
window.overlay_for = overlay_for window.overlay_for = overlay_for
@@ -384,6 +388,13 @@ class Tab: # {{{
if self.windows: if self.windows:
self.remove_window(self.windows[self.active_window_idx]) 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]: def previous_active_window_idx(self, num: int) -> Optional[int]:
try: try:
old_window_id = self.active_window_history[-num] old_window_id = self.active_window_history[-num]

View File

@@ -10,8 +10,8 @@ from collections import deque
from enum import IntEnum from enum import IntEnum
from itertools import chain from itertools import chain
from typing import ( from typing import (
Any, Callable, Deque, Dict, List, Optional, Pattern, Sequence, Tuple, Any, Callable, Deque, Dict, Iterable, List, Optional, Pattern, Sequence,
Union Tuple, Union
) )
from .child import ProcessDesc from .child import ProcessDesc
@@ -32,7 +32,7 @@ from .keys import defines, extended_key_event, keyboard_mode_name
from .options_stub import Options from .options_stub import Options
from .rgb import to_color from .rgb import to_color
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .typing import ChildType, TabType, TypedDict from .typing import BossType, ChildType, TabType, TypedDict
from .utils import ( from .utils import (
color_as_int, get_primary_selection, load_shaders, open_cmd, open_url, color_as_int, get_primary_selection, load_shaders, open_cmd, open_url,
parse_color_set, read_shell_environment, sanitize_title, 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()}) 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: 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 dx, dy = 2 * cell_width / viewport_width, 2 * cell_height / viewport_height
xmargin = window_geometry.left / viewport_width xmargin = window_geometry.left / viewport_width
@@ -181,8 +194,10 @@ class Window:
opts: Options, opts: Options,
args: CLIOptions, args: CLIOptions,
override_title: Optional[str] = None, 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_close: Optional[Callable] = None
self.action_on_removal: Optional[Callable] = None self.action_on_removal: Optional[Callable] = None
self.current_marker_spec: Optional[Tuple[str, Union[str, Tuple[Tuple[int, str], ...]]]] = 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: if not self.pty_resized_once:
self.pty_resized_once = True self.pty_resized_once = True
self.child.mark_terminal_ready() self.child.mark_terminal_ready()
self.call_watchers(self.watchers.on_resize, {'old_geometry': self.geometry, 'new_geometry': new_geometry})
else: else:
sg = self.update_position(new_geometry) sg = self.update_position(new_geometry)
self.geometry = g = 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((l.rstrip() or '\n') for l in lines)
return ''.join(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: def destroy(self) -> None:
self.call_watchers(self.watchers.on_close, {})
self.destroyed = True self.destroyed = True
if hasattr(self, 'screen'): if hasattr(self, 'screen'):
# Remove cycles so that screen is de-allocated immediately # Remove cycles so that screen is de-allocated immediately