Compare commits

...

66 Commits

Author SHA1 Message Date
Kovid Goyal
cc29640604 version 0.17.3 2020-04-23 08:39:07 +05:30
Kovid Goyal
d0329460b8 Fix #2573 2020-04-21 22:04:34 +05:30
Kovid Goyal
717c0c96de Allow mapping arbitrary remote control commands to key presses in kitty.conf 2020-04-19 20:14:23 +05:30
Kovid Goyal
b3f161efce ... 2020-04-19 19:39:56 +05:30
Kovid Goyal
0286940163 Allow changing margings/padding via remote control
Fixes #2546
2020-04-19 19:29:36 +05:30
Kovid Goyal
d4a4ee386f Fix minimal borders in grid layout 2020-04-19 17:58:35 +05:30
Kovid Goyal
33498f9133 ... 2020-04-19 11:01:22 +05:30
Kovid Goyal
da3d4d1bef ... 2020-04-19 09:51:06 +05:30
Kovid Goyal
c69b8870d2 Allow individually setting margins and padding for each edge (left, right, top, bottom) 2020-04-19 09:36:26 +05:30
Kovid Goyal
85b55b31b6 Add a new :opt:listen_on option in kitty.conf
Also allow using environment variables in this option
Fixes #2569
2020-04-19 09:23:52 +05:30
Kovid Goyal
948919e42b Forgot to apply --title on creation of subsequent OS windows
Also update changelog
2020-04-19 08:42:30 +05:30
Kovid Goyal
85c40090ed Fix #2570 2020-04-19 08:36:29 +05:30
Kovid Goyal
98b50ec145 ... 2020-04-19 08:28:40 +05:30
Kovid Goyal
1ab4f7d2d0 Fix a regression in 0.17 that broke displaying graphics over SSH
The new types GraphicsCommand can generate a command with only a payload
and no control keys, as the last command during chunked transfer.
Fix the parser to handle this. Fixes #2568
2020-04-19 08:13:33 +05:30
Kovid Goyal
98801b6fa6 Update changelog 2020-04-19 07:22:02 +05:30
Kovid Goyal
bdd68284b5 Merge branch 'terminfo' of https://github.com/wladh/kitty 2020-04-19 07:21:10 +05:30
Vlad Hanciuta
a61d70ea9a Add strikethrough capabilities to terminfo
The termcap aliases are not standard, but they are used by Vim, it
seems. As far as I can tell, there are no standard termcap attribute for
strikethrough.
2020-04-18 16:37:09 +01:00
Vlad Hanciuta
e271719dd2 Add strikeout capability to terminfo 2020-04-18 16:05:30 +01:00
Kovid Goyal
a08efa1fc5 Fix failing test 2020-04-17 19:57:37 +05:30
Kovid Goyal
ffac3600e0 Layouts: Apply per window padding and margin values
Also make the calculation of blank rects more robust and with less code,
which should hopefully fix #1762
2020-04-17 19:47:34 +05:30
Kovid Goyal
6499f8ac24 Allow window objects to store custom margin/padding 2020-04-17 09:02:08 +05:30
Kovid Goyal
63307cb1d8 Code to serialize state 2020-04-17 08:42:45 +05:30
Kovid Goyal
0a6fb362f6 kitty @ kitten: Allow no_ui kittens to return data to the remote client
Fixes #2552 since the OP can now write a kitten to query the window
manager and return position data even over SSH.
2020-04-15 08:29:55 +05:30
Kovid Goyal
457fcbfb90 Merge branch 'master' of https://github.com/akobel/kitty 2020-04-15 07:44:27 +05:30
Alexander Kobel
3b6277b9e2 Hide "No render frame received" without --debug-rendering 2020-04-14 19:36:58 +02:00
Alexander Kobel
e6e61028b1 Rename --debug-gl to --debug-rendering 2020-04-14 19:35:40 +02:00
Kovid Goyal
e9be66126b Merge branch 'hints-support-empty-word-characters' of https://github.com/trygveaa/kitty 2020-04-14 08:17:13 +05:30
Trygve Aaberge
681fe7f2f2 Don't set word_characters to select_by_word_characters when empty string
This allows you to pass in --word-characters "" which will make hints
only match \w.
2020-04-13 23:02:44 +02:00
Kovid Goyal
0196920533 icat kitten: Fix display of 16-bit depth images
Fixes #2542
2020-04-13 23:05:10 +05:30
Kovid Goyal
5f5bb3ace0 Update Changelog 2020-04-12 20:10:29 +05:30
Kovid Goyal
3c970e0122 Merge branch 'kitten-send-mouse-events' of https://github.com/trygveaa/kitty 2020-04-12 20:09:45 +05:30
Trygve Aaberge
43af6e3b8a Add support for sending mouse events from a kitten
This allows you to a control a program running in kitty from a kitten
using mouse events. If the program is not receiving mouse events of that
type, it is not sent.
2020-04-12 16:19:50 +02:00
Kovid Goyal
ac8cfc12fb Merge branch 'docs-fix-kitten-custom-indentation' of https://github.com/trygveaa/kitty 2020-04-12 19:49:18 +05:30
Trygve Aaberge
b19472e0f5 Fix indentation in kittens/custom.rst to 4 spaces
This file used 3 spaces some places and 4 spaces other places. Other
files like diff.rst and hints.rst use 4 spaces.
2020-04-12 16:04:32 +02:00
Kovid Goyal
b4d08044a0 ... 2020-04-12 17:01:39 +05:30
Kovid Goyal
b2fe02e63f Merge branch 'patch-1' of https://github.com/LesnyRumcajs/kitty 2020-04-12 16:55:10 +05:30
Kovid Goyal
d1a5d30233 Add another test for marking 2020-04-12 16:54:38 +05:30
LesnyRumcajs
bba30138e1 Update Fedora link
The link was pointing to a deprecated `copr`. Let's point it to official Fedora repos! (as copr page suggests anyway)
2020-04-12 13:13:02 +02:00
Kovid Goyal
082546a1e7 Marks: Fix marks not handling wide characters and tab characters correctly
Fixes #2534
2020-04-12 13:28:21 +05:30
Kovid Goyal
41ffd58e26 A bit of cleanup 2020-04-11 21:22:17 +05:30
Kovid Goyal
b4f24e4e1e Merge branch 'must_draw_borders_flag' of https://github.com/franco/kitty 2020-04-11 21:19:52 +05:30
Franco Sebregondi
75c8ffd715 Add flag to Layout to enforce drawing a border
Layout.must_draw_borders = True enforces drawing of a border even
though only a single window is visible. See #2531.
2020-04-11 17:01:44 +02:00
Kovid Goyal
688606ff7c Merge branch 'ignore-keyboard-layout-switch-keys' of https://github.com/trygveaa/kitty 2020-04-10 21:46:07 +05:30
Trygve Aaberge
2e3f9dffa7 Ignore keys for switching keyboard layout
XKB has various options for using keys to switch the keyboard layout.
When these are set, XKB will return keysyms which are unknown to GLFW,
so kitty will fallback to using a keymap without the options set, which
causes the keys to be interpreted as the original keysyms.

However, when these options are set, kitty shouldn't interpret the keys.
Therefore, check for some specific keysyms and return before translating
to GLFW keysyms.

There may be more keysyms which should be ignored, but as far as I can
see from https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/blob/xkeyboard-config-2.29/symbols/group
these are the ones which can be triggered by XKB options.

Fixes #2519
2020-04-10 17:21:53 +02:00
Kovid Goyal
859033415b macOS: Fix incorrect horizontal positioning of some full-width East Asian characters
Fixes #1457
Also render multi-cell PUA characters centered, matching behavior on other platforms
2020-04-09 13:25:19 +05:30
Kovid Goyal
1f1b52b580 Fix specifying options for layouts in the startup session file not working
Fixes #2520
2020-04-09 12:40:09 +05:30
Kovid Goyal
4e7bf80447 diff kitten: New option to control the background color for filler lines in the margin
Fixes #2518
2020-04-08 20:42:49 +05:30
Kovid Goyal
e39df50884 Merge branch 'patch-1' of https://github.com/georgeabr/kitty 2020-04-08 18:56:15 +05:30
George Abraham
8d6a9d45c2 Changing match target to "font"
As suggested in #2516, changing match target to "font" in the fonts.conf file.
2020-04-08 14:02:54 +01:00
Kovid Goyal
0862e85577 Rewrite wcswidth as a state machine 2020-04-07 10:10:30 +05:30
Kovid Goyal
0b9a37139e Render any flag pair in two cells not just known ones 2020-04-06 23:00:34 +05:30
Kovid Goyal
6357f7b811 Update wcswidth() to match new country flag rendering 2020-04-06 22:56:34 +05:30
Kovid Goyal
24197dc422 Render known country flags designated by a pair of unicode codepoints in two cells instead of four. 2020-04-06 22:16:59 +05:30
Kovid Goyal
9bc2ab3245 Function to detect flag pairs 2020-04-06 21:16:14 +05:30
Kovid Goyal
bf4e8c490c Update to Unicode 13.0
Fixes #2513
2020-04-06 18:59:35 +05:30
Kovid Goyal
590a4d70ee Fix --stdin-add-formatting not working
Fixes #2512
2020-04-06 09:35:16 +05:30
Kovid Goyal
40341d2ddd ... 2020-04-04 09:17:38 +05:30
Kovid Goyal
0cd2c41ee3 Update changelog 2020-04-03 22:42:06 +05:30
Kovid Goyal
5d65cdb6a8 Fix reverse video not being rendered correctly when using transparency or a background image
Fixes #2419
2020-04-03 22:40:33 +05:30
Kovid Goyal
759a15ccbb X11: Handle invalid data being sent with drag events by some applications
Fixes #2505
2020-04-02 21:01:19 +05:30
Kovid Goyal
5aead5eebe Replace new-window with launch in the docs 2020-04-01 08:39:37 +05:30
Kovid Goyal
a325a5fdee Merge branch 'strikethrough-info-from-font' of https://github.com/trygveaa/kitty 2020-03-31 09:34:07 +05:30
Trygve Aaberge
931e91f1a7 Read strikethrough info from font when using FreeType
This reads the strikethrough position and thickness from the font so it
is rendered correctly.

This is only implemented for FreeType, not Core Text, because I didn't
find any way to get the info from Core Text, and I don't have a Mac to
test it on either. When using Core Text or when the font doesn't provide
the info, the same approximation as before is used.
2020-03-30 20:09:30 +02:00
Kovid Goyal
6e13509720 Allow entry points and external command combined completion on first word 2020-03-30 11:38:54 +05:30
Kovid Goyal
5b4844e71e oops
Broke normal completion in previous commit. Delegate should only be used
when actual unknown words are present
2020-03-30 11:12:00 +05:30
Kovid Goyal
66cbbea9bb Add completion for executables at kitty invocation in zsh
So now kitty vi<TAB> and kitty vim something<TAB> delegate
completion to zsh for the command that will be executed inside kitty.
2020-03-30 10:54:17 +05:30
57 changed files with 31469 additions and 28802 deletions

View File

@@ -4,6 +4,60 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.17.3 [2020-04-23]
--------------------
- Allow individually setting margins and padding for each edge (left, right,
top, bottom). Margins can also be controlled per window via remote control
(:iss:`2546`)
- Fix reverse video not being rendered correctly when using transparency or a
background image (:iss:`2419`)
- Allow mapping arbitrary remote control commands to key presses in
:file:`kitty.conf`
- X11: Fix crash when doing drag and drop from some applications (:iss:`2505`)
- Fix :option:`launch --stdin-add-formatting` not working (:iss:`2512`)
- Update to Unicode 13.0 (:iss:`2513`)
- Render country flags designated by a pair of unicode codepoints
in two cells instead of four.
- diff kitten: New option to control the background color for filler lines in
the margin (:iss:`2518`)
- Fix specifying options for layouts in the startup session file not working
(:iss:`2520`)
- macOS: Fix incorrect horizontal positioning of some full-width East Asian characters
(:iss:`1457`)
- macOS: Render multi-cell PUA characters centered, matching behavior on other
platforms
- Linux: Ignore keys if they are designated as layout/group/mode switch keys
(:iss:`2519`)
- Marks: Fix marks not handling wide characters and tab characters correctly
(:iss:`2534`)
- Add a new :opt:`listen_on` option in kitty.conf to set :option:`kitty --listen-on`
globally. Also allow using environment variables in this option (:iss:`2569`).
- Allow sending mouse events in kittens (:pull:`2538`)
- icat kitten: Fix display of 16-bit depth images (:iss:`2542`)
- Add ncurses specific terminfo definitions for strikethrough (:pull:`2567`)
- Fix a regression in 0.17 that broke displaying graphics over SSH
(:iss:`2568`)
- Fix :option:`--title` not being applied at window creation time (:iss:`2570`)
0.17.2 [2020-03-29]
--------------------

View File

@@ -179,7 +179,7 @@ following :file:`~/.config/fontconfig/fonts.conf`::
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="scan">
<match target="font">
<test name="family">
<string>Your Font Family Name</string>
</test>

View File

@@ -65,7 +65,7 @@ You can also use your favorite package manager to install the |kitty| package.
`openSUSE <https://build.opensuse.org/package/show/X11:terminals/kitty>`_,
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
`Fedora <https://copr.fedorainfracloud.org/coprs/gagbo/kitty-latest/>`_,
`Fedora <https://src.fedoraproject.org/rpms/kitty>`_,
`Void Linux <https://github.com/void-linux/void-packages/blob/master/srcpkgs/kitty/template>`_,
and `Solus <https://dev.getsol.us/source/kitty/>`_.

View File

@@ -20,24 +20,24 @@ your machine).
.. code-block:: python
def main(args: List[str]) -> str:
# this is the main entry point of the kitten, it will be executed in
# the overlay window when the kitten is launched
answer = input('Enter some text: ')
# whatever this function returns will be available in the
# handle_result() function
return answer
def main(args: List[str]) -> str:
# this is the main entry point of the kitten, it will be executed in
# the overlay window when the kitten is launched
answer = input('Enter some text: ')
# whatever this function returns will be available in the
# handle_result() function
return answer
def handle_result(args: List[str], answer: str, target_window_id: int, boss: kitty.boss.Boss) -> None:
# get the kitty window into which to paste answer
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(answer)
def handle_result(args: List[str], answer: str, target_window_id: int, boss: kitty.boss.Boss) -> None:
# get the kitty window into which to paste answer
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(answer)
Now in :file:`kitty.conf` add the lines::
map ctrl+k kitten mykitten.py
map ctrl+k kitten mykitten.py
Start kitty and press :kbd:`ctrl+k` and you should see the kitten running.
@@ -53,7 +53,7 @@ Passing arguments to kittens
You can pass arguments to kittens by defining them in the map directive in
:file:`kitty.conf`. For example::
map ctrl+k kitten mykitten.py arg1 arg2
map ctrl+k kitten mykitten.py arg1 arg2
These will be available as the ``args`` parameter in the ``main()`` and
``handle_result()`` functions. Note also that the current working directory
@@ -70,17 +70,17 @@ function, telling kitty what kind of input your kitten would like. For example:
.. code-block:: py
# in main, STDIN is for the kitten process and will contain
# the contents of the screen
def main(args):
return sys.stdin.read()
# in main, STDIN is for the kitten process and will contain
# the contents of the screen
def main(args):
return sys.stdin.read()
# in handle_result, STDIN is for the kitty process itself, rather
# than the kitten process and should not be read from.
from kittens.tui.handler import result_handler
@result_handler(type_of_input='text')
def handle_result(args, stdin_data, target_window_id, boss):
pass
# in handle_result, STDIN is for the kitty process itself, rather
# than the kitten process and should not be read from.
from kittens.tui.handler import result_handler
@result_handler(type_of_input='text')
def handle_result(args, stdin_data, target_window_id, boss):
pass
This will send the plain text of the active window to the kitten's
@@ -104,23 +104,23 @@ Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
.. code-block:: py
def main(args):
pass
def main(args):
pass
from kittens.tui.handler import result_handler
@result_handler(no_ui=True)
def handle_result(args, answer, target_window_id, boss):
tab = boss.active_tab
if tab is not None:
if tab.current_layout.name == 'stack':
tab.last_used_layout()
else:
tab.goto_layout('stack')
from kittens.tui.handler import result_handler
@result_handler(no_ui=True)
def handle_result(args, answer, target_window_id, boss):
tab = boss.active_tab
if tab is not None:
if tab.current_layout.name == 'stack':
tab.last_used_layout()
else:
tab.goto_layout('stack')
Now in kitty.conf add::
map f11 kitten zoom_toggle.py
map f11 kitten zoom_toggle.py
Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
more fancy, switching the kitty OS window to fullscreen as well as changing the
@@ -132,6 +132,32 @@ layout, by simply adding the line::
To the ``handle_result()`` function, above.
Sending mouse events
--------------------
If the program running in a window is receiving mouse events you can simulate
those using::
from kitty.fast_data_types import send_mouse_event
send_mouse_event(screen, x, y, button, action, mods)
``screen`` is the ``screen`` attribute of the window you want to send the event
to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is
``GLFW_MOUSE_BUTTON_{button}`` where ``{button}`` is one of ``LEFT``,
``RIGHT``, ``MIDDLE`` or a digit from ``1`` to ``8``. ``action`` is one of
``PRESS``, ``RELEASE``, ``DRAG`` or ``MOVE``. ``mods`` is a bitmask of
``GLFW_MOD_{mod}`` where ``{mod}`` is one of ``SHIFT``, ``CONTROL`` or ``ALT``.
All the mentioned constants are imported from ``kitty.fast_data_types``.
For example, to send a left click at position x: 2, y: 3 to the active window::
from kitty.fast_data_types import send_mouse_event, GLFW_MOUSE_BUTTON_LEFT, PRESS
send_mouse_event(boss.active_window.screen, 2, 3, GLFW_MOUSE_BUTTON_LEFT, PRESS, 0)
The function will only send the event if the program is receiving events of
that type, and will return ``True`` if it sent the event, and ``False`` if not.
Debugging kittens
--------------------
@@ -157,11 +183,11 @@ Kittens created by kitty users
---------------------------------------------
`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
Makes the kitty scroll bindings work in full screen applications
Makes the kitty scroll bindings work in full screen applications
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
Insert a password from a CLI password manager, taking care to only do it at
a password prompt.
Insert a password from a CLI password manager, taking care to only do it at
a password prompt.

View File

@@ -21,7 +21,7 @@ In order for control to work, :opt:`allow_remote_control` must be enabled in
Now, in the new |kitty| window, enter the command::
kitty @ new-window --title Output --keep-focus cat
kitty @ launch --title Output --keep-focus cat
This will open a new window, running the ``cat`` program that will appear next
to the current window.
@@ -54,7 +54,7 @@ window. Type ``Ctrl+D`` when you are ready to stop.
Now, let's open a new tab::
kitty @ new-window --new-tab --tab-title "My Tab" --keep-focus bash
kitty @ launch --type=tab --tab-title "My Tab" --keep-focus bash
This will open a new tab running the bash shell with the title "My Tab".
We can change the title of the tab with::
@@ -134,6 +134,19 @@ still write to the pipes of any other program on the same computer and
therefore can control |kitty|. It can, however, be useful to block programs
running on other computers (for example, over ssh) or as other users.
Mapping key presses to remote control commands
--------------------------------------------------
If you wish to trigger a remote control command easily with just a keypress,
you can map it in :file:`kitty.conf`. For example::
map F1 remote_control set-spacing margin=30
Then pressing the :kbd:`F1` key will set the active window margins to 30.
The syntax for what follows :code:`remote_control` is exactly the same
as the syntax for what follows :code:`kitty @` above.
Documentation for the remote control protocol
-----------------------------------------------

View File

@@ -137,6 +137,7 @@ static inline void
{parr}
{keys_enum}
enum KEYS key = '{initial_key}';
if (screen->parser_buf[pos] == ';') state = AFTER_VALUE;
while (pos < screen->parser_buf_pos) {{
switch(state) {{

View File

@@ -51,7 +51,8 @@ all_symbols: Set[int] = set()
name_map: Dict[int, str] = {}
word_search_map: DefaultDict[str, Set[int]] = defaultdict(set)
zwj = 0x200d
marks = set(emoji_skin_tone_modifiers) | {zwj}
flag_codepoints = frozenset(range(0x1F1E6, 0x1F1E6 + 26))
marks = set(emoji_skin_tone_modifiers) | {zwj} | flag_codepoints
not_assigned = set(range(0, sys.maxunicode))
@@ -103,36 +104,88 @@ def parse_ucd() -> None:
word_search_map['lamda'] |= word_search_map['lambda']
def split_two(line: str) -> Tuple[Set[int], str]:
spec, rest = line.split(';', 1)
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
def parse_range_spec(spec: str) -> Set[int]:
spec = spec.strip()
if '..' in spec:
chars_ = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
chars = set(range(chars_[0], chars_[1] + 1))
else:
chars = {int(spec, 16)}
return chars, rest
return chars
def split_two(line: str) -> Tuple[Set[int], str]:
spec, rest = line.split(';', 1)
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
return parse_range_spec(spec), rest
all_emoji: Set[int] = set()
emoji_categories: Dict[str, Set[int]] = {}
emoji_presentation_bases: Set[int] = set()
narrow_emoji: Set[int] = set()
wide_emoji: Set[int] = set()
flags: Dict[int, List[int]] = {}
def parse_basic_emoji(spec: str) -> None:
parts = list(filter(None, spec.split()))
has_emoji_presentation = len(parts) < 2
chars = parse_range_spec(parts[0])
all_emoji.update(chars)
emoji_presentation_bases.update(chars)
(wide_emoji if has_emoji_presentation else narrow_emoji).update(chars)
def parse_keycap_sequence(spec: str) -> None:
base, fe0f, cc = list(filter(None, spec.split()))
chars = parse_range_spec(base)
all_emoji.update(chars)
emoji_presentation_bases.update(chars)
narrow_emoji.update(chars)
def parse_flag_emoji_sequence(spec: str) -> None:
a, b = list(filter(None, spec.split()))
left, right = int(a, 16), int(b, 16)
chars = {left, right}
all_emoji.update(chars)
wide_emoji.update(chars)
emoji_presentation_bases.update(chars)
flags.setdefault(left, []).append(right)
def parse_emoji_tag_sequence(spec: str) -> None:
a = int(spec.split()[0], 16)
all_emoji.add(a)
wide_emoji.add(a)
emoji_presentation_bases.add(a)
def parse_emoji_modifier_sequence(spec: str) -> None:
a, b = list(filter(None, spec.split()))
char, mod = int(a, 16), int(b, 16)
mod
all_emoji.add(char)
wide_emoji.add(char)
emoji_presentation_bases.add(char)
def parse_emoji() -> None:
for line in get_data('emoji-data.txt', 'emoji'):
chars, rest = split_two(line)
s = emoji_categories.setdefault(rest, set())
s.update(chars)
all_emoji.update(chars)
for line in get_data('emoji-variation-sequences.txt', 'emoji'):
parts = line.split()
base, var = parts[0], parts[1]
if base.startswith('#'):
for line in get_data('emoji-sequences.txt', 'emoji'):
parts = [x.strip() for x in line.split(';')]
if len(parts) < 2:
continue
if var.upper() == 'FE0F':
ibase = int(base, 16)
emoji_presentation_bases.add(ibase)
data, etype = parts[:2]
if etype == 'Basic_Emoji':
parse_basic_emoji(data)
elif etype == 'Emoji_Keycap_Sequence':
parse_keycap_sequence(data)
elif etype == 'RGI_Emoji_Flag_Sequence':
parse_flag_emoji_sequence(data)
elif etype == 'RGI_Emoji_Tag_Sequence':
parse_emoji_tag_sequence(data)
elif etype == 'RGI_Emoji_Modifier_Sequence':
parse_emoji_modifier_sequence(data)
doublewidth: Set[int] = set()
@@ -204,15 +257,6 @@ def gen_emoji() -> None:
p('\t}')
p('\treturn false;\n}')
p('static inline bool\nis_emoji_modifier(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(emoji_categories['Emoji_Modifier'])):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false;\n}')
p('static inline bool\nis_symbol(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(all_symbols)):
@@ -427,7 +471,7 @@ def gen_names() -> None:
def gen_wcwidth() -> None:
seen: Set[int] = set()
def add(p: Callable, comment: str, chars_: Set[int], ret: int) -> None:
def add(p: Callable, comment: str, chars_: Union[Set[int], FrozenSet[int]], ret: int) -> None:
chars = chars_ - seen
seen.update(chars)
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
@@ -441,13 +485,14 @@ def gen_wcwidth() -> None:
p('\tswitch(code) {')
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
add(p, 'Flags', flag_codepoints, 2)
add(p, 'Marks', marks | {0}, 0)
add(p, 'Non-printing characters', non_printing, -1)
add(p, 'Private use', class_maps['Co'], -3)
add(p, 'Text Presentation', emoji_categories['Emoji'] - emoji_categories['Emoji_Presentation'], 1)
add(p, 'Text Presentation', narrow_emoji, 1)
add(p, 'East Asian ambiguous width', ambiguous, -2)
add(p, 'East Asian double width', doublewidth, 2)
add(p, 'Emoji Presentation', emoji_categories['Emoji_Presentation'], 2)
add(p, 'Emoji Presentation', wide_emoji, 2)
add(p, 'Not assigned in the unicode character database', not_assigned, -4)

17
glfw/x11_window.c vendored
View File

@@ -1075,6 +1075,21 @@ static void onConfigChange(void)
}
}
static void
get_atom_names(Atom *atoms, int count, char **atom_names) {
_glfwGrabErrorHandlerX11();
XGetAtomNames(_glfw.x11.display, atoms, count, atom_names);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) {
for (int i = 0; i < count; i++) {
_glfwGrabErrorHandlerX11();
atom_names[i] = XGetAtomName(_glfw.x11.display, atoms[i]);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) atom_names[i] = NULL;
}
}
}
// Process the specified X event
//
static void processEvent(XEvent *event)
@@ -1474,7 +1489,7 @@ static void processEvent(XEvent *event)
}
char **atom_names = calloc(count, sizeof(char**));
if (atom_names) {
XGetAtomNames(_glfw.x11.display, formats, count, atom_names);
get_atom_names(formats, count, atom_names);
for (i = 0; i < count; i++)
{

3
glfw/xkb_glfw.c vendored
View File

@@ -620,6 +620,9 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
}
if (key_text[0]) { debug("%s: %s ", text_type, key_text); }
}
if (xkb_sym == XKB_KEY_ISO_First_Group || xkb_sym == XKB_KEY_ISO_Last_Group || xkb_sym == XKB_KEY_ISO_Next_Group || xkb_sym == XKB_KEY_ISO_Prev_Group || xkb_sym == XKB_KEY_Mode_switch) {
return;
}
int glfw_sym = glfw_key_for_sym(xkb_sym);
bool is_fallback = false;
if (glfw_sym == GLFW_KEY_UNKNOWN && !key_text[0]) {

View File

@@ -19,7 +19,7 @@ from .config_data import all_options, type_convert
defaults: Optional[DiffOptions] = None
formats = {
formats: Dict[str, str] = {
'title': '',
'margin': '',
'text': '',
@@ -35,6 +35,7 @@ def set_formats(opts: DiffOptions) -> None:
formats['added'] = '48' + color_as_sgr(opts.added_bg)
formats['removed'] = '48' + color_as_sgr(opts.removed_bg)
formats['filler'] = '48' + color_as_sgr(opts.filler_bg)
formats['margin_filler'] = '48' + color_as_sgr(opts.margin_filler_bg or opts.filler_bg)
formats['hunk_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_margin_bg)
formats['hunk'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_bg)
formats['removed_highlight'] = '48' + color_as_sgr(opts.highlight_removed_bg)

View File

@@ -9,7 +9,9 @@ from gettext import gettext as _
from typing import Any, Dict, Sequence, Union
from kitty.conf.definition import Option, Shortcut, option_func
from kitty.conf.utils import positive_int, python_string, to_color
from kitty.conf.utils import (
positive_int, python_string, to_color, to_color_or_none
)
# }}}
@@ -78,6 +80,8 @@ c('highlight_added_bg', '#acf2bd')
c('added_margin_bg', '#cdffd8')
c('filler_bg', '#fafbfc', long_text=_('Filler (empty) line background'))
c('margin_filler_bg', 'none', option_type=to_color_or_none, long_text=_(
'Filler (empty) line background in margins, defaults to the filler background'))
c('hunk_margin_bg', '#dbedff', long_text=_('Hunk header colors'))
c('hunk_bg', '#f1f8ff')

View File

@@ -141,6 +141,7 @@ removed_format = format_func('removed')
removed_margin_format = format_func('removed_margin')
added_margin_format = format_func('added_margin')
filler_format = format_func('filler')
margin_filler_format = format_func('margin_filler')
hunk_margin_format = format_func('hunk_margin')
hunk_format = format_func('hunk')
highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_highlight', 'added')}
@@ -214,7 +215,7 @@ def split_with_highlights(line: str, width: int, highlights: List, bg_highlight:
return _split_with_highlights(line, truncate_pts, highlights, bg_highlight)
margin_bg_map = {'filler': filler_format, 'remove': removed_margin_format, 'add': added_margin_format, 'context': margin_format}
margin_bg_map = {'filler': margin_filler_format, 'remove': removed_margin_format, 'add': added_margin_format, 'context': margin_format}
text_bg_map = {'filler': filler_format, 'remove': removed_format, 'add': added_format, 'context': text_format}

View File

@@ -315,7 +315,7 @@ def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
pattern = '[0-9a-f]{7,128}'
elif args.type == 'word':
chars = args.word_characters
if not chars:
if chars is None:
chars = kitty_common_opts()['select_by_word_characters']
pattern = r'(?u)[{}\w]{{{},}}'.format(escape(chars), args.minimum_match_length)
post_processors.extend((brackets, quotes))

View File

@@ -94,6 +94,7 @@ def convert(
if scaled or width > available_width or height > available_height:
width, height = fit_image(width, height, available_width, available_height)
cmd += ['-resize', '{}x{}!'.format(width, height)]
cmd += ['-depth', '8']
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
run_imagemagick(path, cmd + [outfile.name])
# ImageMagick sometimes generated rgba images smaller than the specified

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,6 @@ from enum import IntFlag
from itertools import chain
from typing import List, Optional, Sequence, Tuple
from .constants import WindowGeometry
from .fast_data_types import (
BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
os_window_has_background_image
@@ -22,22 +21,38 @@ class BorderColor(IntFlag):
def vertical_edge(os_window_id: int, tab_id: int, color: int, width: int, top: int, bottom: int, left: int) -> None:
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
if width > 0:
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
def horizontal_edge(os_window_id: int, tab_id: int, color: int, height: int, left: int, right: int, top: int) -> None:
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
if height > 0:
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], width: int, geometry: 'WindowGeometry', base_width: int = 0) -> None:
left = geometry.left - (width + base_width)
top = geometry.top - (width + base_width)
right = geometry.right + (width + base_width)
bottom = geometry.bottom + (width + base_width)
horizontal_edge(os_window_id, tab_id, colors[1], width, left, right, top)
horizontal_edge(os_window_id, tab_id, colors[3], width, left, right, geometry.bottom + base_width)
vertical_edge(os_window_id, tab_id, colors[0], width, top, bottom, left)
vertical_edge(os_window_id, tab_id, colors[2], width, top, bottom, geometry.right + base_width)
def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], window: WindowType, borders: bool = False) -> None:
geometry = window.geometry
pl, pt = window.effective_padding('left'), window.effective_padding('top')
pr, pb = window.effective_padding('right'), window.effective_padding('bottom')
left = geometry.left - pl
top = geometry.top - pt
lr = geometry.right
right = lr + pr
bt = geometry.bottom
bottom = bt + pb
if borders:
width = window.effective_border()
bt = bottom
lr = right
left -= width
top -= width
right += width
bottom += width
pl = pr = pb = pt = width
horizontal_edge(os_window_id, tab_id, colors[1], pt, left, right, top)
horizontal_edge(os_window_id, tab_id, colors[3], pb, left, right, bt)
vertical_edge(os_window_id, tab_id, colors[0], pl, top, bottom, left)
vertical_edge(os_window_id, tab_id, colors[2], pr, top, bottom, lr)
def load_borders_program() -> None:
@@ -58,8 +73,6 @@ class Borders:
active_window: Optional[WindowType],
current_layout: LayoutType,
extra_blank_rects: Sequence[Tuple[int, int, int, int]],
padding_width: int,
border_width: int,
draw_window_borders: bool = True,
) -> None:
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
@@ -68,15 +81,14 @@ class Borders:
for br in chain(current_layout.blank_rects, extra_blank_rects):
left, top, right, bottom = br
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg)
bw, pw = border_width, padding_width
if bw + pw <= 0:
return
bw = 0
if windows:
bw = windows[0].effective_border()
draw_borders = bw > 0 and draw_window_borders
if draw_borders:
border_data = current_layout.resolve_borders(windows, active_window)
for i, w in enumerate(windows):
g = w.geometry
window_bg = w.screen.color_profile.default_bg
window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders:
@@ -85,14 +97,15 @@ class Borders:
color = BorderColor.active
else:
color = BorderColor.bell if w.needs_attention else BorderColor.inactive
colors = tuple(color if needed else window_bg for needed in next(border_data))
draw_edges(
self.os_window_id, self.tab_id, colors, bw, g, base_width=pw)
if pw > 0 and not has_background_image:
try:
colors = tuple(color if needed else window_bg for needed in next(border_data))
draw_edges(self.os_window_id, self.tab_id, colors, w, borders=True)
except StopIteration:
pass
if not has_background_image:
# Draw the background rectangles over the padding region
colors = (window_bg, window_bg, window_bg, window_bg)
draw_edges(
self.os_window_id, self.tab_id, colors, pw, g)
colors = window_bg, window_bg, window_bg, window_bg
draw_edges(self.os_window_id, self.tab_id, colors, w)
color = BorderColor.inactive
for (left, top, right, bottom) in current_layout.window_independent_borders(windows, active_window):

View File

@@ -198,7 +198,7 @@ class Boss:
os_window_id = create_os_window(
initial_window_size_func(opts_for_size, self.cached_values),
pre_show_callback,
appname, wname or self.args.name or wclass, wclass)
self.args.title or appname, wname or self.args.name or wclass, wclass)
tm = TabManager(os_window_id, self.opts, self.args, startup_session)
self.os_window_map[os_window_id] = tm
return os_window_id
@@ -339,6 +339,21 @@ class Boss:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
return response
def remote_control(self, *args: str) -> None:
from .remote_control import parse_rc_args
from .rc.base import command_for_name, parse_subcommand_cli, PayloadGetter
try:
global_opts, items = parse_rc_args(['@'] + list(args))
if not items:
return
cmd = items[0]
c = command_for_name(cmd)
opts, items = parse_subcommand_cli(c, items)
payload = c.message_to_kitty(global_opts, opts, items)
c.response_from_kitty(self, self.active_window, PayloadGetter(c, payload if isinstance(payload, dict) else {}))
except (Exception, SystemExit) as e:
self.show_error(_('remote_control mapping failed'), str(e))
def peer_message_received(self, msg_bytes: bytes) -> Optional[bytes]:
msg = msg_bytes.decode('utf-8')
cmd_prefix = '\x1bP@kitty-cmd'
@@ -534,7 +549,9 @@ class Boss:
sz = os_window_font_size(os_window_id)
if sz:
os_window_font_size(os_window_id, sz, True)
tm.update_dpi_based_sizes()
for tab in tm:
for window in tab:
window.on_dpi_change(sz)
tm.resize()
def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None:
@@ -755,7 +772,7 @@ class Boss:
window: Optional[Window] = None,
custom_callback: Optional[Callable] = None,
action_on_removal: Optional[Callable] = None
) -> Optional[Window]:
) -> Any:
orig_args, args = list(args), list(args)
from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
@@ -766,8 +783,7 @@ class Boss:
w = window
tab = w.tabref() if w else None
if end_kitten.no_ui:
end_kitten(None, getattr(w, 'id', None), self)
return None
return end_kitten(None, getattr(w, 'id', None), self)
if w is not None and tab is not None and w.overlay_for is None:
args[0:0] = [config_dir, kitten]

View File

@@ -208,7 +208,7 @@ void main() {
// Background {{{
#ifdef NEEDS_BACKROUND
float cell_has_non_default_bg = step(1, float(abs(bg_as_uint - default_colors[bg_index])));
float cell_has_non_default_bg = step(1, float(abs(bg_as_uint - default_colors[1])));
draw_bg = 1;
#if defined(BACKGROUND)

View File

@@ -649,7 +649,7 @@ draw_resizing_text(OSWindow *w) {
static inline bool
no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_wait) {
bool ans = now - w->last_render_frame_received_at > max_wait;
if (ans) log_error("No render frame received in %.2f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now));
if (ans && global_state.debug_rendering) log_error("No render frame received in %.2f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now));
return ans;
}

View File

@@ -624,12 +624,15 @@ Tell kitty to listen on the specified address for control
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can
also use abstract UNIX sockets, not associated with a file, like this:
:option:`{appname} --listen-on`=unix:@mykitty. To control kitty, you can send
:option:`{appname} --listen-on`=unix:@mykitty. Environment variables
in the setting are expanded and relative paths are resolved with
respect to the temporary directory. To control kitty, you can send
it commands with :italic:`kitty @` using the :option:`kitty @ --to` option to
specify this address. This option will be ignored, unless you set
:opt:`allow_remote_control` to yes in :file:`kitty.conf`. Note that if you run
:italic:`kitty @` within a kitty window, there is no need to specify the :italic:`--to`
option as it is read automatically from the environment.
option as it is read automatically from the environment. For UNIX sockets, this
can also be specified in :file:`kitty.conf`.
--start-as
@@ -662,10 +665,11 @@ can open a new kitty window to replay the commands with::
Path to file in which to store the raw bytes received from the child process
--debug-gl
--debug-rendering --debug-gl
type=bool-set
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
instead of ignoring them. Useful when debugging rendering problems
Debug rendering commands. This will cause all OpenGL calls to check for errors
instead of ignoring them. Also prints out miscellaneous debug information.
Useful when debugging rendering problems
--debug-keyboard

View File

@@ -29,7 +29,7 @@ your shell's rc file.
2) Add an input_parser function, this takes the input from
the shell for the text being completed and returns a list of words
alongwith a boolean indicating if we are on a new word or not. This
and a boolean indicating if we are on a new word or not. This
is passed to kitty's completion system.
3) An output_serializer function that is responsible for
@@ -47,12 +47,32 @@ def debug(*a: Any, **kw: Any) -> None:
print(*a, **kw)
class Delegate:
def __init__(self, words: Sequence[str] = (), pos: int = -1, new_word: bool = False):
self.words: Sequence[str] = words
self.pos = pos
self.num_of_unknown_args = len(words) - pos
self.new_word = new_word
def __bool__(self) -> bool:
return self.pos > -1 and self.num_of_unknown_args > 0
@property
def precommand(self) -> str:
try:
return self.words[self.pos]
except IndexError:
return ''
class Completions:
def __init__(self) -> None:
self.match_groups: Dict[str, MathGroup] = {}
self.no_space_groups: Set[str] = set()
self.files_groups: Set[str] = set()
self.delegate: Delegate = Delegate()
@lru_cache(maxsize=2)
@@ -153,6 +173,15 @@ def zsh_output_serializer(ans: Completions) -> str:
for word in matches:
cmd.append(shlex.quote(word))
lines.append(' '.join(cmd) + ';')
if ans.delegate:
if ans.delegate.num_of_unknown_args == 1 and not ans.delegate.new_word:
lines.append('_command_names -e')
elif ans.delegate.precommand:
for i in range(ans.delegate.pos + 1):
lines.append('shift words')
lines.append('(( CURRENT-- ))')
lines.append(f'_normal -p "{ans.delegate.precommand}"')
# debug('\n'.join(lines))
return '\n'.join(lines)
@@ -188,6 +217,8 @@ def completions_for_first_word(ans: Completions, prefix: str, entry_points: Iter
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
if not prefix or k.startswith(prefix)
}
if prefix:
ans.delegate = Delegate([prefix], 0)
def kitty_cli_opts(ans: Completions, prefix: Optional[str] = None) -> None:
@@ -201,9 +232,11 @@ def kitty_cli_opts(ans: Completions, prefix: Optional[str] = None) -> None:
ans.match_groups['Options'] = matches
def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
prefix = prefix or ''
if not opt:
if unknown_args.num_of_unknown_args > 0:
ans.delegate = unknown_args
return
dest = opt['dest']
if dest == 'override':
@@ -236,24 +269,24 @@ def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix:
complete_files_and_dirs(ans, prefix[len('unix:'):], files_group_name='UNIX sockets', add_prefix='unix:')
CompleteArgsFunc = Callable[[Completions, Optional[OptionDict], str], None]
CompleteArgsFunc = Callable[[Completions, Optional[OptionDict], str, Delegate], None]
def complete_alias_map(
ans: Completions,
words: Sequence[str],
new_word: str,
new_word: bool,
option_map: Dict[str, OptionDict],
complete_args: Optional[CompleteArgsFunc] = None
) -> None:
expecting_arg = False
opt: Optional[OptionDict] = None
last_word = words[-1] if words else ''
for w in words:
for i, w in enumerate(words):
if expecting_arg:
if w is last_word and not new_word:
if opt is not None and complete_args is not None:
complete_args(ans, opt, w)
complete_args(ans, opt, w, Delegate())
return
expecting_arg = False
continue
@@ -263,27 +296,27 @@ def complete_alias_map(
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(last_word)}
else:
if complete_args is not None:
complete_args(ans, None, last_word)
complete_args(ans, None, last_word, Delegate(words, i))
return
if opt is None:
if complete_args is not None:
complete_args(ans, None, '' if new_word else last_word)
complete_args(ans, None, '' if new_word else last_word, Delegate(words, i, new_word))
return # some non-option word encountered
expecting_arg = not opt.get('type', '').startswith('bool-')
if expecting_arg:
if opt is not None and complete_args is not None:
complete_args(ans, opt, '' if new_word else last_word)
complete_args(ans, opt, '' if new_word else last_word, Delegate())
else:
prefix = '' if new_word else last_word
if complete_args is not None:
complete_args(ans, None, prefix)
complete_args(ans, None, prefix, Delegate())
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(prefix)}
def complete_cli(
ans: Completions,
words: Sequence[str],
new_word: str,
new_word: bool,
seq: OptionSpecSeq,
complete_args: Optional[CompleteArgsFunc] = None
) -> None:
@@ -295,7 +328,7 @@ def complete_cli(
complete_alias_map(ans, words, new_word, option_map, complete_args)
def complete_remote_command(ans: Completions, cmd_name: str, words: Sequence[str], new_word: str) -> None:
def complete_remote_command(ans: Completions, cmd_name: str, words: Sequence[str], new_word: bool) -> None:
aliases, alias_map = options_for_cmd(cmd_name)
if not alias_map:
return
@@ -358,7 +391,7 @@ def complete_files_and_dirs(
ans.files_groups.add(files_group_name)
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
from mimetypes import guess_type
def icat_file_predicate(filename: str) -> bool:
@@ -374,7 +407,7 @@ def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str)
def remote_files_completer(spec: Tuple[str, Tuple[str, ...]]) -> CompleteArgsFunc:
name, matchers = spec
def complete_files_map(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
def complete_files_map(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
def predicate(filename: str) -> bool:
for m in matchers:
@@ -392,14 +425,14 @@ def config_file_predicate(filename: str) -> bool:
return filename.endswith('.conf')
def complete_diff_args(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
def complete_diff_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
if opt is None:
complete_files_and_dirs(ans, prefix, 'Files')
elif opt['dest'] == 'config':
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_word: str) -> None:
def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_word: bool) -> None:
try:
cd = get_kitten_cli_docs(kitten)
except SystemExit:
@@ -419,7 +452,7 @@ def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_wor
}.get(kitten))
def find_completions(words: Sequence[str], new_word: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> Completions:
def find_completions(words: Sequence[str], new_word: bool, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> Completions:
ans = Completions()
if not words or words[0] != 'kitty':
return ans

View File

@@ -23,7 +23,7 @@ from .config_data import all_options, parse_mods, type_convert
from .constants import cache_dir, defconf, is_macos
from .key_names import get_key_name_lookup, key_name_aliases
from .options_stub import Options as OptionsStub
from .typing import TypedDict
from .typing import EdgeLiteral, TypedDict
from .utils import log_error
KeySpec = Tuple[int, bool, int]
@@ -240,6 +240,15 @@ def set_colors(func: str, rest: str) -> FuncArgsType:
return func, r
@func_with_args('remote_control')
def remote_control(func: str, rest: str) -> FuncArgsType:
import shlex
r = shlex.split(rest)
if len(r) < 1:
log_error('Too few arguments to remote_control function')
return func, r
@func_with_args('nth_window')
def nth_window(func: str, rest: str) -> FuncArgsType:
try:
@@ -576,7 +585,7 @@ def handle_deprecated_macos_show_window_title_in_menubar_alias(key: str, val: st
ans['macos_show_window_title_in'] = macos_show_window_title_in
def expandvars(val: str, env: Dict[str, str]) -> str:
def expandvars(val: str, env: Dict[str, str] = {}) -> str:
def sub(m: Match) -> str:
key = m.group(1)
@@ -730,12 +739,23 @@ def initial_window_size_func(opts: OptionsStub, cached_values: Dict) -> Callable
# scaling is not needed on Wayland, but is needed on macOS. Not
# sure about X11.
xscale = yscale = 1
def effective_margin(which: EdgeLiteral) -> float:
ans: float = getattr(opts.single_window_margin_width, which)
if ans < 0:
ans = getattr(opts.window_margin_width, which)
return ans
if w_unit == 'cells':
width = cell_width * w / xscale + (dpi_x / 72) * (opts.window_margin_width + opts.window_padding_width) + 1
spacing = effective_margin('left') + effective_margin('right')
spacing += opts.window_padding_width.left + opts.window_padding_width.right
width = cell_width * w / xscale + (dpi_x / 72) * spacing + 1
else:
width = w
if h_unit == 'cells':
height = cell_height * h / yscale + (dpi_y / 72) * (opts.window_margin_width + opts.window_padding_width) + 1
spacing = effective_margin('top') + effective_margin('bottom')
spacing += opts.window_padding_width.top + opts.window_padding_width.bottom
height = cell_height * h / yscale + (dpi_y / 72) * spacing + 1
else:
height = h
return int(width), int(height)

View File

@@ -6,8 +6,8 @@
import os
from gettext import gettext as _
from typing import (
Any, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set, Tuple,
TypeVar, Union
Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set,
Tuple, TypeVar, Union
)
from . import fast_data_types as defines
@@ -16,7 +16,7 @@ from .conf.utils import (
choices, positive_float, positive_int, to_bool, to_cmdline, to_color,
to_color_or_none, unit_float
)
from .constants import config_dir, is_macos
from .constants import FloatEdges, config_dir, is_macos
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts
from .rgb import Color, color_as_int, color_as_sharp, color_from_int
@@ -675,15 +675,42 @@ that separate the inactive window from a neighbor. Note that setting
a non-zero window margin overrides this and causes all borders to be drawn.
'''))
o('window_margin_width', 0.0, option_type=positive_float, long_text=_('''
The window margin (in pts) (blank area outside the border)'''))
o('single_window_margin_width', -1000.0, option_type=float, long_text=_('''
def edge_width(x: str, converter: Callable[[str], float] = positive_float) -> FloatEdges:
parts = str(x).split()
num = len(parts)
if num == 1:
val = converter(parts[0])
return FloatEdges(val, val, val, val)
if num == 2:
v = converter(parts[0])
h = converter(parts[1])
return FloatEdges(h, v, h, v)
if num == 3:
top, h, bottom = map(converter, parts)
return FloatEdges(h, top, h, bottom)
top, right, bottom, left = map(converter, parts)
return FloatEdges(left, top, right, bottom)
def optional_edge_width(x: str) -> FloatEdges:
return edge_width(x, float)
edge_desc = _(
'A single value sets all four sides. Two values set the vertical and horizontal sides.'
' Three values set top, horizontal and bottom. Four values set top, right, bottom and left.')
o('window_margin_width', '0', option_type=edge_width, long_text=_('''
The window margin (in pts) (blank area outside the border). ''' + edge_desc))
o('single_window_margin_width', '-1', option_type=optional_edge_width, long_text=_('''
The window margin (in pts) to use when only a single window is visible.
Negative values will cause the value of :opt:`window_margin_width` to be used instead.'''))
Negative values will cause the value of :opt:`window_margin_width` to be used instead. ''' + edge_desc))
o('window_padding_width', 0.0, option_type=positive_float, long_text=_('''
The window padding (in pts) (blank area between the text and the window border)'''))
o('window_padding_width', '0', option_type=edge_width, long_text=_('''
The window padding (in pts) (blank area between the text and the window border). ''' + edge_desc))
o('placement_strategy', 'center', option_type=choices('center', 'top-left'), long_text=_('''
When the window size is not an exact multiple of the cell size, the cell area of the terminal
@@ -1012,6 +1039,19 @@ you want to prevent programs running on a remote computer over ssh from
controlling kitty.
'''))
o('listen_on', 'none', long_text=_('''
Tell kitty to listen to the specified unix/tcp socket for remote control
connections. Note that this will apply to all kitty instances. It can be
overridden by the :option:`kitty --listen-on` command line flag. This
option accepts only UNIX sockets, such as unix:${TEMP}/mykitty or (on Linux)
unix:@mykitty. Environment variables are expanded. If {kitty_pid} is present
then it is replaced by the PID of the kitty process, otherwise the PID of the kitty
process is appended to the value, with a hyphen. This option is ignored unless
you also set :opt:`allow_remote_control` to enable remote control. See the
help for :option:`kitty --listen-on` for more details.
'''))
o(
'+env', '',
add_to_default=False,

View File

@@ -20,13 +20,27 @@ class Version(NamedTuple):
appname: str = 'kitty'
version: Version = Version(0, 17, 2)
version: Version = Version(0, 17, 3)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat
base = os.path.dirname(os.path.abspath(__file__))
class Edges(NamedTuple):
left: int = 0
top: int = 0
right: int = 0
bottom: int = 0
class FloatEdges(NamedTuple):
left: float = 0
top: float = 0
right: float = 0
bottom: float = 0
class ScreenGeometry(NamedTuple):
xstart: float
ystart: float
@@ -43,6 +57,7 @@ class WindowGeometry(NamedTuple):
bottom: int
xnum: int
ynum: int
spaces: Edges = Edges()
@lru_cache(maxsize=2)

View File

@@ -312,7 +312,7 @@ harfbuzz_font_for_face(PyObject* s) {
}
void
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness, unsigned int* strikethrough_position, unsigned int* strikethrough_thickness) {
// See https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/TypoFeatures/TextSystemFeatures.html
CTFace *self = (CTFace*)s;
#define count (128 - 32)
@@ -332,6 +332,8 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
*underline_position = (unsigned int)floor(self->ascent - self->underline_position + 0.5);
*underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness));
*baseline = (unsigned int)self->ascent;
*strikethrough_position = (unsigned int)floor(*baseline * 0.65);
*strikethrough_thickness = *underline_thickness;
// float line_height = MAX(1, floor(self->ascent + self->descent + MAX(0, self->leading) + 0.5));
// Let CoreText's layout engine calculate the line height. Slower, but hopefully more accurate.
#define W "AQWMH_gyl "
@@ -503,10 +505,10 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
Region src = {.bottom=cell_height, .right=canvas_width}, dest = {.bottom=cell_height, .right=canvas_width};
render_alpha_mask(render_buf, canvas, &src, &dest, canvas_width, canvas_width);
}
if (num_cells > 1) {
// center glyphs
CGFloat delta = canvas_width - br.size.width;
if (delta > 1) right_shift_canvas(canvas, canvas_width, cell_height, (unsigned)(delta / 2.f));
if (num_cells && (center_glyph || (num_cells == 2 && *was_colored))) {
// center glyphs (two cell emoji and PUA glyphs)
CGFloat delta = (((CGFloat)canvas_width - br.size.width) / 2.f) - br.origin.x;
if (delta >= 1.f) right_shift_canvas(canvas, canvas_width, cell_height, (unsigned)(delta));
}
return true;
}

259
kitty/emoji.h generated
View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2019-10-01
// unicode data, built from the unicode standard on: 2020-04-06
// see gen-wcwidth.py
#pragma once
#include "data-types.h"
@@ -18,14 +18,10 @@ is_emoji(char_type code) {
return true;
case 0xae:
return true;
case 0x200d:
return true;
case 0x203c:
return true;
case 0x2049:
return true;
case 0x20e3:
return true;
case 0x2122:
return true;
case 0x2139:
@@ -38,8 +34,6 @@ is_emoji(char_type code) {
return true;
case 0x2328:
return true;
case 0x2388:
return true;
case 0x23cf:
return true;
case 0x23e9 ... 0x23f3:
@@ -56,15 +50,91 @@ is_emoji(char_type code) {
return true;
case 0x25fb ... 0x25fe:
return true;
case 0x2600 ... 0x2605:
case 0x2600 ... 0x2604:
return true;
case 0x2607 ... 0x2612:
case 0x260e:
return true;
case 0x2614 ... 0x2685:
case 0x2611:
return true;
case 0x2690 ... 0x2705:
case 0x2614 ... 0x2615:
return true;
case 0x2708 ... 0x2712:
case 0x2618:
return true;
case 0x261d:
return true;
case 0x2620:
return true;
case 0x2622 ... 0x2623:
return true;
case 0x2626:
return true;
case 0x262a:
return true;
case 0x262e ... 0x262f:
return true;
case 0x2638 ... 0x263a:
return true;
case 0x2640:
return true;
case 0x2642:
return true;
case 0x2648 ... 0x2653:
return true;
case 0x265f ... 0x2660:
return true;
case 0x2663:
return true;
case 0x2665 ... 0x2666:
return true;
case 0x2668:
return true;
case 0x267b:
return true;
case 0x267e ... 0x267f:
return true;
case 0x2692 ... 0x2697:
return true;
case 0x2699:
return true;
case 0x269b ... 0x269c:
return true;
case 0x26a0 ... 0x26a1:
return true;
case 0x26a7:
return true;
case 0x26aa ... 0x26ab:
return true;
case 0x26b0 ... 0x26b1:
return true;
case 0x26bd ... 0x26be:
return true;
case 0x26c4 ... 0x26c5:
return true;
case 0x26c8:
return true;
case 0x26ce ... 0x26cf:
return true;
case 0x26d1:
return true;
case 0x26d3 ... 0x26d4:
return true;
case 0x26e9 ... 0x26ea:
return true;
case 0x26f0 ... 0x26f5:
return true;
case 0x26f7 ... 0x26fa:
return true;
case 0x26fd:
return true;
case 0x2702:
return true;
case 0x2705:
return true;
case 0x2708 ... 0x270d:
return true;
case 0x270f:
return true;
case 0x2712:
return true;
case 0x2714:
return true;
@@ -90,7 +160,7 @@ is_emoji(char_type code) {
return true;
case 0x2757:
return true;
case 0x2763 ... 0x2767:
case 0x2763 ... 0x2764:
return true;
case 0x2795 ... 0x2797:
return true;
@@ -118,15 +188,11 @@ is_emoji(char_type code) {
return true;
case 0x3299:
return true;
case 0xfe0f:
case 0x1f004:
return true;
case 0x1f000 ... 0x1f0ff:
case 0x1f0cf:
return true;
case 0x1f10d ... 0x1f10f:
return true;
case 0x1f12f:
return true;
case 0x1f16c ... 0x1f171:
case 0x1f170 ... 0x1f171:
return true;
case 0x1f17e ... 0x1f17f:
return true;
@@ -134,9 +200,9 @@ is_emoji(char_type code) {
return true;
case 0x1f191 ... 0x1f19a:
return true;
case 0x1f1ad ... 0x1f1ff:
case 0x1f1e6 ... 0x1f1ff:
return true;
case 0x1f201 ... 0x1f20f:
case 0x1f201 ... 0x1f202:
return true;
case 0x1f21a:
return true;
@@ -144,44 +210,107 @@ is_emoji(char_type code) {
return true;
case 0x1f232 ... 0x1f23a:
return true;
case 0x1f23c ... 0x1f23f:
case 0x1f250 ... 0x1f251:
return true;
case 0x1f249 ... 0x1f53d:
case 0x1f300 ... 0x1f321:
return true;
case 0x1f546 ... 0x1f64f:
case 0x1f324 ... 0x1f393:
return true;
case 0x1f680 ... 0x1f6ff:
case 0x1f396 ... 0x1f397:
return true;
case 0x1f774 ... 0x1f77f:
case 0x1f399 ... 0x1f39b:
return true;
case 0x1f7d5 ... 0x1f7ff:
case 0x1f39e ... 0x1f3f0:
return true;
case 0x1f80c ... 0x1f80f:
case 0x1f3f3 ... 0x1f3f5:
return true;
case 0x1f848 ... 0x1f84f:
case 0x1f3f7 ... 0x1f4fd:
return true;
case 0x1f85a ... 0x1f85f:
case 0x1f4ff ... 0x1f53d:
return true;
case 0x1f888 ... 0x1f88f:
case 0x1f549 ... 0x1f54e:
return true;
case 0x1f8ae ... 0x1f8ff:
case 0x1f550 ... 0x1f567:
return true;
case 0x1f56f ... 0x1f570:
return true;
case 0x1f573 ... 0x1f57a:
return true;
case 0x1f587:
return true;
case 0x1f58a ... 0x1f58d:
return true;
case 0x1f590:
return true;
case 0x1f595 ... 0x1f596:
return true;
case 0x1f5a4 ... 0x1f5a5:
return true;
case 0x1f5a8:
return true;
case 0x1f5b1 ... 0x1f5b2:
return true;
case 0x1f5bc:
return true;
case 0x1f5c2 ... 0x1f5c4:
return true;
case 0x1f5d1 ... 0x1f5d3:
return true;
case 0x1f5dc ... 0x1f5de:
return true;
case 0x1f5e1:
return true;
case 0x1f5e3:
return true;
case 0x1f5e8:
return true;
case 0x1f5ef:
return true;
case 0x1f5f3:
return true;
case 0x1f5fa ... 0x1f64f:
return true;
case 0x1f680 ... 0x1f6c5:
return true;
case 0x1f6cb ... 0x1f6d2:
return true;
case 0x1f6d5 ... 0x1f6d7:
return true;
case 0x1f6e0 ... 0x1f6e5:
return true;
case 0x1f6e9:
return true;
case 0x1f6eb ... 0x1f6ec:
return true;
case 0x1f6f0:
return true;
case 0x1f6f3 ... 0x1f6fc:
return true;
case 0x1f7e0 ... 0x1f7eb:
return true;
case 0x1f90c ... 0x1f93a:
return true;
case 0x1f93c ... 0x1f945:
return true;
case 0x1f947 ... 0x1fffd:
case 0x1f947 ... 0x1f978:
return true;
case 0xe0020 ... 0xe007f:
case 0x1f97a ... 0x1f9cb:
return true;
default: return false;
}
return false;
}
static inline bool
is_emoji_modifier(char_type code) {
switch(code) {
case 0x1f3fb ... 0x1f3ff:
case 0x1f9cd ... 0x1f9ff:
return true;
case 0x1fa70 ... 0x1fa74:
return true;
case 0x1fa78 ... 0x1fa7a:
return true;
case 0x1fa80 ... 0x1fa86:
return true;
case 0x1fa90 ... 0x1faa8:
return true;
case 0x1fab0 ... 0x1fab6:
return true;
case 0x1fac0 ... 0x1fac2:
return true;
case 0x1fad0 ... 0x1fad6:
return true;
default: return false;
}
@@ -390,10 +519,12 @@ is_symbol(char_type code) {
return true;
case 0x2b76 ... 0x2b95:
return true;
case 0x2b98 ... 0x2bff:
case 0x2b97 ... 0x2bff:
return true;
case 0x2ce5 ... 0x2cea:
return true;
case 0x2e50 ... 0x2e51:
return true;
case 0x2e80 ... 0x2e99:
return true;
case 0x2e9b ... 0x2ef3:
@@ -450,6 +581,8 @@ is_symbol(char_type code) {
return true;
case 0xab5b:
return true;
case 0xab6a ... 0xab6b:
return true;
case 0xfb29:
return true;
case 0xfbb2 ... 0xfbc1:
@@ -488,7 +621,7 @@ is_symbol(char_type code) {
return true;
case 0x1018c ... 0x1018e:
return true;
case 0x10190 ... 0x1019b:
case 0x10190 ... 0x1019c:
return true;
case 0x101a0:
return true;
@@ -582,9 +715,7 @@ is_symbol(char_type code) {
return true;
case 0x1f0d1 ... 0x1f0f5:
return true;
case 0x1f110 ... 0x1f16c:
return true;
case 0x1f170 ... 0x1f1ac:
case 0x1f10d ... 0x1f1ad:
return true;
case 0x1f1e6 ... 0x1f202:
return true;
@@ -596,11 +727,11 @@ is_symbol(char_type code) {
return true;
case 0x1f260 ... 0x1f265:
return true;
case 0x1f300 ... 0x1f6d5:
case 0x1f300 ... 0x1f6d7:
return true;
case 0x1f6e0 ... 0x1f6ec:
return true;
case 0x1f6f0 ... 0x1f6fa:
case 0x1f6f0 ... 0x1f6fc:
return true;
case 0x1f700 ... 0x1f773:
return true;
@@ -618,29 +749,33 @@ is_symbol(char_type code) {
return true;
case 0x1f890 ... 0x1f8ad:
return true;
case 0x1f900 ... 0x1f90b:
case 0x1f8b0 ... 0x1f8b1:
return true;
case 0x1f90d ... 0x1f971:
case 0x1f900 ... 0x1f978:
return true;
case 0x1f973 ... 0x1f976:
return true;
case 0x1f97a ... 0x1f9a2:
return true;
case 0x1f9a5 ... 0x1f9aa:
return true;
case 0x1f9ae ... 0x1f9ca:
case 0x1f97a ... 0x1f9cb:
return true;
case 0x1f9cd ... 0x1fa53:
return true;
case 0x1fa60 ... 0x1fa6d:
return true;
case 0x1fa70 ... 0x1fa73:
case 0x1fa70 ... 0x1fa74:
return true;
case 0x1fa78 ... 0x1fa7a:
return true;
case 0x1fa80 ... 0x1fa82:
case 0x1fa80 ... 0x1fa86:
return true;
case 0x1fa90 ... 0x1fa95:
case 0x1fa90 ... 0x1faa8:
return true;
case 0x1fab0 ... 0x1fab6:
return true;
case 0x1fac0 ... 0x1fac2:
return true;
case 0x1fad0 ... 0x1fad6:
return true;
case 0x1fb00 ... 0x1fb92:
return true;
case 0x1fb94 ... 0x1fbca:
return true;
default: return false;
}

View File

@@ -327,6 +327,10 @@ FC_WIDTH_NORMAL: int
FC_SLANT_ROMAN: int
FC_SLANT_ITALIC: int
BORDERS_PROGRAM: int
PRESS: int
RELEASE: int
DRAG: int
MOVE: int
# }}}
@@ -549,7 +553,7 @@ def update_window_visibility(
def set_options(
opts: Options,
is_wayland: bool = False,
debug_gl: bool = False,
debug_rendering: bool = False,
debug_font_fallback: bool = False
) -> None:
pass
@@ -902,7 +906,7 @@ def set_font_data(
box_drawing_func: Callable[[int, int, int, float],
Tuple[int, Union[bytearray, bytes, Array]]],
prerender_func: Callable[
[int, int, int, int, int, float, float, float, float],
[int, int, int, int, int, int, int, float, float, float, float],
Tuple[Union[Array, int], ...]],
descriptor_for_idx: Callable[[int], Tuple[FontObject, bool, bool]],
bold: int, italic: int, bold_italic: int, num_symbol_fonts: int,
@@ -1128,3 +1132,7 @@ def spawn(
def key_to_bytes(glfw_key: int, smkx: bool, extended: bool, mods: int, action: int) -> bytes:
pass
def set_window_padding(os_window_id: int, tab_id: int, window_id: int, left: int, top: int, right: int, bottom: int) -> None:
pass

View File

@@ -85,7 +85,7 @@ typedef struct {
typedef struct {
FONTS_DATA_HEAD
id_type id;
unsigned int baseline, underline_position, underline_thickness;
unsigned int baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
size_t fonts_capacity, fonts_count, fallback_fonts_count;
ssize_t medium_font_idx, bold_font_idx, italic_font_idx, bi_font_idx, first_symbol_font_idx, first_fallback_font_idx;
Font *fonts;
@@ -422,8 +422,8 @@ python_send_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigne
static inline void
calc_cell_metrics(FontGroup *fg) {
unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness;
cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness);
unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness, &strikethrough_position, &strikethrough_thickness);
if (!cell_width) fatal("Failed to calculate cell width for the specified font");
unsigned int before_cell_height = cell_height;
int cw = cell_width, ch = cell_height;
@@ -455,7 +455,7 @@ calc_cell_metrics(FontGroup *fg) {
}
sprite_tracker_set_layout(&fg->sprite_tracker, cell_width, cell_height);
fg->cell_width = cell_width; fg->cell_height = cell_height;
fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness;
fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness, fg->strikethrough_position = strikethrough_position, fg->strikethrough_thickness = strikethrough_thickness;
free(fg->canvas);
fg->canvas = calloc(CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
if (!fg->canvas) fatal("Out of memory allocating canvas for font group");
@@ -1243,7 +1243,7 @@ send_prerendered_sprites(FontGroup *fg) {
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas);
do_increment(fg, &error);
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); }
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIffdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, OPT(cursor_beam_thickness), OPT(cursor_underline_thickness), fg->logical_dpi_x, fg->logical_dpi_y);
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIIIffdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->strikethrough_position, fg->strikethrough_thickness, OPT(cursor_beam_thickness), OPT(cursor_underline_thickness), fg->logical_dpi_x, fg->logical_dpi_y);
if (args == NULL) { PyErr_Print(); fatal("Failed to pre-render cells"); }
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;

View File

@@ -25,7 +25,7 @@ int get_glyph_width(PyObject *, glyph_index);
bool is_glyph_empty(PyObject *, glyph_index);
hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph);
PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);

View File

@@ -191,7 +191,9 @@ def render_special(
cell_width: int = 0, cell_height: int = 0,
baseline: int = 0,
underline_position: int = 0,
underline_thickness: int = 0
underline_thickness: int = 0,
strikethrough_position: int = 0,
strikethrough_thickness: int = 0
) -> ctypes.Array:
underline_position = min(underline_position, cell_height - underline_thickness)
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
@@ -216,8 +218,7 @@ def render_special(
t = max(1, min(cell_height - underline_position - 1, t))
dl([add_line, add_line, add_dline, add_curl][underline], underline_position, t, cell_height)
if strikethrough:
pos = int(0.65 * baseline)
dl(add_line, pos, underline_thickness, cell_height)
dl(add_line, strikethrough_position, strikethrough_thickness, cell_height)
return ans
@@ -268,6 +269,8 @@ def prerender_function(
baseline: int,
underline_position: int,
underline_thickness: int,
strikethrough_position: int,
strikethrough_thickness: int,
cursor_beam_thickness: float,
cursor_underline_thickness: float,
dpi_x: float,
@@ -276,7 +279,8 @@ def prerender_function(
# Pre-render the special underline, strikethrough and missing and cursor cells
f = partial(
render_special, cell_width=cell_width, cell_height=cell_height, baseline=baseline,
underline_position=underline_position, underline_thickness=underline_thickness)
underline_position=underline_position, underline_thickness=underline_thickness,
strikethrough_position=strikethrough_position, strikethrough_thickness=strikethrough_thickness)
c = partial(
render_cursor, cursor_beam_thickness=cursor_beam_thickness,
cursor_underline_thickness=cursor_underline_thickness, cell_width=cell_width,

View File

@@ -22,12 +22,13 @@
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include FT_TRUETYPE_TABLES_H
typedef struct {
PyObject_HEAD
FT_Face face;
unsigned int units_per_EM;
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness;
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
int hinting, hintstyle, index;
bool is_scalable, has_color;
float size_in_pts;
@@ -207,6 +208,12 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_
if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; }
hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT));
TT_OS2 *os2 = (TT_OS2*)FT_Get_Sfnt_Table(self->face, FT_SFNT_OS2);
if (os2 != NULL) {
self->strikethrough_position = os2->yStrikeoutPosition;
self->strikethrough_thickness = os2->yStrikeoutSize;
}
self->path = path;
Py_INCREF(self->path);
self->index = self->face->face_index & 0xFFFF;
@@ -263,11 +270,11 @@ static PyObject *
repr(Face *self) {
const char *ps_name = FT_Get_Postscript_Name(self->face);
return PyUnicode_FromFormat(
"Face(family=%s, style=%s, ps_name=%s, path=%S, index=%d, is_scalable=%S, has_color=%S, ascender=%i, descender=%i, height=%i, underline_position=%i, underline_thickness=%i)",
"Face(family=%s, style=%s, ps_name=%s, path=%S, index=%d, is_scalable=%S, has_color=%S, ascender=%i, descender=%i, height=%i, underline_position=%i, underline_thickness=%i, strikethrough_position=%i, strikethrough_thickness=%i)",
self->face->family_name ? self->face->family_name : "", self->face->style_name ? self->face->style_name : "",
ps_name ? ps_name: "",
self->path, self->index, self->is_scalable ? Py_True : Py_False, self->has_color ? Py_True : Py_False,
self->ascender, self->descender, self->height, self->underline_position, self->underline_thickness
self->ascender, self->descender, self->height, self->underline_position, self->underline_thickness, self->strikethrough_position, self->strikethrough_thickness
);
}
@@ -292,13 +299,24 @@ calc_cell_width(Face *self) {
}
void
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness, unsigned int* strikethrough_position, unsigned int* strikethrough_thickness) {
Face *self = (Face*)s;
*cell_width = calc_cell_width(self);
*cell_height = calc_cell_height(self, true);
*baseline = font_units_to_pixels_y(self, self->ascender);
*underline_position = MIN(*cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->underline_position)));
*underline_thickness = MAX(1, font_units_to_pixels_y(self, self->underline_thickness));
if (self->strikethrough_position != 0) {
*strikethrough_position = MIN(*cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->strikethrough_position)));
} else {
*strikethrough_position = (unsigned int)floor(*baseline * 0.65);
}
if (self->strikethrough_thickness > 0) {
*strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->strikethrough_thickness));
} else {
*strikethrough_thickness = *underline_thickness;
}
}
unsigned int
@@ -659,6 +677,8 @@ static PyMemberDef members[] = {
MEM(max_advance_height, T_INT),
MEM(underline_position, T_INT),
MEM(underline_thickness, T_INT),
MEM(strikethrough_position, T_INT),
MEM(strikethrough_thickness, T_INT),
MEM(is_scalable, T_BOOL),
MEM(path, T_OBJECT_EX),
{NULL} /* Sentinel */

View File

@@ -46,7 +46,7 @@ gl_init() {
if (!gl_version) {
fatal("Loading the OpenGL library failed");
}
if (!global_state.debug_gl) {
if (!global_state.debug_rendering) {
gladUninstallGLDebug();
}
gladSetGLPostCallback(check_for_gl_error);
@@ -59,7 +59,7 @@ gl_init() {
glad_loaded = true;
int gl_major = GLAD_VERSION_MAJOR(gl_version);
int gl_minor = GLAD_VERSION_MINOR(gl_version);
if (global_state.debug_gl) printf("GL version string: '%s' Detected version: %d.%d\n", glGetString(GL_VERSION), gl_major, gl_minor);
if (global_state.debug_rendering) printf("GL version string: '%s' Detected version: %d.%d\n", glGetString(GL_VERSION), gl_major, gl_minor);
if (gl_major < OPENGL_REQUIRED_VERSION_MAJOR || (gl_major == OPENGL_REQUIRED_VERSION_MAJOR && gl_minor < OPENGL_REQUIRED_VERSION_MINOR)) {
fatal("OpenGL version is %d.%d, version >= 3.3 required for kitty", gl_major, gl_minor);
}

View File

@@ -277,6 +277,9 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
kw['overlay_for'] = active.id
if opts.stdin_source and opts.stdin_source != 'none':
q = opts.stdin_source
if opts.stdin_add_formatting:
if q in ('@screen', '@screen_scrollback', '@alternate', '@alternate_scrollback'):
q = '@ansi_' + q[1:]
if opts.stdin_add_line_wrap_markers:
q += '_wrap'
penv, stdin = boss.process_stdin_source(window=active, stdin=q)

File diff suppressed because it is too large Load Diff

View File

@@ -684,6 +684,33 @@ report_marker_error(PyObject *marker) {
} else PyErr_Clear();
}
static inline void
apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int *match_pos) {
#define MARK { line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK; line->gpu_cells[x].attrs |= mark; }
index_type x = *cell_pos;
MARK;
if (line->cpu_cells[x].ch) {
(*match_pos)++;
if (line->cpu_cells[x].ch == '\t') {
unsigned num_cells_to_skip_for_tab = line->cpu_cells[x].cc_idx[0];
while (num_cells_to_skip_for_tab && x + 1 < line->xnum && line->cpu_cells[x+1].ch == ' ') {
x++;
num_cells_to_skip_for_tab--;
MARK;
}
} else if ((line->gpu_cells[x].attrs & WIDTH_MASK) > 1 && x + 1 < line->xnum && !line->cpu_cells[x+1].ch) {
x++;
MARK;
} else {
for (index_type i = 0; i < arraysz(line->cpu_cells[x].cc_idx); i++) {
if (line->cpu_cells[x].cc_idx[i]) (*match_pos)++;
}
}
}
*cell_pos = x + 1;
#undef MARK
}
static inline void
apply_marker(PyObject *marker, Line *line, const PyObject *text) {
unsigned int l=0, r=0, col=0, match_pos=0;
@@ -695,33 +722,20 @@ apply_marker(PyObject *marker, Line *line, const PyObject *text) {
if (iter == NULL) { report_marker_error(marker); return; }
PyObject *match;
index_type x = 0;
#define INCREMENT_MATCH_POS { \
if (line->cpu_cells[x].ch) { \
match_pos++; \
for (index_type i = 0; i < arraysz(line->cpu_cells[x].cc_idx); i++) { \
if (line->cpu_cells[x].cc_idx[i]) match_pos++; \
}}}
while ((match = PyIter_Next(iter)) && x < line->xnum) {
Py_DECREF(match);
while (match_pos < l && x < line->xnum) {
line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK;
INCREMENT_MATCH_POS;
x++;
apply_mark(line, 0, &x, &match_pos);
}
attrs_type am = (col & MARK_MASK) << MARK_SHIFT;
while(x < line->xnum && match_pos <= r) {
line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK;
line->gpu_cells[x].attrs |= am;
INCREMENT_MATCH_POS;
x++;
apply_mark(line, am, &x, &match_pos);
}
}
while(x < line->xnum) line->gpu_cells[x++].attrs &= ATTRS_MASK_WITHOUT_MARK;
Py_DECREF(iter);
while(x < line->xnum) line->gpu_cells[x++].attrs &= ATTRS_MASK_WITHOUT_MARK;
if (PyErr_Occurred()) report_marker_error(marker);
#undef INCREMENT_MATCH_POS
}
void

View File

@@ -15,7 +15,7 @@ from .child import set_default_env
from .cli import create_opts, parse_args
from .cli_stub import CLIOptions
from .conf.utils import BadLine
from .config import cached_values_for, initial_window_size_func
from .config import cached_values_for, initial_window_size_func, expandvars
from .constants import (
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
is_wayland, kitty_exe, logo_data_file, running_in_kitty
@@ -131,7 +131,7 @@ def _run_app(opts: OptionsStub, args: CLIOptions, bad_lines: Sequence[BadLine] =
window_id = create_os_window(
run_app.initial_window_size_func(opts, cached_values),
pre_show_callback,
appname, args.name or args.cls or appname,
args.title or appname, args.name or args.cls or appname,
args.cls or appname, load_all_shaders)
boss = Boss(opts, args, cached_values, new_os_window_trigger)
boss.start(window_id)
@@ -152,7 +152,7 @@ class AppRunner:
def __call__(self, opts: OptionsStub, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) -> None:
set_scale(opts.box_drawing_scale)
set_options(opts, is_wayland(), args.debug_gl, args.debug_font_fallback)
set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback)
set_font_family(opts, debug_font_matching=args.debug_font_fallback)
try:
_run_app(opts, args, bad_lines)
@@ -230,6 +230,22 @@ def get_editor_from_env(shell_env: Mapping[str, str]) -> Optional[str]:
return editor
def expand_listen_on(listen_on: str, from_config_file: bool) -> str:
listen_on = expandvars(listen_on)
if '{kitty_pid}' not in listen_on and from_config_file:
listen_on += '-{kitty_pid}'
listen_on = listen_on.replace('{kitty_pid}', str(os.getpid()))
if listen_on.startswith('unix:'):
path = listen_on[len('unix:'):]
if not path.startswith('@'):
if path.startswith('~'):
listen_on = f'unix:{os.path.expanduser(path)}'
elif not os.path.isabs(path):
import tempfile
listen_on = f'unix:{os.path.join(tempfile.gettempdir(), path)}'
return listen_on
def setup_environment(opts: OptionsStub, cli_opts: CLIOptions) -> None:
extra_env = opts.env.copy()
if opts.editor == '.':
@@ -241,7 +257,12 @@ def setup_environment(opts: OptionsStub, cli_opts: CLIOptions) -> None:
os.environ['EDITOR'] = editor
else:
os.environ['EDITOR'] = opts.editor
if cli_opts.listen_on:
from_config_file = False
if not cli_opts.listen_on and opts.listen_on.startswith('unix:'):
cli_opts.listen_on = opts.listen_on
from_config_file = True
if cli_opts.listen_on and opts.allow_remote_control != 'n':
cli_opts.listen_on = expand_listen_on(cli_opts.listen_on, from_config_file)
os.environ['KITTY_LISTEN_ON'] = cli_opts.listen_on
set_default_env(extra_env)

View File

@@ -15,6 +15,8 @@
#include "control-codes.h"
#include "monotonic.h"
extern PyTypeObject Screen_Type;
static MouseShape mouse_cursor_shape = BEAM;
typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
@@ -95,37 +97,37 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
// }}}
static inline double
window_left(Window *w, OSWindow *os_window) {
return w->geometry.left - OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0);
static inline unsigned int
window_left(Window *w) {
return w->geometry.left - w->padding.left;
}
static inline double
window_right(Window *w, OSWindow *os_window) {
return w->geometry.right + OPT(window_padding_width) * (os_window->logical_dpi_x / 72.0);
static inline unsigned int
window_right(Window *w) {
return w->geometry.right + w->padding.right;
}
static inline double
window_top(Window *w, OSWindow *os_window) {
return w->geometry.top - OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0);
static inline unsigned int
window_top(Window *w) {
return w->geometry.top - w->padding.top;
}
static inline double
window_bottom(Window *w, OSWindow *os_window) {
return w->geometry.bottom + OPT(window_padding_width) * (os_window->logical_dpi_y / 72.0);
static inline unsigned int
window_bottom(Window *w) {
return w->geometry.bottom + w->padding.bottom;
}
static inline bool
contains_mouse(Window *w, OSWindow *os_window) {
contains_mouse(Window *w) {
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
return (w->visible && window_left(w, os_window) <= x && x <= window_right(w, os_window) && window_top(w, os_window) <= y && y <= window_bottom(w, os_window));
return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w));
}
static inline double
distance_to_window(Window *w, OSWindow *os_window) {
distance_to_window(Window *w) {
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
double cx = (window_left(w, os_window) + window_right(w, os_window)) / 2.0;
double cy = (window_top(w, os_window) + window_bottom(w, os_window)) / 2.0;
double cx = (window_left(w) + window_right(w)) / 2.0;
double cy = (window_top(w) + window_bottom(w)) / 2.0;
return (x - cx) * (x - cx) + (y - cy) * (y - cy);
}
@@ -140,7 +142,7 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_
bool in_left_half = true;
double mouse_x = global_state.callback_os_window->mouse_x;
double mouse_y = global_state.callback_os_window->mouse_y;
double left = window_left(w, os_window), top = window_top(w, os_window), right = window_right(w, os_window), bottom = window_bottom(w, os_window);
double left = window_left(w), top = window_top(w), right = window_right(w), bottom = window_bottom(w);
if (clamp_to_window) {
mouse_x = MIN(MAX(mouse_x, left), right);
mouse_y = MIN(MAX(mouse_y, top), bottom);
@@ -511,7 +513,7 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
if (!*in_tab_bar && global_state.callback_os_window->num_tabs > 0) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
if (contains_mouse(t->windows + i, global_state.callback_os_window) && t->windows[i].render_data.screen) {
if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) {
*window_idx = i; return t->windows + i;
}
}
@@ -527,7 +529,7 @@ closest_window_for_event(unsigned int *window_idx) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
Window *w = t->windows + i;
double d = distance_to_window(w, global_state.callback_os_window);
double d = distance_to_window(w);
if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
}
}
@@ -698,6 +700,25 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
}
}
static PyObject*
send_mouse_event(PyObject *self UNUSED, PyObject *args) {
Screen *screen;
unsigned int x, y;
int button, action, mods;
if (!PyArg_ParseTuple(args, "O!IIiii", &Screen_Type, &screen, &x, &y, &button, &action, &mods)) return NULL;
MouseTrackingMode mode = screen->modes.mouse_tracking_mode;
if (mode == ANY_MODE || (mode == MOTION_MODE && action != MOVE) || (mode == BUTTON_MODE && (action == PRESS || action == RELEASE))) {
int sz = encode_mouse_event_impl(x + 1, y + 1, screen->modes.mouse_tracking_protocol, button, action, mods);
if (sz > 0) {
mouse_event_buf[sz] = 0;
write_escape_code_to_child(screen, CSI, mouse_event_buf);
Py_RETURN_TRUE;
}
}
Py_RETURN_FALSE;
}
static PyObject*
test_encode_mouse(PyObject *self UNUSED, PyObject *args) {
unsigned int x, y;
@@ -732,6 +753,7 @@ send_mock_mouse_event_to_window(PyObject *self UNUSED, PyObject *args) {
}
static PyMethodDef module_methods[] = {
METHODB(send_mouse_event, METH_VARARGS),
METHODB(test_encode_mouse, METH_VARARGS),
METHODB(send_mock_mouse_event_to_window, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
@@ -739,6 +761,10 @@ static PyMethodDef module_methods[] = {
bool
init_mouse(PyObject *module) {
PyModule_AddIntMacro(module, PRESS);
PyModule_AddIntMacro(module, RELEASE);
PyModule_AddIntMacro(module, DRAG);
PyModule_AddIntMacro(module, MOVE);
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

View File

@@ -11,7 +11,7 @@ static inline void parse_graphics_code(Screen *screen,
unsigned int i, code;
uint64_t lcode;
bool is_negative;
zero_at_ptr(&g);
memset(&g, 0, sizeof(g));
size_t sz;
static uint8_t payload[4096];
@@ -39,6 +39,8 @@ static inline void parse_graphics_code(Screen *screen,
};
enum KEYS key = 'a';
if (screen->parser_buf[pos] == ';')
state = AFTER_VALUE;
while (pos < screen->parser_buf_pos) {
switch (state) {
@@ -129,8 +131,8 @@ static inline void parse_graphics_code(Screen *screen,
case action: {
g.action = screen->parser_buf[pos++] & 0xff;
if (g.action != 'q' && g.action != 'd' && g.action != 't' &&
g.action != 'T' && g.action != 'p') {
if (g.action != 't' && g.action != 'd' && g.action != 'p' &&
g.action != 'q' && g.action != 'T') {
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
"value for action: 0x%x",
g.action);
@@ -140,14 +142,14 @@ static inline void parse_graphics_code(Screen *screen,
case delete_action: {
g.delete_action = screen->parser_buf[pos++] & 0xff;
if (g.delete_action != 'q' && g.delete_action != 'c' &&
g.delete_action != 'Z' && g.delete_action != 'z' &&
g.delete_action != 'x' && g.delete_action != 'X' &&
g.delete_action != 'I' && g.delete_action != 'i' &&
g.delete_action != 'P' && g.delete_action != 'y' &&
g.delete_action != 'a' && g.delete_action != 'A' &&
g.delete_action != 'Q' && g.delete_action != 'C' &&
g.delete_action != 'p' && g.delete_action != 'Y') {
if (g.delete_action != 'X' && g.delete_action != 'y' &&
g.delete_action != 'i' && g.delete_action != 'I' &&
g.delete_action != 'A' && g.delete_action != 'p' &&
g.delete_action != 'Y' && g.delete_action != 'z' &&
g.delete_action != 'a' && g.delete_action != 'P' &&
g.delete_action != 'x' && g.delete_action != 'q' &&
g.delete_action != 'Z' && g.delete_action != 'Q' &&
g.delete_action != 'c' && g.delete_action != 'C') {
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
"value for delete_action: 0x%x",
g.delete_action);
@@ -157,8 +159,8 @@ static inline void parse_graphics_code(Screen *screen,
case transmission_type: {
g.transmission_type = screen->parser_buf[pos++] & 0xff;
if (g.transmission_type != 's' && g.transmission_type != 'f' &&
g.transmission_type != 't' && g.transmission_type != 'd') {
if (g.transmission_type != 'f' && g.transmission_type != 'd' &&
g.transmission_type != 's' && g.transmission_type != 't') {
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
"value for transmission_type: 0x%x",
g.transmission_type);

View File

@@ -26,7 +26,9 @@ class Kitten(RemoteCommand):
'Run a kitten over the specified window (active window by default).'
' The :italic:`kitten_name` can be either the name of a builtin kitten'
' or the path to a python file containing a custom kitten. If a relative path'
' is used it is searched for in the kitty config directory.'
' is used it is searched for in the kitty config directory. If the kitten is a'
' no_ui kitten and its handle response method returns a string or boolean, this'
' is printed out to stdout.'
)
options_spec = MATCH_WINDOW_OPTION
argspec = 'kitten_name'
@@ -43,10 +45,13 @@ class Kitten(RemoteCommand):
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
retval = None
for window in windows:
if window:
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
if isinstance(retval, (str, bool)):
return retval
kitten = Kitten()

124
kitty/rc/set_spacing.py Normal file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import TYPE_CHECKING, Dict, Optional, List
from .base import (
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType,
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
)
if TYPE_CHECKING:
from kitty.cli_stub import SetSpacingRCOptions as CLIOptions
from kitty.options_stub import Options
def patch_window_edges(w: Window, s: Dict[str, Optional[float]]) -> None:
for k, v in s.items():
which, edge = k.lower().split('-', 1)
if edge == 'left':
w.patch_edge_width(which, 'left', v)
elif edge == 'right':
w.patch_edge_width(which, 'right', v)
elif edge == 'top':
w.patch_edge_width(which, 'top', v)
elif edge == 'bottom':
w.patch_edge_width(which, 'bottom', v)
def patch_configured_edges(opts: 'Options', s: Dict[str, Optional[float]]) -> None:
for k, val in s.items():
if val is None:
continue
which, edge = k.lower().split('-', 1)
q = f'window_{which}_width'
new_edges = getattr(opts, q)._replace(**{edge: val})
setattr(opts, q, new_edges)
class SetSpacing(RemoteCommand):
'''
settings+: An object mapping margins/paddings using canonical form {'margin-top': 50, 'padding-left': null} etc
match_window: Window to change colors in
match_tab: Tab to change colors in
all: Boolean indicating change colors everywhere or not
configured: Boolean indicating whether to change the configured colors. Must be True if reset is True
'''
short_desc = 'Set window padding and margins'
desc = (
'Set the padding and margins for the specified windows (defaults to active window).'
' For example: margin=20 or padding-left=10 or margin-h=30. The shorthand form sets'
' all values, the :code:`*-h` and :code:`*-v` variants set horizontal and vertical values.'
' The special value "default" resets to using the default value.'
' If you specify a tab rather than a window, all windows in that tab are affected.'
)
options_spec = '''\
--all -a
type=bool-set
By default, settings are only changed for the currently active window. This option will
cause colors to be changed in all windows.
--configured -c
type=bool-set
Also change the configured padding and margins (i.e. the settings kitty will use for new
windows).
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
argspec = 'MARGIN_OR_PADDING ...'
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
settings: Dict[str, Optional[float]] = {}
mapper: Dict[str, List[str]] = {}
for q in ('margin', 'padding'):
mapper[q] = f'{q}-left {q}-top {q}-right {q}-bottom'.split()
mapper[f'{q}-h'] = mapper[f'{q}-horizontal'] = f'{q}-left {q}-right'.split()
mapper[f'{q}-v'] = mapper[f'{q}-vertical'] = f'{q}-top {q}-bottom'.split()
for edge in ('left', 'top', 'right', 'bottom'):
mapper[f'{q}-{edge}'] = [f'{q}-{edge}']
if not args:
self.fatal('At least one setting must be specified')
for spec in args:
parts = spec.split('=', 1)
if len(parts) != 2:
self.fatal(f'{spec} is not a valid setting')
which = mapper.get(parts[0].lower())
if not which:
self.fatal(f'{parts[0]} is not a valid edge specification')
if parts[1].lower() == 'default':
val = None
else:
try:
val = float(parts[1])
except Exception:
self.fatal(f'{parts[1]} is not a number')
for q in which:
settings[q] = val
ans = {
'match_window': opts.match, 'match_tab': opts.match_tab,
'all': opts.all, 'configured': opts.configured,
'settings': settings
}
return ans
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
windows = self.windows_for_payload(boss, window, payload_get)
settings: Dict[str, Optional[float]] = payload_get('settings')
dirtied_tabs = {}
if payload_get('configured'):
patch_configured_edges(boss.opts, settings)
for w in windows:
patch_window_edges(w, settings)
tab = w.tabref()
if tab is not None:
dirtied_tabs[tab.id] = tab
for tab in dirtied_tabs.values():
tab.relayout()
set_spacing = SetSpacing()

View File

@@ -357,6 +357,31 @@ selection_has_screen_line(Selection *s, int y) {
return top <= y && y <= bottom;
}
static inline bool is_flag_pair(char_type a, char_type b) {
return is_flag_codepoint(a) && is_flag_codepoint(b);
}
static inline bool
draw_second_flag_codepoint(Screen *self, char_type ch) {
index_type xpos = 0, ypos = 0;
if (self->cursor->x > 1) {
ypos = self->cursor->y;
xpos = self->cursor->x - 2;
} else if (self->cursor->y > 0 && self->columns > 1) {
ypos = self->cursor->y - 1;
xpos = self->columns - 2;
} else return false;
linebuf_init_line(self->linebuf, ypos);
CPUCell *cell = self->linebuf->line->cpu_cells + xpos;
if (!is_flag_pair(cell->ch, ch) || cell->cc_idx[0]) return false;
line_add_combining_char(self->linebuf->line, ch, xpos);
self->is_dirty = true;
if (selection_has_screen_line(&self->selection, ypos)) self->selection = EMPTY_SELECTION;
linebuf_mark_line_dirty(self->linebuf, ypos);
return true;
}
static inline void
draw_combining_char(Screen *self, char_type ch) {
bool has_prev_char = false;
@@ -372,6 +397,17 @@ draw_combining_char(Screen *self, char_type ch) {
xpos = self->columns - 1;
has_prev_char = true;
}
if (self->cursor->x > 0) {
ypos = self->cursor->y;
linebuf_init_line(self->linebuf, ypos);
xpos = self->cursor->x - 1;
has_prev_char = true;
} else if (self->cursor->y > 0) {
ypos = self->cursor->y - 1;
linebuf_init_line(self->linebuf, ypos);
xpos = self->columns - 1;
has_prev_char = true;
}
if (has_prev_char) {
line_add_combining_char(self->linebuf->line, ch, xpos);
self->is_dirty = true;
@@ -412,6 +448,10 @@ screen_draw(Screen *self, uint32_t och) {
draw_combining_char(self, ch);
return;
}
bool is_flag = is_flag_codepoint(ch);
if (UNLIKELY(is_flag)) {
if (draw_second_flag_codepoint(self, ch)) return;
}
int char_width = wcwidth_std(ch);
if (UNLIKELY(char_width < 1)) {
if (char_width == 0) return;
@@ -1827,37 +1867,54 @@ screen_wcswidth(PyObject UNUSED *self, PyObject *str) {
unsigned long ans = 0;
char_type prev_ch = 0;
int prev_width = 0;
bool in_sgr = false;
typedef enum {NORMAL, IN_SGR, FLAG_PAIR_STARTED} WCSState;
WCSState state = NORMAL;
for (i = 0; i < len; i++) {
char_type ch = PyUnicode_READ(kind, data, i);
if (in_sgr) {
if (ch == 'm') in_sgr = false;
continue;
}
if (ch == 0x1b && i + 1 < len && PyUnicode_READ(kind, data, i + 1) == '[') { in_sgr = true; continue; }
if (ch == 0xfe0f) {
if (is_emoji_presentation_base(prev_ch) && prev_width == 1) {
ans += 1;
prev_width = 2;
} else prev_width = 0;
} else if (ch == 0xfe0e) {
if (is_emoji_presentation_base(prev_ch) && prev_width == 2) {
ans -= 1;
prev_width = 1;
} else prev_width = 0;
} else {
int w = wcwidth_std(ch);
switch(w) {
case -1:
case 0:
prev_width = 0; break;
case 2:
prev_width = 2; break;
default:
prev_width = 1; break;
}
ans += prev_width;
}
switch(state) {
case IN_SGR: {
if (ch == 'm') state = NORMAL;
} continue;
case FLAG_PAIR_STARTED: {
state = NORMAL;
if (is_flag_pair(prev_ch, ch)) break;
} /* fallthrough */
case NORMAL: {
if (ch == 0x1b && i + 1 < len && PyUnicode_READ(kind, data, i + 1) == '[') { state = IN_SGR; continue; }
switch(ch) {
case 0xfe0f: {
if (is_emoji_presentation_base(prev_ch) && prev_width == 1) {
ans += 1;
prev_width = 2;
} else prev_width = 0;
} break;
case 0xfe0e: {
if (is_emoji_presentation_base(prev_ch) && prev_width == 2) {
ans -= 1;
prev_width = 1;
} else prev_width = 0;
} break;
default: {
if (is_flag_codepoint(ch)) state = FLAG_PAIR_STARTED;
int w = wcwidth_std(ch);
switch(w) {
case -1:
case 0:
prev_width = 0; break;
case 2:
prev_width = 2; break;
default:
prev_width = 1; break;
}
ans += prev_width;
} break;
} break; // switch(ch)
} break; // case NORMAL
} // switch(state)
prev_ch = ch;
}
return PyLong_FromUnsignedLong(ans);

View File

@@ -8,7 +8,7 @@ from typing import Generator, List, NamedTuple, Optional, Tuple, Union
from .cli_stub import CLIOptions
from .config_data import to_layout_names
from .constants import kitty_exe
from .constants import FloatEdges, kitty_exe
from .layout import all_layouts
from .options_stub import Options
from .typing import SpecialWindowInstance
@@ -19,8 +19,8 @@ class WindowSizeOpts(NamedTuple):
initial_window_width: Tuple[int, str]
initial_window_height: Tuple[int, str]
window_margin_width: float
window_padding_width: float
window_margin_width: FloatEdges
window_padding_width: FloatEdges
remember_window_size: bool
@@ -53,7 +53,7 @@ class Session:
self.tabs[-1].next_title = title.strip()
def set_layout(self, val: str) -> None:
if val not in all_layouts:
if val.partition(':')[0] not in all_layouts:
raise ValueError('{} is not a valid layout'.format(val))
self.tabs[-1].layout = val

View File

@@ -27,13 +27,25 @@ GlobalState global_state = {{0}};
#define END_WITH_OS_WINDOW break; }}
#define WITH_TAB(os_window_id, tab_id) \
for (size_t o = 0; o < global_state.num_os_windows; o++) { \
for (size_t o = 0, tab_found = 0; o < global_state.num_os_windows && !tab_found; o++) { \
OSWindow *osw = global_state.os_windows + o; \
if (osw->id == os_window_id) { \
for (size_t t = 0; t < osw->num_tabs; t++) { \
if (osw->tabs[t].id == tab_id) { \
Tab *tab = osw->tabs + t;
#define END_WITH_TAB break; }}}}
#define END_WITH_TAB tab_found = 1; break; }}}}
#define WITH_WINDOW(os_window_id, tab_id, window_id) \
for (size_t o = 0, window_found = 0; o < global_state.num_os_windows && !window_found; o++) { \
OSWindow *osw = global_state.os_windows + o; \
if (osw->id == os_window_id) { \
for (size_t t = 0; t < osw->num_tabs && !window_found; t++) { \
if (osw->tabs[t].id == tab_id) { \
Tab *tab = osw->tabs + t; \
for (size_t w = 0; w < tab->num_windows; w++) { \
Window *window = tab->windows + w;
#define END_WITH_WINDOW break; }}}}}
#define WITH_OS_WINDOW_REFS \
id_type cb_window_id = 0, focused_window_id = 0; \
@@ -577,14 +589,14 @@ PYWRAP1(handle_for_window_id) {
PYWRAP1(set_options) {
PyObject *ret, *opts;
int is_wayland = 0, debug_gl = 0, debug_font_fallback = 0;
PA("O|ppp", &opts, &is_wayland, &debug_gl, &debug_font_fallback);
int is_wayland = 0, debug_rendering = 0, debug_font_fallback = 0;
PA("O|ppp", &opts, &is_wayland, &debug_rendering, &debug_font_fallback);
global_state.is_wayland = is_wayland ? true : false;
#ifdef __APPLE__
global_state.has_render_frames = true;
#endif
if (global_state.is_wayland) global_state.has_render_frames = true;
global_state.debug_gl = debug_gl ? true : false;
global_state.debug_rendering = debug_rendering ? true : false;
global_state.debug_font_fallback = debug_font_fallback ? true : false;
#define GA(name) ret = PyObject_GetAttrString(opts, #name); if (ret == NULL) return NULL;
#define SS(name, dest, convert) { GA(name); dest = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
@@ -603,7 +615,6 @@ PYWRAP1(set_options) {
S(dim_opacity, PyFloat_AsFloat);
S(dynamic_background_opacity, PyObject_IsTrue);
S(inactive_text_alpha, PyFloat_AsFloat);
S(window_padding_width, PyFloat_AsFloat);
S(scrollback_pager_history_size, PyLong_AsUnsignedLong);
S(cursor_shape, PyLong_AsLong);
S(cursor_beam_thickness, PyFloat_AsFloat);
@@ -839,6 +850,16 @@ fix_window_idx(Tab *tab, id_type window_id, unsigned int *window_idx) {
return false;
}
PYWRAP1(set_window_padding) {
id_type os_window_id, tab_id, window_id;
unsigned int left, top, right, bottom;
PA("KKKIIII", &os_window_id, &tab_id, &window_id, &left, &top, &right, &bottom);
WITH_WINDOW(os_window_id, tab_id, window_id);
window->padding.left = left; window->padding.top = top; window->padding.right = right; window->padding.bottom = bottom;
END_WITH_WINDOW;
Py_RETURN_NONE;
}
PYWRAP1(set_window_render_data) {
#define A(name) &(d.name)
#define B(name) &(g.name)
@@ -1080,6 +1101,7 @@ static PyMethodDef module_methods[] = {
MW(add_borders_rect, METH_VARARGS),
MW(set_tab_bar_render_data, METH_VARARGS),
MW(set_window_render_data, METH_VARARGS),
MW(set_window_padding, METH_VARARGS),
MW(viewport_for_window, METH_VARARGS),
MW(cell_size_for_window, METH_VARARGS),
MW(os_window_has_background_image, METH_VARARGS),

View File

@@ -53,7 +53,6 @@ typedef struct {
bool dynamic_background_opacity;
float inactive_text_alpha;
float window_padding_width;
Edge tab_bar_edge;
unsigned long tab_bar_min_tabs;
DisableLigature disable_ligatures;
@@ -106,6 +105,9 @@ typedef struct {
double x, y;
bool in_left_half_of_cell;
} mouse_pos;
struct {
unsigned int left, top, right, bottom;
} padding;
WindowGeometry geometry;
ClickQueue click_queue;
monotonic_t last_drag_scroll_at;
@@ -196,7 +198,7 @@ typedef struct {
bool terminate;
bool is_wayland;
bool has_render_frames;
bool debug_gl, debug_font_fallback;
bool debug_rendering, debug_font_fallback;
bool has_pending_resizes, has_pending_closes;
bool in_sequence_mode;
bool tab_bar_hidden;

View File

@@ -7,8 +7,8 @@ from collections import deque
from contextlib import suppress
from functools import partial
from typing import (
Deque, Dict, Generator, Iterator, List, NamedTuple,
Optional, Pattern, Sequence, Tuple, cast
Any, Deque, Dict, Generator, Iterator, List, NamedTuple, Optional, Pattern,
Sequence, Tuple, cast
)
from .borders import Borders
@@ -17,17 +17,17 @@ from .cli_stub import CLIOptions
from .constants import appname, is_macos, is_wayland
from .fast_data_types import (
add_tab, attach_window, detach_window, get_boss, mark_tab_bar_dirty,
next_window_id, pt_to_px, remove_tab, remove_window, ring_bell,
set_active_tab, swap_tabs, x11_window_id
next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
swap_tabs, x11_window_id
)
from .layout import (
Layout, Rect, create_layout_object_for, evict_cached_layouts
)
from .options_stub import Options
from .tab_bar import TabBar, TabBarData
from .typing import SessionTab, SessionType, TypedDict
from .utils import log_error, resolved_shell
from .window import Window, WindowDict, Watchers
from .typing import TypedDict, SessionTab, SessionType
from .window import Watchers, Window, WindowDict
class TabDict(TypedDict):
@@ -87,11 +87,10 @@ class Tab: # {{{
if not self.id:
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id))
self.opts, self.args = tab_manager.opts, tab_manager.args
self.recalculate_sizes(update_layout=False)
self.name = getattr(session_tab, 'name', '')
self.enabled_layouts = [x.lower() for x in getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts]
self.borders = Borders(self.os_window_id, self.id, self.opts)
self.windows: Deque[Window] = deque()
self.windows: List[Window] = []
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
setattr(self, which + '_window', partial(self.nth_window, num=i))
self._last_used_layout: Optional[str] = None
@@ -113,15 +112,6 @@ class Tab: # {{{
self._set_current_layout(l0)
self.startup(session_tab)
def recalculate_sizes(self, update_layout: bool = True) -> None:
self.margin_width, self.padding_width, self.single_window_margin_width = map(
lambda x: pt_to_px(getattr(self.opts, x), self.os_window_id), (
'window_margin_width', 'window_padding_width', 'single_window_margin_width'))
self.border_width = pt_to_px(self.opts.window_border_width, self.os_window_id)
if update_layout and self.current_layout:
self.current_layout.update_sizes(
self.margin_width, self.single_window_margin_width, self.padding_width, self.border_width)
def take_over_from(self, other_tab: 'Tab') -> None:
self.name, self.cwd = other_tab.name, other_tab.cwd
self.enabled_layouts = list(other_tab.enabled_layouts)
@@ -129,12 +119,12 @@ class Tab: # {{{
self._set_current_layout(other_tab._current_layout_name)
self._last_used_layout = other_tab._last_used_layout
orig_windows = deque(other_tab.windows)
orig_windows = list(other_tab.windows)
orig_history = deque(other_tab.active_window_history)
orig_active = other_tab._active_window_idx
for window in other_tab.windows:
detach_window(other_tab.os_window_id, other_tab.id, window.id)
other_tab.windows = deque()
other_tab.windows = []
other_tab._active_window_idx = 0
self.active_window_history = orig_history
self.windows = orig_windows
@@ -157,6 +147,18 @@ class Tab: # {{{
self.new_window(cmd=cmd)
self.set_active_window_idx(session_tab.active_window_idx)
def serialize_state(self) -> Dict[str, Any]:
return {
'version': 1,
'id': self.id,
'active_window_idx': self.active_window_idx,
'windows': [w.serialize_state() for w in self],
'current_layout': self._current_layout_name,
'last_used_layout': self._last_used_layout,
'active_window_history': list(self.active_window_history),
'name': self.name,
}
@property
def active_window_idx(self) -> int:
return self._active_window_idx
@@ -227,20 +229,17 @@ class Tab: # {{{
if tm is not None:
visible_windows = [w for w in self.windows if w.is_visible_in_layout]
w = self.active_window
ly = self.current_layout
self.borders(
windows=visible_windows, active_window=w,
current_layout=self.current_layout, extra_blank_rects=tm.blank_rects,
padding_width=self.padding_width, border_width=self.border_width,
draw_window_borders=self.current_layout.needs_window_borders and len(visible_windows) > 1
current_layout=ly, extra_blank_rects=tm.blank_rects,
draw_window_borders=(ly.needs_window_borders and len(visible_windows) > 1) or ly.must_draw_borders
)
if w is not None:
w.change_titlebar_color()
def create_layout_object(self, name: str) -> Layout:
return create_layout_object_for(
name, self.os_window_id, self.id, self.margin_width,
self.single_window_margin_width, self.padding_width,
self.border_width)
return create_layout_object_for(name, self.os_window_id, self.id)
def next_layout(self) -> None:
if len(self.enabled_layouts) > 1:
@@ -546,7 +545,7 @@ class Tab: # {{{
evict_cached_layouts(self.id)
for w in self.windows:
w.destroy()
self.windows = deque()
self.windows = []
def __repr__(self) -> str:
return 'Tab(title={}, id={})'.format(self.name or self.title, hex(id(self)))
@@ -640,10 +639,6 @@ class TabManager: # {{{
def update_tab_bar_data(self) -> None:
self.tab_bar.update(self.tab_bar_data)
def update_dpi_based_sizes(self) -> None:
for tab in self.tabs:
tab.recalculate_sizes()
def resize(self, only_tabs: bool = False) -> None:
if not only_tabs:
if not self.tab_bar_hidden:
@@ -703,6 +698,14 @@ class TabManager: # {{{
'active_window_history': list(tab.active_window_history),
}
def serialize_state(self) -> Dict[str, Any]:
return {
'version': 1,
'id': self.os_window_id,
'tabs': [tab.serialize_state() for tab in self],
'active_tab_idx': self.active_tab_idx,
}
@property
def active_tab(self) -> Optional[Tab]:
return self.tabs[self.active_tab_idx] if self.tabs else None

View File

@@ -198,6 +198,8 @@ string_capabilities = {
'rmso': r'\E[27m',
# Exit underline mode
'rmul': r'\E[24m',
# Exit strikethrough mode
'rmxx': r'\E[29m',
# Reset string1 (empty OSC sequence to exit OSC/OTH modes, and regular reset)
'rs1': r'\E]\E\\\Ec',
# Save cursor
@@ -224,6 +226,8 @@ string_capabilities = {
'smso': r'\E[7m',
# Enter underline mode
'smul': r'\E[4m',
# Enter strikethrough mode
'smxx': r'\E[9m',
# Clear all tab stops
'tbc': r'\E[3g',
# To status line (used to set window titles)
@@ -351,6 +355,7 @@ termcap_aliases.update({
'ei': 'rmir',
'se': 'rmso',
'ue': 'rmul',
'Te': 'rmxx',
'r1': 'rs1',
'sc': 'sc',
'AB': 'setab',
@@ -363,6 +368,7 @@ termcap_aliases.update({
'im': 'smir',
'so': 'smso',
'us': 'smul',
'Ts': 'smxx',
'ct': 'tbc',
'cv': 'vpa',
'ZH': 'sitm',

557
kitty/unicode-data.c generated

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
#pragma once
#include "data-types.h"
#define VS15 1280
#define VS16 1281
#define VS15 1285
#define VS16 1286
bool is_combining_char(char_type ch);
bool is_ignored_char(char_type ch);
@@ -29,3 +29,9 @@ static inline bool
is_private_use(char_type ch) {
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
}
static inline bool
is_flag_codepoint(char_type ch) {
return 0x1F1E6 <= ch && ch <= 0x1F1FF;
}

395
kitty/wcwidth-std.h generated
View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2019-10-01
// unicode data, built from the unicode standard on: 2020-04-06
// see gen-wcwidth.py
#pragma once
#include "data-types.h"
@@ -8,7 +8,12 @@ START_ALLOW_CASE_RANGE
static int
wcwidth_std(int32_t code) {
switch(code) {
// Marks (2275 codepoints) {{{
// Flags (26 codepoints) {{{
case 0x1f1e6 ... 0x1f1ff:
return 2;
// }}}
// Marks (2302 codepoints) {{{
case 0x0:
return 0;
case 0x300 ... 0x36f:
@@ -127,7 +132,7 @@ wcwidth_std(int32_t code) {
return 0;
case 0xb4b ... 0xb4d:
return 0;
case 0xb56 ... 0xb57:
case 0xb55 ... 0xb57:
return 0;
case 0xb62 ... 0xb63:
return 0;
@@ -181,7 +186,7 @@ wcwidth_std(int32_t code) {
return 0;
case 0xd62 ... 0xd63:
return 0;
case 0xd82 ... 0xd83:
case 0xd81 ... 0xd83:
return 0;
case 0xdca:
return 0;
@@ -275,7 +280,7 @@ wcwidth_std(int32_t code) {
return 0;
case 0x1a7f:
return 0;
case 0x1ab0 ... 0x1abe:
case 0x1ab0 ... 0x1ac0:
return 0;
case 0x1b00 ... 0x1b04:
return 0;
@@ -335,6 +340,8 @@ wcwidth_std(int32_t code) {
return 0;
case 0xa823 ... 0xa827:
return 0;
case 0xa82c:
return 0;
case 0xa880 ... 0xa881:
return 0;
case 0xa8b4 ... 0xa8c5:
@@ -405,6 +412,8 @@ wcwidth_std(int32_t code) {
return 0;
case 0x10d24 ... 0x10d27:
return 0;
case 0x10eab ... 0x10eac:
return 0;
case 0x10f46 ... 0x10f50:
return 0;
case 0x11000 ... 0x11002:
@@ -429,6 +438,8 @@ wcwidth_std(int32_t code) {
return 0;
case 0x111c9 ... 0x111cc:
return 0;
case 0x111ce ... 0x111cf:
return 0;
case 0x1122c ... 0x11237:
return 0;
case 0x1123e:
@@ -473,6 +484,16 @@ wcwidth_std(int32_t code) {
return 0;
case 0x1182c ... 0x1183a:
return 0;
case 0x11930 ... 0x11935:
return 0;
case 0x11937 ... 0x11938:
return 0;
case 0x1193b ... 0x1193e:
return 0;
case 0x11940:
return 0;
case 0x11942 ... 0x11943:
return 0;
case 0x119d1 ... 0x119d7:
return 0;
case 0x119da ... 0x119e0:
@@ -527,6 +548,10 @@ wcwidth_std(int32_t code) {
return 0;
case 0x16f8f ... 0x16f92:
return 0;
case 0x16fe4:
return 0;
case 0x16ff0 ... 0x16ff1:
return 0;
case 0x1bc9d ... 0x1bc9e:
return 0;
case 0x1d165 ... 0x1d169:
@@ -637,7 +662,7 @@ wcwidth_std(int32_t code) {
return -3;
// }}}
// Text Presentation (218 codepoints) {{{
// Text Presentation (219 codepoints) {{{
case 0x23:
return 1;
case 0x2a:
@@ -728,6 +753,8 @@ wcwidth_std(int32_t code) {
return 1;
case 0x26a0:
return 1;
case 0x26a7:
return 1;
case 0x26b0 ... 0x26b1:
return 1;
case 0x26c8:
@@ -1227,7 +1254,7 @@ wcwidth_std(int32_t code) {
return -2;
// }}}
// East Asian double width (181868 codepoints) {{{
// East Asian double width (182418 codepoints) {{{
case 0x1100 ... 0x115f:
return 2;
case 0x231a ... 0x231b:
@@ -1320,9 +1347,7 @@ wcwidth_std(int32_t code) {
return 2;
case 0x3131 ... 0x318e:
return 2;
case 0x3190 ... 0x31ba:
return 2;
case 0x31c0 ... 0x31e3:
case 0x3190 ... 0x31e3:
return 2;
case 0x31f0 ... 0x321e:
return 2;
@@ -1360,7 +1385,9 @@ wcwidth_std(int32_t code) {
return 2;
case 0x17000 ... 0x187f7:
return 2;
case 0x18800 ... 0x18af2:
case 0x18800 ... 0x18cd5:
return 2;
case 0x18d00 ... 0x18d08:
return 2;
case 0x1b000 ... 0x1b11e:
return 2;
@@ -1434,33 +1461,37 @@ wcwidth_std(int32_t code) {
return 2;
case 0x1f6d0 ... 0x1f6d2:
return 2;
case 0x1f6d5:
case 0x1f6d5 ... 0x1f6d7:
return 2;
case 0x1f6eb ... 0x1f6ec:
return 2;
case 0x1f6f4 ... 0x1f6fa:
case 0x1f6f4 ... 0x1f6fc:
return 2;
case 0x1f7e0 ... 0x1f7eb:
return 2;
case 0x1f90d ... 0x1f971:
case 0x1f90c ... 0x1f93a:
return 2;
case 0x1f973 ... 0x1f976:
case 0x1f93c ... 0x1f945:
return 2;
case 0x1f97a ... 0x1f9a2:
case 0x1f947 ... 0x1f978:
return 2;
case 0x1f9a5 ... 0x1f9aa:
return 2;
case 0x1f9ae ... 0x1f9ca:
case 0x1f97a ... 0x1f9cb:
return 2;
case 0x1f9cd ... 0x1f9ff:
return 2;
case 0x1fa70 ... 0x1fa73:
case 0x1fa70 ... 0x1fa74:
return 2;
case 0x1fa78 ... 0x1fa7a:
return 2;
case 0x1fa80 ... 0x1fa82:
case 0x1fa80 ... 0x1fa86:
return 2;
case 0x1fa90 ... 0x1fa95:
case 0x1fa90 ... 0x1faa8:
return 2;
case 0x1fab0 ... 0x1fab6:
return 2;
case 0x1fac0 ... 0x1fac2:
return 2;
case 0x1fad0 ... 0x1fad6:
return 2;
case 0x20000 ... 0x2fffd:
return 2;
@@ -1468,12 +1499,10 @@ wcwidth_std(int32_t code) {
return 2;
// }}}
// Emoji Presentation (26 codepoints) {{{
case 0x1f1e6 ... 0x1f1ff:
return 2;
// Emoji Presentation (0 codepoints) {{{
// }}}
// Not assigned in the unicode character database (766326 codepoints) {{{
// Not assigned in the unicode character database (765365 codepoints) {{{
case 0x378 ... 0x379:
return -4;
case 0x380 ... 0x383:
@@ -1520,7 +1549,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0x8b5:
return -4;
case 0x8be ... 0x8d2:
case 0x8c8 ... 0x8d2:
return -4;
case 0x984:
return -4;
@@ -1628,7 +1657,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0xb49 ... 0xb4a:
return -4;
case 0xb4e ... 0xb55:
case 0xb4e ... 0xb54:
return -4;
case 0xb58 ... 0xb5b:
return -4;
@@ -1718,8 +1747,6 @@ wcwidth_std(int32_t code) {
return -4;
case 0xcf3 ... 0xcff:
return -4;
case 0xd04:
return -4;
case 0xd0d:
return -4;
case 0xd11:
@@ -1732,7 +1759,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0xd64 ... 0xd65:
return -4;
case 0xd80 ... 0xd81:
case 0xd80:
return -4;
case 0xd84:
return -4;
@@ -1906,7 +1933,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0x1aae ... 0x1aaf:
return -4;
case 0x1abf ... 0x1aff:
case 0x1ac1 ... 0x1aff:
return -4;
case 0x1b4c ... 0x1b4f:
return -4;
@@ -1980,7 +2007,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0x2b74 ... 0x2b75:
return -4;
case 0x2b96 ... 0x2b97:
case 0x2b96:
return -4;
case 0x2c2f:
return -4;
@@ -2016,7 +2043,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0x2ddf:
return -4;
case 0x2e50 ... 0x2e7f:
case 0x2e53 ... 0x2e7f:
return -4;
case 0x2e9a:
return -4;
@@ -2036,8 +2063,6 @@ wcwidth_std(int32_t code) {
return -4;
case 0x318f:
return -4;
case 0x31bb ... 0x31bf:
return -4;
case 0x31e4 ... 0x31ef:
return -4;
case 0x321f:
@@ -2052,9 +2077,9 @@ wcwidth_std(int32_t code) {
return -4;
case 0xa7c0 ... 0xa7c1:
return -4;
case 0xa7c7 ... 0xa7f6:
case 0xa7cb ... 0xa7f4:
return -4;
case 0xa82c ... 0xa82f:
case 0xa82d ... 0xa82f:
return -4;
case 0xa83a ... 0xa83f:
return -4;
@@ -2094,7 +2119,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0xab2f:
return -4;
case 0xab68 ... 0xab6f:
case 0xab6c ... 0xab6f:
return -4;
case 0xabee ... 0xabef:
return -4;
@@ -2180,7 +2205,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0x1018f:
return -4;
case 0x1019c ... 0x1019f:
case 0x1019d ... 0x1019f:
return -4;
case 0x101a1 ... 0x101cf:
return -4;
@@ -2298,11 +2323,19 @@ wcwidth_std(int32_t code) {
return -4;
case 0x10d3a ... 0x10e5f:
return -4;
case 0x10e7f ... 0x10eff:
case 0x10e7f:
return -4;
case 0x10eaa:
return -4;
case 0x10eae ... 0x10eaf:
return -4;
case 0x10eb2 ... 0x10eff:
return -4;
case 0x10f28 ... 0x10f2f:
return -4;
case 0x10f5a ... 0x10fdf:
case 0x10f5a ... 0x10faf:
return -4;
case 0x10fcc ... 0x10fdf:
return -4;
case 0x10ff7 ... 0x10fff:
return -4;
@@ -2320,12 +2353,10 @@ wcwidth_std(int32_t code) {
return -4;
case 0x11135:
return -4;
case 0x11147 ... 0x1114f:
case 0x11148 ... 0x1114f:
return -4;
case 0x11177 ... 0x1117f:
return -4;
case 0x111ce ... 0x111cf:
return -4;
case 0x111e0:
return -4;
case 0x111f5 ... 0x111ff:
@@ -2378,11 +2409,9 @@ wcwidth_std(int32_t code) {
return -4;
case 0x11375 ... 0x113ff:
return -4;
case 0x1145a:
return -4;
case 0x1145c:
return -4;
case 0x11460 ... 0x1147f:
case 0x11462 ... 0x1147f:
return -4;
case 0x114c8 ... 0x114cf:
return -4;
@@ -2412,7 +2441,21 @@ wcwidth_std(int32_t code) {
return -4;
case 0x118f3 ... 0x118fe:
return -4;
case 0x11900 ... 0x1199f:
case 0x11907 ... 0x11908:
return -4;
case 0x1190a ... 0x1190b:
return -4;
case 0x11914:
return -4;
case 0x11917:
return -4;
case 0x11936:
return -4;
case 0x11939 ... 0x1193a:
return -4;
case 0x11947 ... 0x1194f:
return -4;
case 0x1195a ... 0x1199f:
return -4;
case 0x119a8 ... 0x119a9:
return -4;
@@ -2466,7 +2509,9 @@ wcwidth_std(int32_t code) {
return -4;
case 0x11daa ... 0x11edf:
return -4;
case 0x11ef9 ... 0x11fbf:
case 0x11ef9 ... 0x11faf:
return -4;
case 0x11fb1 ... 0x11fbf:
return -4;
case 0x11ff2 ... 0x11ffe:
return -4;
@@ -2514,11 +2559,15 @@ wcwidth_std(int32_t code) {
return -4;
case 0x16fa0 ... 0x16fdf:
return -4;
case 0x16fe4 ... 0x16fff:
case 0x16fe5 ... 0x16fef:
return -4;
case 0x16ff2 ... 0x16fff:
return -4;
case 0x187f8 ... 0x187ff:
return -4;
case 0x18af3 ... 0x1afff:
case 0x18cd6 ... 0x18cff:
return -4;
case 0x18d09 ... 0x1afff:
return -4;
case 0x1b11f ... 0x1b14f:
return -4;
@@ -2714,11 +2763,7 @@ wcwidth_std(int32_t code) {
return -4;
case 0x1f0f6 ... 0x1f0ff:
return -4;
case 0x1f10d ... 0x1f10f:
return -4;
case 0x1f16d ... 0x1f16f:
return -4;
case 0x1f1ad ... 0x1f1e5:
case 0x1f1ae ... 0x1f1e5:
return -4;
case 0x1f203 ... 0x1f20f:
return -4;
@@ -2730,11 +2775,11 @@ wcwidth_std(int32_t code) {
return -4;
case 0x1f266 ... 0x1f2ff:
return -4;
case 0x1f6d6 ... 0x1f6df:
case 0x1f6d8 ... 0x1f6df:
return -4;
case 0x1f6ed ... 0x1f6ef:
return -4;
case 0x1f6fb ... 0x1f6ff:
case 0x1f6fd ... 0x1f6ff:
return -4;
case 0x1f774 ... 0x1f77f:
return -4;
@@ -2750,31 +2795,37 @@ wcwidth_std(int32_t code) {
return -4;
case 0x1f888 ... 0x1f88f:
return -4;
case 0x1f8ae ... 0x1f8ff:
case 0x1f8ae ... 0x1f8af:
return -4;
case 0x1f90c:
case 0x1f8b2 ... 0x1f8ff:
return -4;
case 0x1f972:
case 0x1f979:
return -4;
case 0x1f977 ... 0x1f979:
return -4;
case 0x1f9a3 ... 0x1f9a4:
return -4;
case 0x1f9ab ... 0x1f9ad:
return -4;
case 0x1f9cb ... 0x1f9cc:
case 0x1f9cc:
return -4;
case 0x1fa54 ... 0x1fa5f:
return -4;
case 0x1fa6e ... 0x1fa6f:
return -4;
case 0x1fa74 ... 0x1fa77:
case 0x1fa75 ... 0x1fa77:
return -4;
case 0x1fa7b ... 0x1fa7f:
return -4;
case 0x1fa83 ... 0x1fa8f:
case 0x1fa87 ... 0x1fa8f:
return -4;
case 0x1fa96 ... 0x1ffff:
case 0x1faa9 ... 0x1faaf:
return -4;
case 0x1fab7 ... 0x1fabf:
return -4;
case 0x1fac3 ... 0x1facf:
return -4;
case 0x1fad7 ... 0x1faff:
return -4;
case 0x1fb93:
return -4;
case 0x1fbcb ... 0x1fbef:
return -4;
case 0x1fbfa ... 0x1ffff:
return -4;
case 0x2fffe ... 0x2ffff:
return -4;
@@ -2827,11 +2878,7 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x23cf:
return true;
case 0x23e9 ... 0x23ea:
return true;
case 0x23ed ... 0x23ef:
return true;
case 0x23f1 ... 0x23f3:
case 0x23e9 ... 0x23f3:
return true;
case 0x23f8 ... 0x23fa:
return true;
@@ -2895,6 +2942,8 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x26a0 ... 0x26a1:
return true;
case 0x26a7:
return true;
case 0x26aa ... 0x26ab:
return true;
case 0x26b0 ... 0x26b1:
@@ -2905,7 +2954,7 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x26c8:
return true;
case 0x26cf:
case 0x26ce ... 0x26cf:
return true;
case 0x26d1:
return true;
@@ -2921,9 +2970,9 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x2702:
return true;
case 0x2708 ... 0x2709:
case 0x2705:
return true;
case 0x270c ... 0x270d:
case 0x2708 ... 0x270d:
return true;
case 0x270f:
return true;
@@ -2937,20 +2986,32 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x2721:
return true;
case 0x2728:
return true;
case 0x2733 ... 0x2734:
return true;
case 0x2744:
return true;
case 0x2747:
return true;
case 0x2753:
case 0x274c:
return true;
case 0x274e:
return true;
case 0x2753 ... 0x2755:
return true;
case 0x2757:
return true;
case 0x2763 ... 0x2764:
return true;
case 0x2795 ... 0x2797:
return true;
case 0x27a1:
return true;
case 0x27b0:
return true;
case 0x27bf:
return true;
case 0x2934 ... 0x2935:
return true;
case 0x2b05 ... 0x2b07:
@@ -2971,125 +3032,51 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x1f004:
return true;
case 0x1f0cf:
return true;
case 0x1f170 ... 0x1f171:
return true;
case 0x1f17e ... 0x1f17f:
return true;
case 0x1f202:
case 0x1f18e:
return true;
case 0x1f191 ... 0x1f19a:
return true;
case 0x1f1e6 ... 0x1f1ff:
return true;
case 0x1f201 ... 0x1f202:
return true;
case 0x1f21a:
return true;
case 0x1f22f:
return true;
case 0x1f237:
case 0x1f232 ... 0x1f23a:
return true;
case 0x1f30d ... 0x1f30f:
case 0x1f250 ... 0x1f251:
return true;
case 0x1f315:
case 0x1f300 ... 0x1f321:
return true;
case 0x1f31c:
return true;
case 0x1f321:
return true;
case 0x1f324 ... 0x1f32c:
return true;
case 0x1f336:
return true;
case 0x1f378:
return true;
case 0x1f37d:
return true;
case 0x1f393:
case 0x1f324 ... 0x1f393:
return true;
case 0x1f396 ... 0x1f397:
return true;
case 0x1f399 ... 0x1f39b:
return true;
case 0x1f39e ... 0x1f39f:
case 0x1f39e ... 0x1f3f0:
return true;
case 0x1f3a7:
case 0x1f3f3 ... 0x1f3f5:
return true;
case 0x1f3ac ... 0x1f3ae:
case 0x1f3f7 ... 0x1f4fd:
return true;
case 0x1f3c2:
case 0x1f4ff ... 0x1f53d:
return true;
case 0x1f3c4:
return true;
case 0x1f3c6:
return true;
case 0x1f3ca ... 0x1f3ce:
return true;
case 0x1f3d4 ... 0x1f3e0:
return true;
case 0x1f3ed:
return true;
case 0x1f3f3:
return true;
case 0x1f3f5:
return true;
case 0x1f3f7:
return true;
case 0x1f408:
return true;
case 0x1f415:
return true;
case 0x1f41f:
return true;
case 0x1f426:
return true;
case 0x1f43f:
return true;
case 0x1f441 ... 0x1f442:
return true;
case 0x1f446 ... 0x1f449:
return true;
case 0x1f44d ... 0x1f44e:
return true;
case 0x1f453:
return true;
case 0x1f46a:
return true;
case 0x1f47d:
return true;
case 0x1f4a3:
return true;
case 0x1f4b0:
return true;
case 0x1f4b3:
return true;
case 0x1f4bb:
return true;
case 0x1f4bf:
return true;
case 0x1f4cb:
return true;
case 0x1f4da:
return true;
case 0x1f4df:
return true;
case 0x1f4e4 ... 0x1f4e6:
return true;
case 0x1f4ea ... 0x1f4ed:
return true;
case 0x1f4f7:
return true;
case 0x1f4f9 ... 0x1f4fb:
return true;
case 0x1f4fd:
return true;
case 0x1f508:
return true;
case 0x1f50d:
return true;
case 0x1f512 ... 0x1f513:
return true;
case 0x1f549 ... 0x1f54a:
case 0x1f549 ... 0x1f54e:
return true;
case 0x1f550 ... 0x1f567:
return true;
case 0x1f56f ... 0x1f570:
return true;
case 0x1f573 ... 0x1f579:
case 0x1f573 ... 0x1f57a:
return true;
case 0x1f587:
return true;
@@ -3097,7 +3084,9 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x1f590:
return true;
case 0x1f5a5:
case 0x1f595 ... 0x1f596:
return true;
case 0x1f5a4 ... 0x1f5a5:
return true;
case 0x1f5a8:
return true;
@@ -3121,39 +3110,49 @@ is_emoji_presentation_base(uint32_t code) {
return true;
case 0x1f5f3:
return true;
case 0x1f5fa:
case 0x1f5fa ... 0x1f64f:
return true;
case 0x1f610:
case 0x1f680 ... 0x1f6c5:
return true;
case 0x1f687:
case 0x1f6cb ... 0x1f6d2:
return true;
case 0x1f68d:
return true;
case 0x1f691:
return true;
case 0x1f694:
return true;
case 0x1f698:
return true;
case 0x1f6ad:
return true;
case 0x1f6b2:
return true;
case 0x1f6b9 ... 0x1f6ba:
return true;
case 0x1f6bc:
return true;
case 0x1f6cb:
return true;
case 0x1f6cd ... 0x1f6cf:
case 0x1f6d5 ... 0x1f6d7:
return true;
case 0x1f6e0 ... 0x1f6e5:
return true;
case 0x1f6e9:
return true;
case 0x1f6eb ... 0x1f6ec:
return true;
case 0x1f6f0:
return true;
case 0x1f6f3:
case 0x1f6f3 ... 0x1f6fc:
return true;
case 0x1f7e0 ... 0x1f7eb:
return true;
case 0x1f90c ... 0x1f93a:
return true;
case 0x1f93c ... 0x1f945:
return true;
case 0x1f947 ... 0x1f978:
return true;
case 0x1f97a ... 0x1f9cb:
return true;
case 0x1f9cd ... 0x1f9ff:
return true;
case 0x1fa70 ... 0x1fa74:
return true;
case 0x1fa78 ... 0x1fa7a:
return true;
case 0x1fa80 ... 0x1fa86:
return true;
case 0x1fa90 ... 0x1faa8:
return true;
case 0x1fab0 ... 0x1fab6:
return true;
case 0x1fac0 ... 0x1fac2:
return true;
case 0x1fad0 ... 0x1fad6:
return true;
default: return false;
}

View File

@@ -25,14 +25,15 @@ from .fast_data_types import (
MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
STRIKETHROUGH, TINT_PROGRAM, Screen, add_window, cell_size_for_window,
compile_program, get_boss, get_clipboard_string, init_cell_program,
set_clipboard_string, set_titlebar_color, set_window_render_data,
update_window_title, update_window_visibility, viewport_for_window
pt_to_px, set_clipboard_string, set_titlebar_color, set_window_padding,
set_window_render_data, update_window_title, update_window_visibility,
viewport_for_window
)
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 BossType, ChildType, TabType, TypedDict
from .typing import BossType, ChildType, EdgeLiteral, 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,
@@ -185,6 +186,25 @@ def text_sanitizer(as_ansi: bool, add_wrap_markers: bool) -> Callable[[str], str
return remove_wrap_markers
class EdgeWidths:
left: Optional[float]
top: Optional[float]
right: Optional[float]
bottom: Optional[float]
def __init__(self, serialized: Optional[Dict[str, Optional[float]]] = None):
if serialized is not None:
self.left = serialized['left']
self.right = serialized['right']
self.top = serialized['top']
self.bottom = serialized['bottom']
else:
self.left = self.top = self.right = self.bottom = None
def serialize(self) -> Dict[str, Optional[float]]:
return {'left': self.left, 'right': self.right, 'top': self.top, 'bottom': self.bottom}
class Window:
def __init__(
@@ -211,6 +231,8 @@ class Window:
self.title_stack: Deque[str] = deque(maxlen=10)
self.allow_remote_control = child.allow_remote_control
self.id = add_window(tab.os_window_id, tab.id, self.title)
self.margin = EdgeWidths()
self.padding = EdgeWidths()
if not self.id:
raise Exception('No tab with id: {} in OS Window: {} was found, or the window counter wrapped'.format(tab.id, tab.os_window_id))
self.tab_id = tab.id
@@ -229,11 +251,47 @@ class Window:
else:
setup_colors(self.screen, opts)
def on_dpi_change(self, font_sz: float) -> None:
self.update_effective_padding()
def change_tab(self, tab: TabType) -> None:
self.tab_id = tab.id
self.os_window_id = tab.os_window_id
self.tabref = weakref.ref(tab)
def effective_margin(self, edge: EdgeLiteral, is_single_window: bool = False) -> int:
q = getattr(self.margin, edge)
if q is not None:
return pt_to_px(q, self.os_window_id)
if is_single_window:
q = getattr(self.opts.single_window_margin_width, edge)
if q > -0.1:
return pt_to_px(q, self.os_window_id)
q = getattr(self.opts.window_margin_width, edge)
return pt_to_px(q, self.os_window_id)
def effective_padding(self, edge: EdgeLiteral) -> int:
q = getattr(self.padding, edge)
if q is not None:
return pt_to_px(q, self.os_window_id)
q = getattr(self.opts.window_padding_width, edge)
return pt_to_px(q, self.os_window_id)
def update_effective_padding(self) -> None:
set_window_padding(
self.os_window_id, self.tab_id, self.id,
self.effective_padding('left'), self.effective_padding('top'),
self.effective_padding('right'), self.effective_padding('bottom'))
def patch_edge_width(self, which: str, edge: EdgeLiteral, val: Optional[float]) -> None:
q = self.padding if which == 'padding' else self.margin
setattr(q, edge, val)
if q is self.padding:
self.update_effective_padding()
def effective_border(self) -> int:
return pt_to_px(self.opts.window_border_width, self.os_window_id)
@property
def title(self) -> str:
return self.override_title or self.child_title
@@ -254,6 +312,24 @@ class Window:
foreground_processes=self.child.foreground_processes
)
def serialize_state(self) -> Dict[str, Any]:
return {
'version': 1,
'id': self.id,
'child_title': self.child_title,
'override_title': self.override_title,
'default_title': self.default_title,
'title_stack': list(self.title_stack),
'allow_remote_control': self.allow_remote_control,
'overlay_window_id': self.overlay_window_id,
'overlay_for': self.overlay_for,
'cwd': self.child.current_cwd or self.child.cwd,
'env': self.child.environ,
'cmdline': self.child.cmdline,
'margin': self.margin.serialize(),
'padding': self.padding.serialize(),
}
@property
def current_colors(self) -> Dict:
return self.screen.color_profile.as_dict()
@@ -323,6 +399,7 @@ class Window:
sg = self.update_position(new_geometry)
self.geometry = g = new_geometry
set_window_render_data(self.os_window_id, self.tab_id, self.id, window_idx, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen, *g[:4])
self.update_effective_padding()
def contains(self, x: int, y: int) -> bool:
g = self.geometry

View File

@@ -359,6 +359,10 @@ class TestDataTypes(BaseTest):
self.ae(wcswidth('\033a\033[2mb'), 2)
self.ae(wcswidth('\u25b6\ufe0f'), 2)
self.ae(wcswidth('\U0001f610\ufe0e'), 1)
self.ae(wcswidth('\U0001f1e6a'), 3)
self.ae(wcswidth('\U0001F1E6a\U0001F1E8a'), 6)
self.ae(wcswidth('\U0001F1E6\U0001F1E8a'), 3)
self.ae(wcswidth('\U0001F1E6\U0001F1E8\U0001F1E6'), 4)
# Regional indicator symbols (unicode flags) are defined as having
# Emoji_Presentation so must have width 2
self.ae(tuple(map(w, '\U0001f1ee\U0001f1f3')), (2, 2))

View File

@@ -80,26 +80,25 @@ class TestParser(BaseTest):
km(modify_key_bytes(base_key, num).decode('ascii')[1:], key)
def test_encode_mouse_event(self):
PRESS, RELEASE, DRAG, MOVE = range(4)
NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL = range(4)
L, M, R = defines.GLFW_MOUSE_BUTTON_LEFT, defines.GLFW_MOUSE_BUTTON_MIDDLE, defines.GLFW_MOUSE_BUTTON_RIGHT
protocol = SGR_PROTOCOL
def enc(button=L, action=PRESS, mods=0, x=1, y=1):
def enc(button=L, action=defines.PRESS, mods=0, x=1, y=1):
return defines.test_encode_mouse(x, y, protocol, button, action, mods)
self.ae(enc(), '<0;1;1M')
self.ae(enc(action=RELEASE), '<0;1;1m')
self.ae(enc(action=MOVE), '<35;1;1M')
self.ae(enc(action=DRAG), '<32;1;1M')
self.ae(enc(action=defines.RELEASE), '<0;1;1m')
self.ae(enc(action=defines.MOVE), '<35;1;1M')
self.ae(enc(action=defines.DRAG), '<32;1;1M')
self.ae(enc(R), '<2;1;1M')
self.ae(enc(R, action=RELEASE), '<2;1;1m')
self.ae(enc(R, action=DRAG), '<34;1;1M')
self.ae(enc(R, action=defines.RELEASE), '<2;1;1m')
self.ae(enc(R, action=defines.DRAG), '<34;1;1M')
self.ae(enc(M), '<1;1;1M')
self.ae(enc(M, action=RELEASE), '<1;1;1m')
self.ae(enc(M, action=DRAG), '<33;1;1M')
self.ae(enc(M, action=defines.RELEASE), '<1;1;1m')
self.ae(enc(M, action=defines.DRAG), '<33;1;1M')
self.ae(enc(x=1234, y=5678), '<0;1234;5678M')
self.ae(enc(mods=defines.GLFW_MOD_SHIFT), '<4;1;1M')

View File

@@ -3,9 +3,9 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from kitty.config import defaults
from kitty.fast_data_types import pt_to_px
from kitty.layout import Horizontal, Stack, Tall, Grid, idx_for_id, Splits
from kitty.constants import WindowGeometry
from kitty.layout import Grid, Horizontal, Splits, Stack, Tall, idx_for_id
from kitty.window import EdgeWidths
from . import BaseTest
@@ -18,6 +18,17 @@ class Window:
self.overlay_window_id = overlay_window_id
self.is_visible_in_layout = True
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
self.padding = EdgeWidths()
self.margin = EdgeWidths()
def effective_border(self):
return 1
def effective_padding(self, edge):
return 1
def effective_margin(self, edge, is_single_window=False):
return 0 if is_single_window else 1
def set_visible_in_layout(self, idx, val):
self.is_visible_in_layout = bool(val)
@@ -29,8 +40,7 @@ class Window:
def create_layout(cls, opts=None, border_width=2):
if opts is None:
opts = defaults
mw, pw = map(pt_to_px, (opts.window_margin_width, opts.window_padding_width))
ans = cls(1, 1, mw, mw, pw, border_width)
ans = cls(1, 1)
ans.set_active_window_in_os_window = lambda idx: None
ans.swap_windows_in_os_window = lambda a, b: None
return ans

View File

@@ -456,12 +456,16 @@ class TestScreen(BaseTest):
self.ae(as_text(True), '\x1b[mababa\x1b[mbabab\n\x1b[mc\n\n')
def test_user_marking(self):
def cells(*a, y=0, mark=3):
return [(x, y, mark) for x in a]
s = self.create_screen()
s.draw('abaa')
s.carriage_return(), s.linefeed()
s.draw('xyxyx')
s.set_marker(marker_from_regex('a', 3))
self.ae(s.marked_cells(), [(0, 0, 3), (2, 0, 3), (3, 0, 3)])
self.ae(s.marked_cells(), cells(0, 2, 3))
s.set_marker()
self.ae(s.marked_cells(), [])
@@ -488,3 +492,19 @@ class TestScreen(BaseTest):
self.assertTrue(s.scroll_to_next_mark(0, False))
self.ae(s.scrolled_by, 10 - i - 1)
self.ae(s.scrolled_by, 0)
s = self.create_screen()
s.draw('🐈ab')
s.set_marker(marker_from_regex('🐈', 3))
self.ae(s.marked_cells(), cells(0, 1))
s.set_marker(marker_from_regex('🐈a', 3))
self.ae(s.marked_cells(), cells(0, 1, 2))
s.set_marker(marker_from_regex('a', 3))
self.ae(s.marked_cells(), cells(2))
s = self.create_screen(cols=20)
s.tab()
s.draw('ab')
s.set_marker(marker_from_regex('a', 3))
self.ae(s.marked_cells(), cells(8))
s.set_marker(marker_from_regex('\t', 3))
self.ae(s.marked_cells(), cells(*range(8)))

View File

@@ -31,6 +31,7 @@
#define KITTY_LIB_DIR_NAME "lib"
#endif
#ifndef __FreeBSD__
static inline bool
safe_realpath(const char* src, char *buf, size_t buf_sz) {
char* ans = realpath(src, NULL);
@@ -39,6 +40,7 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
free(ans);
return true;
}
#endif
static inline void
set_bundle_exe_dir(const wchar_t *exe_dir) {

View File

@@ -215,6 +215,7 @@ xterm-kitty|KovIdTTY,
rmkx=\E[?1l,
rmso=\E[27m,
rmul=\E[24m,
rmxx=\E[29m,
rs1=\E]\E\\\Ec,
sc=\E7,
setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
@@ -231,6 +232,7 @@ xterm-kitty|KovIdTTY,
smkx=\E[?1h,
smso=\E[7m,
smul=\E[4m,
smxx=\E[9m,
tbc=\E[3g,
tsl=\E]2;,
vpa=\E[%i%p1%dd,

Binary file not shown.