Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d32202b3a | ||
|
|
4f4d562670 | ||
|
|
28fc9c69da | ||
|
|
6edf00e699 | ||
|
|
a0d6445dda | ||
|
|
e74797037c | ||
|
|
3fb125dba7 | ||
|
|
3c8640f9ad | ||
|
|
2e63a4c83f | ||
|
|
9da5bee178 | ||
|
|
2fc6f3b542 | ||
|
|
4754f42e51 | ||
|
|
941fe918fb | ||
|
|
f4e82e972e | ||
|
|
f66dbf111d | ||
|
|
1a2da5c49d | ||
|
|
8327dabb29 | ||
|
|
195ea6c140 | ||
|
|
fcc0707174 | ||
|
|
aa9c3cd634 | ||
|
|
b541341681 | ||
|
|
e5d50aca6d | ||
|
|
b738f4d4ff | ||
|
|
5c38aabf0e | ||
|
|
03e9ed6947 | ||
|
|
63493fad22 | ||
|
|
696b857f91 | ||
|
|
53ae2bbe0d | ||
|
|
01069e919d | ||
|
|
fc0095f505 | ||
|
|
421528e130 | ||
|
|
7b70356515 | ||
|
|
e803505aad | ||
|
|
ad1ff455a0 | ||
|
|
7692cb5c3d | ||
|
|
a94e208db3 | ||
|
|
ba9d5cf3ff | ||
|
|
0dff0d03f9 | ||
|
|
c92aca5d19 | ||
|
|
5762baeed7 | ||
|
|
601cf01222 | ||
|
|
0eef48b516 | ||
|
|
3c1954f329 | ||
|
|
cc29640604 | ||
|
|
d0329460b8 | ||
|
|
717c0c96de | ||
|
|
b3f161efce | ||
|
|
0286940163 | ||
|
|
d4a4ee386f | ||
|
|
33498f9133 | ||
|
|
da3d4d1bef | ||
|
|
c69b8870d2 | ||
|
|
85b55b31b6 | ||
|
|
948919e42b | ||
|
|
85c40090ed | ||
|
|
98b50ec145 | ||
|
|
1ab4f7d2d0 | ||
|
|
98801b6fa6 | ||
|
|
bdd68284b5 | ||
|
|
a61d70ea9a | ||
|
|
e271719dd2 | ||
|
|
a08efa1fc5 | ||
|
|
ffac3600e0 | ||
|
|
6499f8ac24 | ||
|
|
63307cb1d8 | ||
|
|
0a6fb362f6 | ||
|
|
457fcbfb90 | ||
|
|
3b6277b9e2 | ||
|
|
e6e61028b1 | ||
|
|
e9be66126b | ||
|
|
681fe7f2f2 | ||
|
|
0196920533 | ||
|
|
5f5bb3ace0 | ||
|
|
3c970e0122 | ||
|
|
43af6e3b8a | ||
|
|
ac8cfc12fb | ||
|
|
b19472e0f5 | ||
|
|
b4d08044a0 | ||
|
|
b2fe02e63f | ||
|
|
d1a5d30233 | ||
|
|
bba30138e1 | ||
|
|
082546a1e7 | ||
|
|
41ffd58e26 | ||
|
|
b4f24e4e1e | ||
|
|
75c8ffd715 | ||
|
|
688606ff7c | ||
|
|
2e3f9dffa7 | ||
|
|
859033415b | ||
|
|
1f1b52b580 | ||
|
|
4e7bf80447 | ||
|
|
e39df50884 | ||
|
|
8d6a9d45c2 | ||
|
|
0862e85577 | ||
|
|
0b9a37139e | ||
|
|
6357f7b811 | ||
|
|
24197dc422 | ||
|
|
9bc2ab3245 | ||
|
|
bf4e8c490c | ||
|
|
590a4d70ee | ||
|
|
40341d2ddd | ||
|
|
0cd2c41ee3 | ||
|
|
5d65cdb6a8 | ||
|
|
759a15ccbb | ||
|
|
5aead5eebe | ||
|
|
a325a5fdee | ||
|
|
931e91f1a7 | ||
|
|
6e13509720 | ||
|
|
5b4844e71e | ||
|
|
66cbbea9bb | ||
|
|
b65119b8a9 | ||
|
|
86703da917 | ||
|
|
610e9afdf0 | ||
|
|
e3af9f68d3 | ||
|
|
747ac85e7c | ||
|
|
8c23f9e526 | ||
|
|
e6e339fcd3 | ||
|
|
f04680ac47 | ||
|
|
90985cc846 | ||
|
|
63917944f2 | ||
|
|
793605f80f | ||
|
|
93a281c7e3 | ||
|
|
8f1c6c4d74 | ||
|
|
1610dba7ab | ||
|
|
d9d419991c | ||
|
|
f9e86b19aa | ||
|
|
fffb976e43 | ||
|
|
0da566b49f | ||
|
|
da55717d20 | ||
|
|
254836902c | ||
|
|
d01f8d1865 | ||
|
|
a46c3f7007 | ||
|
|
4e58062025 |
@@ -4,6 +4,119 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
|
||||
0.17.4 [2020-05-09]
|
||||
--------------------
|
||||
|
||||
- Allow showing the name of the current layout and the number of windows
|
||||
in tab titles (:iss:`2634`)
|
||||
|
||||
- macOS: Fix a regression in the previous release that caused ligatures to be
|
||||
not be centered horizontally (:iss:`2591`)
|
||||
|
||||
- By default, double clicking no longer considers the : as part of words, see
|
||||
:opt:`select_by_word_characters` (:iss:`2602`)
|
||||
|
||||
- Fix a regression that caused clicking in the padding/margins of windows in
|
||||
the stack layout to switch the window to the first window (:iss:`2604`)
|
||||
|
||||
- macOS: Fix a regression that broke drag and drop (:iss:`2605`)
|
||||
|
||||
- Report modifier key state when sending wheel events to the terminal program
|
||||
|
||||
- Fix kitty @ send-text not working with text larger than 1024 bytes when using
|
||||
:option:`kitty --listen-on` (:iss:`2607`)
|
||||
|
||||
- Wayland: Fix OS window title not updating for hidden windows (:iss:`2629`)
|
||||
|
||||
- Fix :opt:`background_tint` making the window semi-transparent (:iss:`2618`)
|
||||
|
||||
|
||||
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]
|
||||
--------------------
|
||||
|
||||
- Add a :option:`launch --watcher` option that allows defining callbacks
|
||||
that are called for various events in the window's life-cycle (:iss:`2440`)
|
||||
|
||||
- Fix a regression in 0.17 that broke drawing of borders with non-minimal
|
||||
borders (:iss:`2474`)
|
||||
|
||||
- Hints kitten: Allow copying to primary selection as well as clipboard
|
||||
(:pull:`2487`)
|
||||
|
||||
- Add a new mappable action ``close_other_windows_in_tab`` to close all but the
|
||||
active window (:iss:`2484`)
|
||||
|
||||
- Hints kitten: Adjust the default regex used to detect line numbers to handle
|
||||
line+column numbers (:iss:`2268`)
|
||||
|
||||
- Fix blank space at the start of tab bar in the powerline style when first tab is
|
||||
inactive (:iss:`2478`)
|
||||
|
||||
- Fix regression causing incorrect rendering of separators in tab bar when
|
||||
defining a tab bar background color (:pull:`2480`)
|
||||
|
||||
- Fix a regression in 0.17 that broke the kitty @ launch remote command and
|
||||
also broke the --tab-title option when creating a new tab. (:iss:`2488`)
|
||||
|
||||
- Linux: Fix selection of fonts with multiple width variants not preferring
|
||||
the normal width faces (:iss:`2491`)
|
||||
|
||||
|
||||
0.17.1 [2020-03-24]
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/>`_.
|
||||
|
||||
@@ -192,6 +192,12 @@ Similarly, you can detach the current tab, with::
|
||||
# asks which OS Window to move the tab into
|
||||
map ctrl+f4 detach_tab ask
|
||||
|
||||
Finally, you can define a shortcut to close all windows in a tab other than
|
||||
the currently active window::
|
||||
|
||||
map f9 close_other_windows_in_tab
|
||||
|
||||
|
||||
Other keyboard shortcuts
|
||||
----------------------------------
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -21,6 +21,12 @@ Then you can simply use ``icat image.png`` to view images.
|
||||
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
||||
work.
|
||||
|
||||
.. note::
|
||||
|
||||
kitty's image display protocol may not work when used within a terminal
|
||||
multiplexer such as ``screen`` or ``tmux``, depending on whether the
|
||||
multiplexer has added support for it or not.
|
||||
|
||||
|
||||
.. program:: kitty +kitten icat
|
||||
|
||||
|
||||
@@ -61,6 +61,35 @@ currently active kitty window. For example::
|
||||
map f1 launch my-program @active-kitty-window-id
|
||||
|
||||
|
||||
Watching launched windows
|
||||
---------------------------
|
||||
|
||||
The :option:`launch --watcher` option allows you to specify python functions
|
||||
that will be called at specific events, such as when the window is resized or
|
||||
closed. Simply specify the path to a python module that specifies callback
|
||||
functions for the events you are interested in, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_resize(boss, window, data):
|
||||
# Here data will contain old_geometry and new_geometry
|
||||
|
||||
def on_close(boss, window, data):
|
||||
# called when window is closed, typically when the program running in
|
||||
# it exits.
|
||||
|
||||
|
||||
Every callback is passed a reference to the global ``Boss`` object as well as
|
||||
the ``Window`` object the action is occurring on. The ``data`` object is
|
||||
mapping that contains event dependent data. Some useful methods and attributes
|
||||
for the ``Window`` object are: ``as_text(as_ans=False, add_history=False,
|
||||
add_wrap_markers=False, alternate_screen=False)`` with which you can get the
|
||||
contents of the window and its scrollback buffer. Similarly,
|
||||
``window.child.pid`` is the PID of the processes that was launched
|
||||
in the window and ``window.id`` is the internal kitty ``id`` of the
|
||||
window.
|
||||
|
||||
|
||||
Syntax reference
|
||||
------------------
|
||||
|
||||
|
||||
@@ -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
|
||||
-----------------------------------------------
|
||||
|
||||
|
||||
@@ -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) {{
|
||||
|
||||
105
gen-wcwidth.py
105
gen-wcwidth.py
@@ -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)
|
||||
|
||||
|
||||
@@ -1133,7 +1133,7 @@ is_ascii_control_char(char x) {
|
||||
break;
|
||||
}
|
||||
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags, translateFlags([event modifierFlags]));
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||
|
||||
@@ -55,7 +55,7 @@ def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
|
||||
|
||||
def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_compile: Callable, module: str = 'x11') -> Env:
|
||||
ans = env.copy()
|
||||
ans.cflags.append('-fpic')
|
||||
ans.cflags.append('-fPIC')
|
||||
ans.cppflags.append('-D_GLFW_' + module.upper())
|
||||
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
||||
|
||||
@@ -95,7 +95,7 @@ def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_co
|
||||
for p in ans.wayland_protocols:
|
||||
ans.sources.append(wayland_protocol_file_name(p))
|
||||
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
|
||||
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
||||
for dep in 'wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
|
||||
|
||||
3
glfw/glfw3.h
vendored
3
glfw/glfw3.h
vendored
@@ -1550,6 +1550,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
|
||||
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
|
||||
* scrolling cancelled and value 6 if scrolling may begin soon.
|
||||
* @param[int] mods The keyboard modifiers
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
@@ -1559,7 +1560,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int);
|
||||
|
||||
/*! @brief The function pointer type for key callbacks.
|
||||
*
|
||||
|
||||
4
glfw/input.c
vendored
4
glfw/input.c
vendored
@@ -300,10 +300,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, GLFWkeyevent* ev)
|
||||
|
||||
// Notifies shared code of a scroll event
|
||||
//
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags)
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods)
|
||||
{
|
||||
if (window->callbacks.scroll)
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags);
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags, mods);
|
||||
}
|
||||
|
||||
// Notifies shared code of a mouse button click event
|
||||
|
||||
2
glfw/internal.h
vendored
2
glfw/internal.h
vendored
@@ -755,7 +755,7 @@ void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
|
||||
|
||||
void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods);
|
||||
void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev);
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags);
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods);
|
||||
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
|
||||
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered);
|
||||
|
||||
2
glfw/wl_init.c
vendored
2
glfw/wl_init.c
vendored
@@ -334,7 +334,7 @@ static void pointerHandleAxis(void* data UNUSED,
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
y = wl_fixed_to_double(value) * -1;
|
||||
|
||||
_glfwInputScroll(window, x, y, 1);
|
||||
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
|
||||
4
glfw/x11_init.c
vendored
4
glfw/x11_init.c
vendored
@@ -502,8 +502,10 @@ static Window createHelperWindow(void)
|
||||
|
||||
// X error handler
|
||||
//
|
||||
static int errorHandler(Display *display UNUSED, XErrorEvent* event)
|
||||
static int errorHandler(Display *display, XErrorEvent* event)
|
||||
{
|
||||
if (_glfw.x11.display != display)
|
||||
return 0;
|
||||
_glfw.x11.errorCode = event->error_code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
43
glfw/x11_window.c
vendored
43
glfw/x11_window.c
vendored
@@ -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)
|
||||
@@ -1250,13 +1265,13 @@ static void processEvent(XEvent *event)
|
||||
|
||||
// Modern X provides scroll events as mouse button presses
|
||||
else if (event->xbutton.button == Button4)
|
||||
_glfwInputScroll(window, 0.0, 1.0, 0);
|
||||
_glfwInputScroll(window, 0.0, 1.0, 0, mods);
|
||||
else if (event->xbutton.button == Button5)
|
||||
_glfwInputScroll(window, 0.0, -1.0, 0);
|
||||
_glfwInputScroll(window, 0.0, -1.0, 0, mods);
|
||||
else if (event->xbutton.button == Button6)
|
||||
_glfwInputScroll(window, 1.0, 0.0, 0);
|
||||
_glfwInputScroll(window, 1.0, 0.0, 0, mods);
|
||||
else if (event->xbutton.button == Button7)
|
||||
_glfwInputScroll(window, -1.0, 0.0, 0);
|
||||
_glfwInputScroll(window, -1.0, 0.0, 0, mods);
|
||||
|
||||
else
|
||||
{
|
||||
@@ -1393,12 +1408,18 @@ static void processEvent(XEvent *event)
|
||||
if (!event->xany.send_event && window->x11.parent != _glfw.x11.root)
|
||||
{
|
||||
Window dummy;
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XTranslateCoordinates(_glfw.x11.display,
|
||||
window->x11.parent,
|
||||
_glfw.x11.root,
|
||||
xpos, ypos,
|
||||
&xpos, &ypos,
|
||||
&dummy);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to translate ConfigureNotiy co-ords for reparented window");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
|
||||
@@ -1474,7 +1495,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++)
|
||||
{
|
||||
@@ -1535,17 +1556,21 @@ static void processEvent(XEvent *event)
|
||||
const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff;
|
||||
const int yabs = (event->xclient.data.l[2]) & 0xffff;
|
||||
Window dummy;
|
||||
int xpos, ypos;
|
||||
int xpos = 0, ypos = 0;
|
||||
|
||||
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
|
||||
return;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XTranslateCoordinates(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
window->x11.handle,
|
||||
xabs, yabs,
|
||||
&xpos, &ypos,
|
||||
&dummy);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get DND event position");
|
||||
|
||||
_glfwInputCursorPos(window, xpos, ypos);
|
||||
|
||||
@@ -1973,10 +1998,14 @@ void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
|
||||
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
||||
{
|
||||
Window dummy;
|
||||
int x, y;
|
||||
int x = 0, y = 0;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XTranslateCoordinates(_glfw.x11.display, window->x11.handle, _glfw.x11.root,
|
||||
0, 0, &x, &y, &dummy);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get window position");
|
||||
|
||||
if (xpos)
|
||||
*xpos = x;
|
||||
|
||||
3
glfw/xkb_glfw.c
vendored
3
glfw/xkb_glfw.c
vendored
@@ -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]) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -48,6 +48,9 @@ try:
|
||||
except ImportError:
|
||||
has_highlighter = False
|
||||
|
||||
def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]:
|
||||
return ''
|
||||
|
||||
|
||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||
ESCAPE = K['ESCAPE']
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from kitty.key_encoding import (
|
||||
KeyEvent, backspace_key, enter_key, key_defs as K
|
||||
)
|
||||
from kitty.typing import BossType, KittyCommonOpts
|
||||
from kitty.utils import ScreenSize, screen_size_function
|
||||
from kitty.utils import ScreenSize, screen_size_function, set_primary_selection
|
||||
|
||||
from ..tui.handler import Handler, result_handler
|
||||
from ..tui.loop import Loop
|
||||
@@ -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))
|
||||
@@ -349,7 +349,7 @@ def parse_input(text: str) -> str:
|
||||
def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
|
||||
regex = args.regex
|
||||
if regex == DEFAULT_REGEX:
|
||||
regex = r'(?P<path>(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
|
||||
regex = r'(?P<path>(?:\S*/\S+?)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
|
||||
yield from mark(regex, [brackets, quotes], text, args)
|
||||
|
||||
|
||||
@@ -360,12 +360,8 @@ def load_custom_processor(customize_processing: str) -> Any:
|
||||
return {k: getattr(m, k) for k in dir(m)}
|
||||
if customize_processing == '::linenum::':
|
||||
return {'mark': linenum_marks, 'handle_result': linenum_handle_result}
|
||||
from kitty.constants import config_dir
|
||||
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing))
|
||||
if os.path.isabs(customize_processing):
|
||||
custom_path = customize_processing
|
||||
else:
|
||||
custom_path = os.path.join(config_dir, customize_processing)
|
||||
from kitty.constants import resolve_custom_file
|
||||
custom_path = resolve_custom_file(customize_processing)
|
||||
import runpy
|
||||
return runpy.run_path(custom_path, run_name='__main__')
|
||||
|
||||
@@ -413,9 +409,11 @@ OPTIONS = r'''
|
||||
type=list
|
||||
What program to use to open matched text. Defaults to the default open program
|
||||
for the operating system. Use a value of :file:`-` to paste the match into the
|
||||
terminal window instead. A value of :file:`@` will copy the match to the clipboard.
|
||||
A value of :file:`default` will run the default open program. Can be specified
|
||||
multiple times to run multiple programs.
|
||||
terminal window instead. A value of :file:`@` will copy the match to the
|
||||
clipboard. A value of :file:`*` will copy the match to the primary selection
|
||||
(on systems that support primary selections). A value of :file:`default` will
|
||||
run the default open program. Can be specified multiple times to run multiple
|
||||
programs.
|
||||
|
||||
|
||||
--type
|
||||
@@ -629,6 +627,8 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
|
||||
w.paste(joined_text())
|
||||
elif program == '@':
|
||||
set_clipboard_string(joined_text())
|
||||
elif program == '*':
|
||||
set_primary_selection(joined_text())
|
||||
else:
|
||||
cwd = None
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
|
||||
@@ -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
|
||||
|
||||
56991
kittens/unicode_input/names.h
generated
56991
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -336,19 +336,41 @@ class Boss:
|
||||
if not getattr(err, 'hide_traceback', False):
|
||||
response['tb'] = traceback.format_exc()
|
||||
else:
|
||||
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
|
||||
no_response = False
|
||||
try:
|
||||
no_response = json.loads(cmd).get('no_response')
|
||||
except Exception:
|
||||
pass
|
||||
if not no_response:
|
||||
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'
|
||||
if msg.startswith(cmd_prefix):
|
||||
cmd = msg[len(cmd_prefix):-2]
|
||||
cmd_prefix = b'\x1bP@kitty-cmd'
|
||||
terminator = b'\x1b\\'
|
||||
if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator):
|
||||
cmd = msg_bytes[len(cmd_prefix):-len(terminator)].decode('utf-8')
|
||||
response = self._handle_remote_command(cmd, from_peer=True)
|
||||
if response is None:
|
||||
return None
|
||||
return (cmd_prefix + json.dumps(response) + '\x1b\\').encode('utf-8')
|
||||
data = json.loads(msg)
|
||||
return cmd_prefix + json.dumps(response).encode('utf-8') + terminator
|
||||
|
||||
data = json.loads(msg_bytes.decode('utf-8'))
|
||||
if isinstance(data, dict) and data.get('cmd') == 'new_instance':
|
||||
from .cli_stub import CLIOptions
|
||||
startup_id = data.get('startup_id')
|
||||
@@ -534,7 +556,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:
|
||||
@@ -700,6 +724,9 @@ class Boss:
|
||||
text = '\n'.join(parse_uri_list(text))
|
||||
w.paste(text)
|
||||
|
||||
def confirm_os_window_close(self, os_window_id: int) -> None:
|
||||
mark_os_window_for_close(os_window_id)
|
||||
|
||||
def on_os_window_closed(self, os_window_id: int, viewport_width: int, viewport_height: int) -> None:
|
||||
self.cached_values['window-size'] = viewport_width, viewport_height
|
||||
tm = self.os_window_map.pop(os_window_id, None)
|
||||
@@ -755,7 +782,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 +793,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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -523,17 +523,6 @@ change_menubar_title(PyObject *title UNUSED) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool
|
||||
update_window_title(Window *w, OSWindow *os_window) {
|
||||
if (w->title && w->title != os_window->window_title) {
|
||||
os_window->window_title = w->title;
|
||||
Py_INCREF(os_window->window_title);
|
||||
set_os_window_title(os_window, PyUnicode_AsUTF8(w->title));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg) {
|
||||
#define TD os_window->tab_bar_render_data
|
||||
@@ -577,7 +566,7 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
|
||||
*active_window_id = w->id;
|
||||
collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window);
|
||||
if (w->cursor_visible_at_last_render != WD.screen->cursor_render_info.is_visible || w->last_cursor_x != WD.screen->cursor_render_info.x || w->last_cursor_y != WD.screen->cursor_render_info.y || w->last_cursor_shape != WD.screen->cursor_render_info.shape) needs_render = true;
|
||||
update_window_title(w, os_window);
|
||||
set_os_window_title_from_window(w, os_window);
|
||||
*active_window_bg = window_bg;
|
||||
} else WD.screen->cursor_render_info.is_visible = false;
|
||||
if (send_cell_data_to_gpu(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true;
|
||||
@@ -625,15 +614,6 @@ render_os_window(OSWindow *os_window, monotonic_t now, unsigned int active_windo
|
||||
#undef TD
|
||||
}
|
||||
|
||||
static inline void
|
||||
update_os_window_title(OSWindow *os_window) {
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
if (tab->num_windows) {
|
||||
Window *w = tab->windows + tab->active_window;
|
||||
update_window_title(w, os_window);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_resizing_text(OSWindow *w) {
|
||||
char text[32] = {0};
|
||||
@@ -649,7 +629,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;
|
||||
}
|
||||
|
||||
@@ -865,25 +845,42 @@ process_pending_resizes(monotonic_t now) {
|
||||
|
||||
static inline void
|
||||
close_all_windows(void) {
|
||||
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
|
||||
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], IMPERATIVE_CLOSE_REQUESTED);
|
||||
}
|
||||
|
||||
static inline void
|
||||
close_os_window(ChildMonitor *self, OSWindow *os_window) {
|
||||
destroy_os_window(os_window);
|
||||
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
|
||||
for (size_t t=0; t < os_window->num_tabs; t++) {
|
||||
Tab *tab = os_window->tabs + t;
|
||||
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
|
||||
}
|
||||
remove_os_window(os_window->id);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
process_pending_closes(ChildMonitor *self) {
|
||||
global_state.has_pending_closes = false;
|
||||
bool has_open_windows = false;
|
||||
for (size_t w = global_state.num_os_windows; w > 0; w--) {
|
||||
OSWindow *os_window = global_state.os_windows + w - 1;
|
||||
if (should_os_window_close(os_window)) {
|
||||
destroy_os_window(os_window);
|
||||
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
|
||||
for (size_t t=0; t < os_window->num_tabs; t++) {
|
||||
Tab *tab = os_window->tabs + t;
|
||||
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
|
||||
}
|
||||
remove_os_window(os_window->id);
|
||||
} else has_open_windows = true;
|
||||
switch(os_window->close_request) {
|
||||
case NO_CLOSE_REQUESTED:
|
||||
has_open_windows = true;
|
||||
break;
|
||||
case CONFIRMABLE_CLOSE_REQUESTED:
|
||||
os_window->close_request = NO_CLOSE_REQUESTED;
|
||||
call_boss(confirm_os_window_close, "K", os_window->id);
|
||||
if (os_window->close_request == IMPERATIVE_CLOSE_REQUESTED) {
|
||||
close_os_window(self, os_window);
|
||||
} else has_open_windows = true;
|
||||
break;
|
||||
case IMPERATIVE_CLOSE_REQUESTED:
|
||||
close_os_window(self, os_window);
|
||||
break;
|
||||
}
|
||||
}
|
||||
global_state.has_pending_closes = false;
|
||||
#ifdef __APPLE__
|
||||
if (!OPT(macos_quit_when_last_window_closed)) {
|
||||
if (!has_open_windows && !application_quit_requested()) has_open_windows = true;
|
||||
@@ -1279,7 +1276,7 @@ typedef struct {
|
||||
char *data;
|
||||
size_t capacity, used;
|
||||
int fd;
|
||||
bool finished, close_socket;
|
||||
bool finished, close_socket, is_peer_command;
|
||||
} PeerReadData;
|
||||
static PeerReadData empty_prd = {.fd = -1, 0};
|
||||
|
||||
@@ -1329,6 +1326,41 @@ accept_peer(int listen_fd, bool shutting_down) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
|
||||
|
||||
static inline void
|
||||
queue_peer_message(ChildMonitor *self, char *buf, size_t sz, int fd) {
|
||||
children_mutex(lock);
|
||||
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
|
||||
Message *m = self->messages + self->messages_count++;
|
||||
m->data = buf; m->sz = sz; m->fd = fd;
|
||||
children_mutex(unlock);
|
||||
wakeup_main_loop();
|
||||
}
|
||||
|
||||
static inline void
|
||||
dispatch_peer_command(ChildMonitor *self, PeerReadData *rd, int fd) {
|
||||
size_t end = 0;
|
||||
for (size_t i = 0; i < rd->used - 1; i++) {
|
||||
if (rd->data[i] == 0x1b && rd->data[i+1] == '\\') {
|
||||
end = i + 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!end) return;
|
||||
char *buf = malloc(end + 8);
|
||||
if (buf) {
|
||||
memcpy(buf, rd->data, end);
|
||||
queue_peer_message(self, buf, end, fd);
|
||||
}
|
||||
rd->is_peer_command = false;
|
||||
if (rd->used > end) {
|
||||
rd->used -= end;
|
||||
memmove(rd->data, rd->data + end, rd->used);
|
||||
if (rd->used >= sizeof(KITTY_CMD_PREFIX) - 1 && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
|
||||
} else rd->used = 0;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
read_from_peer(ChildMonitor *self, int s) {
|
||||
bool read_finished = false;
|
||||
@@ -1337,26 +1369,28 @@ read_from_peer(ChildMonitor *self, int s) {
|
||||
#define failed(msg) { read_finished = true; log_error("%s", msg); rd->finished = true; rd->close_socket = true; break; }
|
||||
if (rd->fd == s) {
|
||||
if (rd->used >= rd->capacity) {
|
||||
if (rd->capacity >= 1024 * 1024) failed("Ignoring too large message from peer");
|
||||
if (rd->capacity >= 64 * 1024) failed("Ignoring too large message from peer");
|
||||
rd->capacity = MAX(8192u, rd->capacity * 2);
|
||||
rd->data = realloc(rd->data, rd->capacity);
|
||||
if (!rd->data) failed("Out of memory");
|
||||
}
|
||||
ssize_t n = recv(s, rd->data + rd->used, rd->capacity - rd->used, 0);
|
||||
if (n == 0) {
|
||||
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
|
||||
read_finished = true; rd->finished = true;
|
||||
children_mutex(lock);
|
||||
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
|
||||
Message *m = self->messages + self->messages_count++;
|
||||
m->data = rd->data; rd->data = NULL; m->sz = rd->used; m->fd = s;
|
||||
children_mutex(unlock);
|
||||
wakeup_main_loop();
|
||||
if (rd->used) queue_peer_message(self, rd->data, rd->used, s);
|
||||
else free(rd->data);
|
||||
rd->data = NULL;
|
||||
} else if (n < 0) {
|
||||
if (errno != EINTR) {
|
||||
perror("Error reading from talk peer");
|
||||
failed("");
|
||||
}
|
||||
} else rd->used += n;
|
||||
} else {
|
||||
if (!rd->used && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
|
||||
rd->used += n;
|
||||
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
27
kitty/cli.py
27
kitty/cli.py
@@ -96,7 +96,9 @@ def opt(text: str) -> str:
|
||||
|
||||
|
||||
def option(x: str) -> str:
|
||||
idx = x.find('-')
|
||||
idx = x.rfind('--')
|
||||
if idx < 0:
|
||||
idx = x.find('-')
|
||||
if idx > -1:
|
||||
x = x[idx:]
|
||||
parts = map(bold, x.split())
|
||||
@@ -119,6 +121,10 @@ def file(x: str) -> str:
|
||||
return italic(x)
|
||||
|
||||
|
||||
def doc(x: str) -> str:
|
||||
return f'https://sw.kovidgoyal.net/kitty/{x}.html'
|
||||
|
||||
|
||||
OptionSpecSeq = List[Union[str, OptionDict]]
|
||||
|
||||
|
||||
@@ -403,7 +409,10 @@ def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, e
|
||||
elif otype == 'list':
|
||||
t = 'typing.Sequence[str]'
|
||||
elif otype in ('choice', 'choices'):
|
||||
t = 'str'
|
||||
if opt['choices']:
|
||||
t = 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in opt['choices']))
|
||||
else:
|
||||
t = 'str'
|
||||
elif otype.startswith('bool-'):
|
||||
t = 'bool'
|
||||
else:
|
||||
@@ -618,12 +627,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
|
||||
@@ -656,10 +668,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,10 +6,10 @@ import re
|
||||
from functools import partial
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterable, List, Match, Optional, Sequence,
|
||||
Set, Tuple, Union, cast, get_type_hints
|
||||
Set, Tuple, Union, get_type_hints
|
||||
)
|
||||
|
||||
from .utils import to_bool
|
||||
from .utils import Choice, to_bool
|
||||
|
||||
|
||||
def to_string(x: str) -> str:
|
||||
@@ -56,6 +56,8 @@ class Option:
|
||||
|
||||
if type(self.option_type) is type:
|
||||
return type_name(self.option_type)
|
||||
if isinstance(self.option_type, Choice):
|
||||
return 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in self.option_type.all_choices))
|
||||
th = get_type_hints(self.option_type)
|
||||
try:
|
||||
rettype = th['return']
|
||||
@@ -156,7 +158,7 @@ def remove_markup(text: str) -> str:
|
||||
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
|
||||
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
|
||||
}[m.group(2)]
|
||||
return cast(str, m.group(2))
|
||||
return str(m.group(2))
|
||||
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||
|
||||
|
||||
@@ -68,17 +68,21 @@ def python_string(text: str) -> str:
|
||||
return ans
|
||||
|
||||
|
||||
def choices(*choices: str) -> Callable[[str], str]:
|
||||
defval = choices[0]
|
||||
uc = frozenset(choices)
|
||||
class Choice:
|
||||
|
||||
def choice(x: str) -> str:
|
||||
def __init__(self, choices: Sequence[str]):
|
||||
self.defval = choices[0]
|
||||
self.all_choices = frozenset(choices)
|
||||
|
||||
def __call__(self, x: str) -> str:
|
||||
x = x.lower()
|
||||
if x not in uc:
|
||||
x = defval
|
||||
if x not in self.all_choices:
|
||||
x = self.defval
|
||||
return x
|
||||
|
||||
return choice
|
||||
|
||||
def choices(*choices: str) -> Choice:
|
||||
return Choice(choices)
|
||||
|
||||
|
||||
def parse_line(
|
||||
|
||||
@@ -9,8 +9,8 @@ import sys
|
||||
from contextlib import contextmanager, suppress
|
||||
from functools import partial
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterable, List, Match, NamedTuple,
|
||||
Optional, Sequence, Set, Tuple, Type, Union
|
||||
Any, Callable, Dict, Generator, Iterable, List, NamedTuple, Optional,
|
||||
Sequence, Set, Tuple, Type, Union
|
||||
)
|
||||
|
||||
from . import fast_data_types as defines
|
||||
@@ -23,8 +23,8 @@ 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 .utils import log_error
|
||||
from .typing import EdgeLiteral, TypedDict
|
||||
from .utils import expandvars, log_error
|
||||
|
||||
KeySpec = Tuple[int, bool, int]
|
||||
KeyMap = Dict[KeySpec, 'KeyAction']
|
||||
@@ -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,20 +585,6 @@ 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 sub(m: Match) -> str:
|
||||
key = m.group(1)
|
||||
result = env.get(key)
|
||||
if result is None:
|
||||
result = os.environ.get(key)
|
||||
if result is None:
|
||||
result = m.group()
|
||||
return result
|
||||
|
||||
return re.sub(r'\$\{(\S+?)\}', sub, val)
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_env(key: str, val: str, ans: Dict[str, Any]) -> None:
|
||||
key, val = val.partition('=')[::2]
|
||||
@@ -730,12 +725,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)
|
||||
|
||||
@@ -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
|
||||
@@ -548,7 +548,7 @@ rectangular block with the mouse)'''))
|
||||
o('terminal_select_modifiers', 'shift', option_type=to_modifiers, long_text=_('''
|
||||
The modifiers to override mouse selection even when a terminal application has grabbed the mouse'''))
|
||||
|
||||
o('select_by_word_characters', ':@-./_~?&=%+#', long_text=_('''
|
||||
o('select_by_word_characters', '@-./_~?&=%+#', long_text=_('''
|
||||
Characters considered part of a word when double clicking. In addition to these characters
|
||||
any character that is marked as an alphanumeric character in the unicode
|
||||
database will be matched.'''))
|
||||
@@ -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
|
||||
@@ -843,6 +870,11 @@ A template to render the tab title. The default just renders
|
||||
the title. If you wish to include the tab-index as well,
|
||||
use something like: :code:`{index}: {title}`. Useful
|
||||
if you have shortcuts mapped for :code:`goto_tab N`.
|
||||
In addition you can use :code:`{layout_name}` for the current
|
||||
layout name and :code:`{num_windows}` for the number of windows
|
||||
in the tab. Note that formatting is done by Python's string formatting
|
||||
machinery, so you can use, for instance, :code:`{layout_name[:2].upper()}` to
|
||||
show only the first two letters of the layout name, upper-cased.
|
||||
'''))
|
||||
o('active_tab_title_template', 'none', option_type=active_tab_title_template, long_text=_('''
|
||||
Template to use for active tabs, if not specified falls back
|
||||
@@ -1012,6 +1044,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,
|
||||
|
||||
@@ -20,13 +20,27 @@ class Version(NamedTuple):
|
||||
|
||||
|
||||
appname: str = 'kitty'
|
||||
version: Version = Version(0, 17, 1)
|
||||
version: Version = Version(0, 17, 4)
|
||||
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)
|
||||
@@ -198,3 +213,10 @@ def running_in_kitty(set_val: Optional[bool] = None) -> bool:
|
||||
if set_val is not None:
|
||||
setattr(running_in_kitty, 'ans', set_val)
|
||||
return bool(getattr(running_in_kitty, 'ans', False))
|
||||
|
||||
|
||||
def resolve_custom_file(path: str) -> str:
|
||||
path = os.path.expandvars(os.path.expanduser(path))
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(config_dir, path)
|
||||
return path
|
||||
|
||||
@@ -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,12 @@ 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, PUA glyphs, ligatures, etc)
|
||||
CGFloat delta = (((CGFloat)canvas_width - br.size.width) / 2.f);
|
||||
// FiraCode ligatures result in negative origins
|
||||
if (br.origin.x > 0) delta -= br.origin.x;
|
||||
if (delta >= 1.f) right_shift_canvas(canvas, canvas_width, cell_height, (unsigned)(delta));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ void set_mouse_cursor(MouseShape);
|
||||
void enter_event(void);
|
||||
void mouse_event(int, int, int);
|
||||
void focus_in_event(void);
|
||||
void scroll_event(double, double, int);
|
||||
void scroll_event(double, double, int, int);
|
||||
void fake_scroll(int, bool);
|
||||
void set_special_key_combo(int glfw_key, int mods, bool is_native);
|
||||
void on_key_input(GLFWkeyevent *ev);
|
||||
|
||||
@@ -30,18 +30,22 @@ static void* libsn_handle = NULL;
|
||||
static PyObject*
|
||||
init_x11_startup_notification(PyObject UNUSED *self, PyObject *args) {
|
||||
static bool done = false;
|
||||
static const char* libname = "libstartup-notification-1.so";
|
||||
// some installs are missing the .so symlink, so try the full name
|
||||
static const char* libname2 = "libstartup-notification-1.so.0";
|
||||
static const char* libname3 = "libstartup-notification-1.so.0.0.0";
|
||||
if (!done) {
|
||||
done = true;
|
||||
|
||||
libsn_handle = dlopen(libname, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) libsn_handle = dlopen(libname2, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) libsn_handle = dlopen(libname3, RTLD_LAZY);
|
||||
const char* libnames[] = {
|
||||
"libstartup-notification-1.so",
|
||||
// some installs are missing the .so symlink, so try the full name
|
||||
"libstartup-notification-1.so.0",
|
||||
"libstartup-notification-1.so.0.0.0",
|
||||
NULL
|
||||
};
|
||||
for (int i = 0; libnames[i]; i++) {
|
||||
libsn_handle = dlopen(libnames[i], RTLD_LAZY);
|
||||
if (libsn_handle) break;
|
||||
}
|
||||
if (libsn_handle == NULL) {
|
||||
PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libname, dlerror());
|
||||
PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libnames[0], dlerror());
|
||||
return NULL;
|
||||
}
|
||||
dlerror(); /* Clear any existing error */
|
||||
@@ -105,18 +109,22 @@ load_libcanberra_functions(void) {
|
||||
|
||||
static void
|
||||
load_libcanberra(void) {
|
||||
static const char* libname = "libcanberra.so";
|
||||
// some installs are missing the .so symlink, so try the full name
|
||||
static const char* libname2 = "libcanberra.so.0";
|
||||
static const char* libname3 = "libcanberra.so.0.2.5";
|
||||
static bool done = false;
|
||||
if (done) return;
|
||||
done = true;
|
||||
libcanberra_handle = dlopen(libname, RTLD_LAZY);
|
||||
if (libcanberra_handle == NULL) libcanberra_handle = dlopen(libname2, RTLD_LAZY);
|
||||
if (libcanberra_handle == NULL) libcanberra_handle = dlopen(libname3, RTLD_LAZY);
|
||||
const char* libnames[] = {
|
||||
"libcanberra.so",
|
||||
// some installs are missing the .so symlink, so try the full name
|
||||
"libcanberra.so.0",
|
||||
"libcanberra.so.0.2.5",
|
||||
NULL
|
||||
};
|
||||
for (int i = 0; libnames[i]; i++) {
|
||||
libcanberra_handle = dlopen(libnames[i], RTLD_LAZY);
|
||||
if (libcanberra_handle) break;
|
||||
}
|
||||
if (libcanberra_handle == NULL) {
|
||||
fprintf(stderr, "Failed to load %s, cannot play beep sound, with error: %s\n", libname, dlerror());
|
||||
fprintf(stderr, "Failed to load %s, cannot play beep sound, with error: %s\n", libnames[0], dlerror());
|
||||
return;
|
||||
}
|
||||
load_libcanberra_functions();
|
||||
|
||||
259
kitty/emoji.h
generated
259
kitty/emoji.h
generated
@@ -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;
|
||||
}
|
||||
|
||||
@@ -323,9 +323,14 @@ FC_MONO: int = 100
|
||||
FC_DUAL: int
|
||||
FC_WEIGHT_REGULAR: int
|
||||
FC_WEIGHT_BOLD: int
|
||||
FC_WIDTH_NORMAL: int
|
||||
FC_SLANT_ROMAN: int
|
||||
FC_SLANT_ITALIC: int
|
||||
BORDERS_PROGRAM: int
|
||||
PRESS: int
|
||||
RELEASE: int
|
||||
DRAG: int
|
||||
MOVE: int
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -402,6 +407,7 @@ class FontConfigPattern(TypedDict):
|
||||
style: str
|
||||
spacing: str
|
||||
weight: int
|
||||
width: int
|
||||
slant: int
|
||||
hint_style: int
|
||||
subpixel: int
|
||||
@@ -544,10 +550,14 @@ def update_window_visibility(
|
||||
pass
|
||||
|
||||
|
||||
def sync_os_window_title(os_window_id: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def set_options(
|
||||
opts: Options,
|
||||
is_wayland: bool = False,
|
||||
debug_gl: bool = False,
|
||||
debug_rendering: bool = False,
|
||||
debug_font_fallback: bool = False
|
||||
) -> None:
|
||||
pass
|
||||
@@ -900,7 +910,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,
|
||||
@@ -1126,3 +1136,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
|
||||
|
||||
@@ -47,6 +47,7 @@ pattern_as_dict(FcPattern *pat) {
|
||||
S(FC_FULLNAME, full_name);
|
||||
S(FC_POSTSCRIPT_NAME, postscript_name);
|
||||
I(FC_WEIGHT, weight);
|
||||
I(FC_WIDTH, width)
|
||||
I(FC_SLANT, slant);
|
||||
I(FC_HINT_STYLE, hint_style);
|
||||
I(FC_INDEX, index);
|
||||
@@ -247,5 +248,7 @@ init_fontconfig_library(PyObject *module) {
|
||||
PyModule_AddIntMacro(module, FC_DUAL);
|
||||
PyModule_AddIntMacro(module, FC_MONO);
|
||||
PyModule_AddIntMacro(module, FC_CHARCELL);
|
||||
PyModule_AddIntMacro(module, FC_WIDTH_NORMAL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -1004,14 +1004,20 @@ merge_groups_for_pua_space_ligature(void) {
|
||||
G(groups)->is_space_ligature = true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_group_calt_ligature(const Group *group) {
|
||||
GPUCell *first_cell = G(first_gpu_cell) + group->first_cell_idx;
|
||||
return group->num_cells > 1 && group->has_special_glyph && (first_cell->attrs & WIDTH_MASK) == 1;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right) {
|
||||
*left = 0; *right = 0;
|
||||
for (unsigned idx = 0; idx < G(group_idx) + 1; idx++) {
|
||||
Group *group = G(groups) + idx;
|
||||
if (group->first_cell_idx <= cursor_offset && cursor_offset < group->first_cell_idx + group->num_cells) {
|
||||
GPUCell *first_cell = G(first_gpu_cell) + group->first_cell_idx;
|
||||
if (group->num_cells > 1 && group->has_special_glyph && (first_cell->attrs & WIDTH_MASK) == 1) {
|
||||
if (is_group_calt_ligature(group)) {
|
||||
// likely a calt ligature
|
||||
*left = group->first_cell_idx; *right = group->first_cell_idx + group->num_cells;
|
||||
}
|
||||
@@ -1039,6 +1045,11 @@ render_groups(FontGroup *fg, Font *font, bool center_glyph) {
|
||||
// there exist stupid fonts like Powerline that have no space glyph,
|
||||
// so special case it: https://github.com/kovidgoyal/kitty/issues/1225
|
||||
unsigned int num_glyphs = group->is_space_ligature ? 1 : group->num_glyphs;
|
||||
#ifdef __APPLE__
|
||||
// FiraCode ligatures need to be centered on macOS
|
||||
// see https://github.com/kovidgoyal/kitty/issues/2591
|
||||
if (is_group_calt_ligature(group)) center_glyph = true;
|
||||
#endif
|
||||
render_group(fg, group->num_cells, num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed, center_glyph);
|
||||
idx++;
|
||||
}
|
||||
@@ -1243,7 +1254,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Dict, Generator, List, Optional, Tuple, cast
|
||||
|
||||
from kitty.fast_data_types import (
|
||||
FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD,
|
||||
FC_WEIGHT_REGULAR, fc_list, fc_match as fc_match_impl
|
||||
FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, fc_list, fc_match as fc_match_impl
|
||||
)
|
||||
from kitty.options_stub import Options
|
||||
from kitty.typing import FontConfigPattern
|
||||
@@ -73,11 +73,13 @@ def find_best_match(family: str, bold: bool = False, italic: bool = False, monos
|
||||
q = family_name_to_key(family)
|
||||
font_map = all_fonts_map(monospaced)
|
||||
|
||||
def score(candidate: FontConfigPattern) -> Tuple[int, int]:
|
||||
def score(candidate: FontConfigPattern) -> Tuple[int, int, int]:
|
||||
bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0))
|
||||
italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0))
|
||||
monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1
|
||||
return bold_score + italic_score, monospace_match
|
||||
width_score = abs(candidate.get('width', FC_WIDTH_NORMAL) - FC_WIDTH_NORMAL)
|
||||
|
||||
return bold_score + italic_score, monospace_match, width_score
|
||||
|
||||
# First look for an exact match
|
||||
for selector in ('ps_map', 'full_map', 'family_map'):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
3
kitty/glfw-wrapper.h
generated
3
kitty/glfw-wrapper.h
generated
@@ -1313,6 +1313,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
|
||||
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
|
||||
* scrolling cancelled and value 6 if scrolling may begin soon.
|
||||
* @param[int] mods The keyboard modifiers
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
@@ -1322,7 +1323,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int);
|
||||
|
||||
/*! @brief The function pointer type for key callbacks.
|
||||
*
|
||||
|
||||
41
kitty/glfw.c
41
kitty/glfw.c
@@ -153,6 +153,10 @@ static void
|
||||
window_close_callback(GLFWwindow* window) {
|
||||
if (!set_callback_window(window)) return;
|
||||
global_state.has_pending_closes = true;
|
||||
if (global_state.callback_os_window->close_request < CONFIRMABLE_CLOSE_REQUESTED) {
|
||||
global_state.callback_os_window->close_request = CONFIRMABLE_CLOSE_REQUESTED;
|
||||
}
|
||||
glfwSetWindowShouldClose(window, false);
|
||||
request_tick_callback();
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
@@ -287,12 +291,12 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
|
||||
}
|
||||
|
||||
static void
|
||||
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags) {
|
||||
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags, int mods) {
|
||||
if (!set_callback_window(w)) return;
|
||||
show_mouse_cursor(w);
|
||||
monotonic_t now = monotonic();
|
||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
||||
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags);
|
||||
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags, mods);
|
||||
request_tick_callback();
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
@@ -323,23 +327,17 @@ window_focus_callback(GLFWwindow *w, int focused) {
|
||||
static int
|
||||
drop_callback(GLFWwindow *w, const char *mime, const char *data, size_t sz) {
|
||||
if (!set_callback_window(w)) return 0;
|
||||
#define RETURN(x) { global_state.callback_os_window = NULL; return x; }
|
||||
if (!data) {
|
||||
if (strcmp(mime, "text/uri-list") == 0) return 3;
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0) return 2;
|
||||
if (strcmp(mime, "text/plain") == 0) return 1;
|
||||
return 0;
|
||||
if (strcmp(mime, "text/uri-list") == 0) RETURN(3);
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0) RETURN(2);
|
||||
if (strcmp(mime, "text/plain") == 0) RETURN(1);
|
||||
RETURN(0);
|
||||
}
|
||||
WINDOW_CALLBACK(on_drop, "sy#", mime, data, (int)sz);
|
||||
WINDOW_CALLBACK(on_drop, "sy#", mime, data, (Py_ssize_t)sz);
|
||||
request_tick_callback();
|
||||
/* PyObject *s = PyTuple_New(count); */
|
||||
/* if (s) { */
|
||||
/* for (int i = 0; i < count; i++) PyTuple_SET_ITEM(s, i, PyUnicode_FromString(strings[i])); */
|
||||
/* WINDOW_CALLBACK(on_drop, "O", s); */
|
||||
/* Py_CLEAR(s); */
|
||||
/* request_tick_callback(); */
|
||||
/* } */
|
||||
global_state.callback_os_window = NULL;
|
||||
return 0;
|
||||
RETURN(0);
|
||||
#undef RETURN
|
||||
}
|
||||
|
||||
// }}}
|
||||
@@ -979,12 +977,6 @@ wakeup_main_loop() {
|
||||
glfwPostEmptyEvent();
|
||||
}
|
||||
|
||||
void
|
||||
mark_os_window_for_close(OSWindow* w, bool yes) {
|
||||
global_state.has_pending_closes = true;
|
||||
glfwSetWindowShouldClose(w->handle, yes);
|
||||
}
|
||||
|
||||
bool
|
||||
should_os_window_be_rendered(OSWindow* w) {
|
||||
return (
|
||||
@@ -994,11 +986,6 @@ should_os_window_be_rendered(OSWindow* w) {
|
||||
) ? false : true;
|
||||
}
|
||||
|
||||
bool
|
||||
should_os_window_close(OSWindow* w) {
|
||||
return glfwWindowShouldClose(w->handle) ? true : false;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
primary_monitor_size(PYNOARG) {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
|
||||
@@ -939,7 +939,7 @@ static PyMethodDef methods[] = {
|
||||
};
|
||||
|
||||
static PyMemberDef members[] = {
|
||||
{"image_count", T_UINT, offsetof(GraphicsManager, image_count), 0, "image_count"},
|
||||
{"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
|
||||
{NULL},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
from functools import lru_cache
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
from .boss import Boss
|
||||
from .child import Child
|
||||
from .cli import parse_args
|
||||
from .cli_stub import LaunchCLIOptions
|
||||
from .constants import resolve_custom_file
|
||||
from .fast_data_types import set_clipboard_string
|
||||
from .utils import set_primary_selection
|
||||
from .window import Window
|
||||
from .boss import Boss
|
||||
from .tabs import Tab
|
||||
from .utils import set_primary_selection
|
||||
from .window import Watchers, Window
|
||||
|
||||
try:
|
||||
from typing import TypedDict
|
||||
@@ -41,7 +42,7 @@ choices=window,tab,os-window,overlay,background,clipboard,primary
|
||||
Where to launch the child process, in a new kitty window in the current tab,
|
||||
a new tab, or a new OS window or an overlay over the current window.
|
||||
Note that if the current window already has an overlay, then it will
|
||||
open a new window. The value of none means the process will be
|
||||
open a new window. The value of background means the process will be
|
||||
run in the background. The values clipboard and primary are meant
|
||||
to work with :option:`launch --stdin-source` to copy data to the system
|
||||
clipboard or primary selection.
|
||||
@@ -148,6 +149,14 @@ defaults to :code:`kitty`.
|
||||
--os-window-name
|
||||
Set the WM_NAME property on X11 for the newly created OS Window when using
|
||||
:option:`launch --type`=os-window. Defaults to :option:`launch --os-window-class`.
|
||||
|
||||
|
||||
--watcher -w
|
||||
type=list
|
||||
Path to a python file. Appropriately named functions in this file will be called
|
||||
for various events, such as when the window is resized or closed. See the section
|
||||
on watchers in the launch command documentation :doc:`launch`. Relative paths are
|
||||
resolved relative to the kitty config directory.
|
||||
'''
|
||||
|
||||
|
||||
@@ -176,7 +185,7 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab]
|
||||
tm = boss.active_tab_manager
|
||||
if tm:
|
||||
tab: Optional[Tab] = tm.new_tab(empty_tab=True, location=opts.location)
|
||||
if opts.tab_title and tab:
|
||||
if opts.tab_title and tab is not None:
|
||||
tab.set_title(opts.tab_title)
|
||||
else:
|
||||
tab = None
|
||||
@@ -184,7 +193,7 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab]
|
||||
oswid = boss.add_os_window(wclass=opts.os_window_class, wname=opts.os_window_name)
|
||||
tm = boss.os_window_map[oswid]
|
||||
tab = tm.new_tab(empty_tab=True)
|
||||
if opts.tab_title:
|
||||
if opts.tab_title and tab is not None:
|
||||
tab.set_title(opts.tab_title)
|
||||
else:
|
||||
tab = target_tab or boss.active_tab
|
||||
@@ -192,6 +201,23 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab]
|
||||
return tab
|
||||
|
||||
|
||||
def load_watch_modules(opts: LaunchCLIOptions) -> Optional[Watchers]:
|
||||
if not opts.watcher:
|
||||
return None
|
||||
import runpy
|
||||
ans = Watchers()
|
||||
for path in opts.watcher:
|
||||
path = resolve_custom_file(path)
|
||||
m = runpy.run_path(path, run_name='__kitty_watcher__')
|
||||
w = m.get('on_close')
|
||||
if callable(w):
|
||||
ans.on_close.append(w)
|
||||
w = m.get('on_resize')
|
||||
if callable(w):
|
||||
ans.on_resize.append(w)
|
||||
return ans
|
||||
|
||||
|
||||
class LaunchKwds(TypedDict):
|
||||
|
||||
allow_remote_control: bool
|
||||
@@ -249,8 +275,11 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
|
||||
kw['cmd'] = final_cmd
|
||||
if opts.type == 'overlay' and active and not active.overlay_window_id:
|
||||
kw['overlay_for'] = active.id
|
||||
if opts.stdin_source and opts.stdin_source != 'none':
|
||||
q = opts.stdin_source
|
||||
if opts.stdin_source != 'none':
|
||||
q = str(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)
|
||||
@@ -274,7 +303,8 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
|
||||
else:
|
||||
tab = tab_for_window(boss, opts, target_tab)
|
||||
if tab is not None:
|
||||
new_window: Window = tab.new_window(env=env or None, **kw)
|
||||
watchers = load_watch_modules(opts)
|
||||
new_window: Window = tab.new_window(env=env or None, watchers=watchers or None, **kw)
|
||||
if opts.keep_focus and active:
|
||||
boss.set_active_window(active, switch_os_window_if_needed=True)
|
||||
return new_window
|
||||
|
||||
702
kitty/layout.py
702
kitty/layout.py
File diff suppressed because it is too large
Load Diff
46
kitty/line.c
46
kitty/line.c
@@ -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
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
import shutil
|
||||
import sys
|
||||
from contextlib import contextmanager, suppress
|
||||
from typing import Generator, List, Mapping, Optional, Tuple, Sequence
|
||||
from typing import Generator, List, Mapping, Optional, Sequence, Tuple
|
||||
|
||||
from .borders import load_borders_program
|
||||
from .boss import Boss
|
||||
@@ -29,7 +29,7 @@ from .fonts.box_drawing import set_scale
|
||||
from .fonts.render import set_font_family
|
||||
from .options_stub import Options as OptionsStub
|
||||
from .utils import (
|
||||
detach, log_error, read_shell_environment, single_instance,
|
||||
detach, expandvars, log_error, read_shell_environment, single_instance,
|
||||
startup_notification_handler, unix_socket_paths
|
||||
)
|
||||
from .window import load_shader_programs
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,8 +513,9 @@ 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) {
|
||||
*window_idx = i; return t->windows + i;
|
||||
if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) {
|
||||
*window_idx = i;
|
||||
return t->windows + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -527,8 +530,10 @@ 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);
|
||||
if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
|
||||
if (w->visible) {
|
||||
double d = distance_to_window(w);
|
||||
if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
@@ -608,7 +613,7 @@ mouse_event(int button, int modifiers, int action) {
|
||||
}
|
||||
|
||||
void
|
||||
scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
scroll_event(double UNUSED xoffset, double yoffset, int flags, int modifiers) {
|
||||
bool in_tab_bar;
|
||||
static id_type window_for_momentum_scroll = 0;
|
||||
static bool main_screen_for_momentum_scroll = false;
|
||||
@@ -685,7 +690,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
} else {
|
||||
if (screen->modes.mouse_tracking_mode) {
|
||||
int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0);
|
||||
int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, modifiers);
|
||||
if (sz > 0) {
|
||||
mouse_event_buf[sz] = 0;
|
||||
for (s = abs(s); s > 0; s--) {
|
||||
@@ -698,6 +703,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 +756,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 +764,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;
|
||||
}
|
||||
|
||||
28
kitty/parse-graphics-command.h
generated
28
kitty/parse-graphics-command.h
generated
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from kitty.cli_stub import LaunchCLIOptions
|
||||
from kitty.launch import (
|
||||
launch as do_launch, options_spec as launch_options_spec,
|
||||
parse_launch_args
|
||||
@@ -16,7 +17,7 @@ from .base import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import LaunchRCOptions as CLIOptions, LaunchCLIOptions
|
||||
from kitty.cli_stub import LaunchRCOptions as CLIOptions
|
||||
|
||||
|
||||
class Launch(RemoteCommand):
|
||||
|
||||
@@ -57,6 +57,7 @@ Restore all colors to the values they had at kitty startup. Note that if you spe
|
||||
this option, any color arguments are ignored and --configured and --all are implied.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
argspec = 'COLOR_OR_FILE ...'
|
||||
args_completion = {'files': ('CONF files', ('*.conf',))}
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
final_colors: Dict[str, int] = {}
|
||||
|
||||
124
kitty/rc/set_spacing.py
Normal file
124
kitty/rc/set_spacing.py
Normal 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()
|
||||
115
kitty/screen.c
115
kitty/screen.c
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -465,7 +465,11 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWind
|
||||
bind_program(CELL_BG_PROGRAM);
|
||||
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
} else if (OPT(background_tint) > 0) draw_tint(false, screen, xstart, ystart, width, height);
|
||||
} else if (OPT(background_tint) > 0) {
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
|
||||
draw_tint(false, screen, xstart, ystart, width, height);
|
||||
BLEND_ONTO_OPAQUE;
|
||||
}
|
||||
|
||||
if (screen->grman->num_of_below_refs || has_bgimage(w)) {
|
||||
if (screen->grman->num_of_below_refs) draw_graphics(
|
||||
|
||||
@@ -27,13 +27,26 @@ 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++) { \
|
||||
if (tab->windows[w].id == window_id) { \
|
||||
Window *window = tab->windows + w;
|
||||
#define END_WITH_WINDOW window_found = 1; break; }}}}}}
|
||||
|
||||
|
||||
#define WITH_OS_WINDOW_REFS \
|
||||
id_type cb_window_id = 0, focused_window_id = 0; \
|
||||
@@ -201,6 +214,27 @@ update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyO
|
||||
END_WITH_TAB;
|
||||
}
|
||||
|
||||
void
|
||||
set_os_window_title_from_window(Window *w, OSWindow *os_window) {
|
||||
if (w->title && w->title != os_window->window_title) {
|
||||
Py_XDECREF(os_window->window_title);
|
||||
os_window->window_title = w->title;
|
||||
Py_INCREF(os_window->window_title);
|
||||
set_os_window_title(os_window, PyUnicode_AsUTF8(w->title));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
update_os_window_title(OSWindow *os_window) {
|
||||
if (os_window->num_tabs) {
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
if (tab->num_windows) {
|
||||
Window *w = tab->windows + tab->active_window;
|
||||
set_os_window_title_from_window(w, os_window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
destroy_window(Window *w) {
|
||||
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
|
||||
@@ -410,6 +444,14 @@ os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mark_os_window_for_close(OSWindow* w, CloseRequest cr) {
|
||||
global_state.has_pending_closes = true;
|
||||
w->close_request = cr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Python API {{{
|
||||
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
|
||||
@@ -577,14 +619,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 +645,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);
|
||||
@@ -772,10 +813,10 @@ PYWRAP1(os_window_has_background_image) {
|
||||
|
||||
PYWRAP1(mark_os_window_for_close) {
|
||||
id_type os_window_id;
|
||||
int yes = 1;
|
||||
PA("K|p", &os_window_id, &yes);
|
||||
CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED;
|
||||
PA("K|i", &os_window_id, &cr);
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
mark_os_window_for_close(os_window, yes ? true : false);
|
||||
mark_os_window_for_close(os_window, cr);
|
||||
Py_RETURN_TRUE;
|
||||
END_WITH_OS_WINDOW
|
||||
Py_RETURN_FALSE;
|
||||
@@ -839,6 +880,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)
|
||||
@@ -878,6 +929,17 @@ PYWRAP1(update_window_visibility) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PYWRAP1(sync_os_window_title) {
|
||||
id_type os_window_id;
|
||||
PA("K", &os_window_id);
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
update_os_window_title(os_window);
|
||||
END_WITH_OS_WINDOW
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static inline double
|
||||
dpi_for_os_window_id(id_type os_window_id) {
|
||||
double dpi = 0;
|
||||
@@ -1080,6 +1142,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),
|
||||
@@ -1090,6 +1153,7 @@ static PyMethodDef module_methods[] = {
|
||||
MW(change_background_opacity, METH_VARARGS),
|
||||
MW(background_opacity_of, METH_O),
|
||||
MW(update_window_visibility, METH_VARARGS),
|
||||
MW(sync_os_window_title, METH_VARARGS),
|
||||
MW(global_font_size, METH_VARARGS),
|
||||
MW(set_background_image, METH_VARARGS),
|
||||
MW(os_window_font_size, METH_VARARGS),
|
||||
|
||||
@@ -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;
|
||||
@@ -137,6 +139,7 @@ typedef struct {
|
||||
} OSWindowGeometry;
|
||||
|
||||
enum RENDER_STATE { RENDER_FRAME_NOT_REQUESTED, RENDER_FRAME_REQUESTED, RENDER_FRAME_READY };
|
||||
typedef enum { NO_CLOSE_REQUESTED, CONFIRMABLE_CLOSE_REQUESTED, IMPERATIVE_CLOSE_REQUESTED } CloseRequest;
|
||||
|
||||
typedef struct {
|
||||
monotonic_t last_resize_event_at;
|
||||
@@ -181,6 +184,7 @@ typedef struct {
|
||||
uint64_t render_calls;
|
||||
id_type last_focused_counter;
|
||||
ssize_t gvao_idx;
|
||||
CloseRequest close_request;
|
||||
} OSWindow;
|
||||
|
||||
|
||||
@@ -196,7 +200,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;
|
||||
@@ -218,9 +222,8 @@ void remove_vao(ssize_t vao_idx);
|
||||
bool remove_os_window(id_type os_window_id);
|
||||
void make_os_window_context_current(OSWindow *w);
|
||||
void update_os_window_references(void);
|
||||
void mark_os_window_for_close(OSWindow* w, bool yes);
|
||||
void mark_os_window_for_close(OSWindow* w, CloseRequest cr);
|
||||
void update_os_window_viewport(OSWindow *window, bool);
|
||||
bool should_os_window_close(OSWindow* w);
|
||||
bool should_os_window_be_rendered(OSWindow* w);
|
||||
void wakeup_main_loop(void);
|
||||
void swap_window_buffers(OSWindow *w);
|
||||
@@ -274,3 +277,5 @@ void update_main_loop_timer(id_type timer_id, monotonic_t interval, bool enabled
|
||||
void run_main_loop(tick_callback_fun, void*);
|
||||
void stop_main_loop(void);
|
||||
void os_window_update_size_increments(OSWindow *window);
|
||||
void set_os_window_title_from_window(Window *w, OSWindow *os_window);
|
||||
void update_os_window_title(OSWindow *os_window);
|
||||
|
||||
@@ -21,6 +21,8 @@ class TabBarData(NamedTuple):
|
||||
title: str
|
||||
is_active: bool
|
||||
needs_attention: bool
|
||||
num_windows: int
|
||||
layout_name: str
|
||||
|
||||
|
||||
class DrawData(NamedTuple):
|
||||
@@ -56,7 +58,7 @@ def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int)
|
||||
if tab.is_active and draw_data.active_title_template is not None:
|
||||
template = draw_data.active_title_template
|
||||
try:
|
||||
title = template.format(title=tab.title, index=index)
|
||||
title = template.format(title=tab.title, index=index, layout_name=tab.layout_name, num_windows=tab.num_windows)
|
||||
except Exception as e:
|
||||
if template not in template_failures:
|
||||
template_failures.add(template)
|
||||
@@ -81,8 +83,11 @@ def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData
|
||||
screen.draw(' ' * trailing_spaces)
|
||||
end = screen.cursor.x
|
||||
screen.cursor.bold = screen.cursor.italic = False
|
||||
screen.cursor.fg = screen.cursor.bg = 0
|
||||
screen.draw(draw_data.sep)
|
||||
screen.cursor.fg = 0
|
||||
if not is_last:
|
||||
screen.cursor.bg = as_rgb(color_as_int(draw_data.inactive_bg))
|
||||
screen.draw(draw_data.sep)
|
||||
screen.cursor.bg = 0
|
||||
return end
|
||||
|
||||
|
||||
@@ -137,6 +142,7 @@ def draw_tab_with_powerline(draw_data: DrawData, screen: Screen, tab: TabBarData
|
||||
screen.draw(' ')
|
||||
screen.cursor.fg = tab_fg
|
||||
elif screen.cursor.x == 0:
|
||||
screen.cursor.bg = tab_bg
|
||||
screen.draw(' ')
|
||||
start_draw = 1
|
||||
|
||||
@@ -182,7 +188,7 @@ class TabBar:
|
||||
s.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
|
||||
s.color_profile.set_configured_colors(
|
||||
color_as_int(opts.inactive_tab_foreground),
|
||||
color_as_int(opts.background)
|
||||
color_as_int(opts.tab_bar_background or opts.background)
|
||||
)
|
||||
self.blank_rects: Tuple[Rect, ...] = ()
|
||||
sep = opts.tab_separator
|
||||
@@ -226,10 +232,13 @@ class TabBar:
|
||||
self.draw_data = self.draw_data._replace(default_bg=color_from_int(spec['tab_bar_background']))
|
||||
elif 'background' in spec and not self.opts.tab_bar_background:
|
||||
self.draw_data = self.draw_data._replace(default_bg=color_from_int(spec['background']))
|
||||
self.screen.color_profile.set_configured_colors(
|
||||
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)),
|
||||
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background))
|
||||
)
|
||||
fg = spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground))
|
||||
bg = spec.get('tab_bar_background', False)
|
||||
if bg is None:
|
||||
bg = color_as_int(self.opts.background)
|
||||
elif bg is False:
|
||||
bg = color_as_int(self.opts.tab_bar_background or self.opts.background)
|
||||
self.screen.color_profile.set_configured_colors(fg, bg)
|
||||
|
||||
def layout(self) -> None:
|
||||
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
|
||||
|
||||
104
kitty/tabs.py
104
kitty/tabs.py
@@ -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, sync_os_window_title, 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
|
||||
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
|
||||
@@ -148,6 +138,7 @@ class Tab: # {{{
|
||||
self._last_used_layout = self._current_layout_name
|
||||
self.current_layout = self.create_layout_object(layout_name)
|
||||
self._current_layout_name = layout_name
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
def startup(self, session_tab: 'SessionTab') -> None:
|
||||
for cmd in session_tab.windows:
|
||||
@@ -157,6 +148,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
|
||||
@@ -181,10 +184,13 @@ class Tab: # {{{
|
||||
old_active_window.focus_changed(False)
|
||||
if new_active_window is not None:
|
||||
new_active_window.focus_changed(True)
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
self.relayout_borders()
|
||||
tm.mark_tab_bar_dirty()
|
||||
self.relayout_borders()
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
def mark_tab_bar_dirty(self) -> None:
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
@property
|
||||
def active_window(self) -> Optional[Window]:
|
||||
@@ -204,7 +210,7 @@ class Tab: # {{{
|
||||
if window is self.active_window:
|
||||
tm = self.tab_manager_ref()
|
||||
if tm is not None:
|
||||
tm.mark_tab_bar_dirty()
|
||||
tm.title_changed(self)
|
||||
|
||||
def on_bell(self, window: Window) -> None:
|
||||
tm = self.tab_manager_ref()
|
||||
@@ -227,20 +233,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:
|
||||
@@ -331,6 +334,7 @@ class Tab: # {{{
|
||||
def _add_window(self, window: Window, location: Optional[str] = None) -> None:
|
||||
self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location)
|
||||
self.relayout_borders()
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
def new_window(
|
||||
self,
|
||||
@@ -345,11 +349,15 @@ class Tab: # {{{
|
||||
location: Optional[str] = None,
|
||||
copy_colors_from: Optional[Window] = None,
|
||||
allow_remote_control: bool = False,
|
||||
marker: Optional[str] = None
|
||||
marker: Optional[str] = None,
|
||||
watchers: Optional[Watchers] = None
|
||||
) -> Window:
|
||||
child = self.launch_child(
|
||||
use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, allow_remote_control=allow_remote_control)
|
||||
window = Window(self, child, self.opts, self.args, override_title=override_title, copy_colors_from=copy_colors_from)
|
||||
window = Window(
|
||||
self, child, self.opts, self.args, override_title=override_title,
|
||||
copy_colors_from=copy_colors_from, watchers=watchers
|
||||
)
|
||||
if overlay_for is not None:
|
||||
overlaid = next(w for w in self.windows if w.id == overlay_for)
|
||||
window.overlay_for = overlay_for
|
||||
@@ -384,6 +392,13 @@ class Tab: # {{{
|
||||
if self.windows:
|
||||
self.remove_window(self.windows[self.active_window_idx])
|
||||
|
||||
def close_other_windows_in_tab(self) -> None:
|
||||
if len(self.windows) > 1:
|
||||
active_window = self.windows[self.active_window_idx]
|
||||
for window in tuple(self.windows):
|
||||
if window is not active_window:
|
||||
self.remove_window(window)
|
||||
|
||||
def previous_active_window_idx(self, num: int) -> Optional[int]:
|
||||
try:
|
||||
old_window_id = self.active_window_history[-num]
|
||||
@@ -421,6 +436,7 @@ class Tab: # {{{
|
||||
else:
|
||||
self.active_window_idx = active_window_idx
|
||||
self.relayout_borders()
|
||||
self.mark_tab_bar_dirty()
|
||||
active_window = self.active_window
|
||||
if active_window:
|
||||
self.title_changed(active_window)
|
||||
@@ -535,7 +551,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)))
|
||||
@@ -629,9 +645,10 @@ 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 title_changed(self, tab: Tab) -> None:
|
||||
self.mark_tab_bar_dirty()
|
||||
if tab is self.active_tab:
|
||||
sync_os_window_title(self.os_window_id)
|
||||
|
||||
def resize(self, only_tabs: bool = False) -> None:
|
||||
if not only_tabs:
|
||||
@@ -692,6 +709,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
|
||||
@@ -785,7 +810,10 @@ class TabManager: # {{{
|
||||
if w.needs_attention:
|
||||
needs_attention = True
|
||||
break
|
||||
ans.append(TabBarData(title, t is at, needs_attention))
|
||||
ans.append(TabBarData(
|
||||
title, t is at, needs_attention,
|
||||
len(t), t.current_layout.name or ''
|
||||
))
|
||||
return ans
|
||||
|
||||
def activate_tab_at(self, x: int) -> None:
|
||||
|
||||
@@ -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
557
kitty/unicode-data.c
generated
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ from contextlib import suppress
|
||||
from functools import lru_cache
|
||||
from time import monotonic
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterable, List, NamedTuple, Optional,
|
||||
Tuple, Union, cast
|
||||
Any, Callable, Dict, Generator, Iterable, List, Match, NamedTuple,
|
||||
Optional, Tuple, Union, cast
|
||||
)
|
||||
|
||||
from .constants import (
|
||||
@@ -28,6 +28,20 @@ from .typing import AddressFamily, PopenType, Socket, StartupCtx
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def expandvars(val: str, env: Dict[str, str] = {}) -> str:
|
||||
|
||||
def sub(m: Match) -> str:
|
||||
key = m.group(1)
|
||||
result = env.get(key)
|
||||
if result is None:
|
||||
result = os.environ.get(key)
|
||||
if result is None:
|
||||
result = m.group()
|
||||
return result
|
||||
|
||||
return re.sub(r'\$\{(\S+?)\}', sub, val)
|
||||
|
||||
|
||||
def load_shaders(name: str) -> Tuple[str, str]:
|
||||
from .fast_data_types import GLSL_VERSION
|
||||
with open(os.path.join(BASE, '{}_vertex.glsl'.format(name))) as f:
|
||||
|
||||
395
kitty/wcwidth-std.h
generated
395
kitty/wcwidth-std.h
generated
@@ -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;
|
||||
}
|
||||
|
||||
115
kitty/window.py
115
kitty/window.py
@@ -10,8 +10,8 @@ from collections import deque
|
||||
from enum import IntEnum
|
||||
from itertools import chain
|
||||
from typing import (
|
||||
Any, Callable, Deque, Dict, List, Optional, Pattern, Sequence, Tuple,
|
||||
Union
|
||||
Any, Callable, Deque, Dict, Iterable, List, Optional, Pattern, Sequence,
|
||||
Tuple, Union
|
||||
)
|
||||
|
||||
from .child import ProcessDesc
|
||||
@@ -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 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,
|
||||
@@ -77,6 +78,19 @@ DYNAMIC_COLOR_CODES = {
|
||||
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
|
||||
|
||||
|
||||
class Watcher:
|
||||
|
||||
def __call__(self, boss: BossType, window: 'Window', data: Dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Watchers:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.on_resize: List[Watcher] = []
|
||||
self.on_close: List[Watcher] = []
|
||||
|
||||
|
||||
def calculate_gl_geometry(window_geometry: WindowGeometry, viewport_width: int, viewport_height: int, cell_width: int, cell_height: int) -> ScreenGeometry:
|
||||
dx, dy = 2 * cell_width / viewport_width, 2 * cell_height / viewport_height
|
||||
xmargin = window_geometry.left / viewport_width
|
||||
@@ -172,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__(
|
||||
@@ -181,8 +214,10 @@ class Window:
|
||||
opts: Options,
|
||||
args: CLIOptions,
|
||||
override_title: Optional[str] = None,
|
||||
copy_colors_from: Optional['Window'] = None
|
||||
copy_colors_from: Optional['Window'] = None,
|
||||
watchers: Optional[Watchers] = None
|
||||
):
|
||||
self.watchers = watchers or Watchers()
|
||||
self.action_on_close: Optional[Callable] = None
|
||||
self.action_on_removal: Optional[Callable] = None
|
||||
self.current_marker_spec: Optional[Tuple[str, Union[str, Tuple[Tuple[int, str], ...]]]] = None
|
||||
@@ -196,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
|
||||
@@ -214,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
|
||||
@@ -239,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()
|
||||
@@ -303,10 +394,12 @@ class Window:
|
||||
if not self.pty_resized_once:
|
||||
self.pty_resized_once = True
|
||||
self.child.mark_terminal_ready()
|
||||
self.call_watchers(self.watchers.on_resize, {'old_geometry': self.geometry, 'new_geometry': new_geometry})
|
||||
else:
|
||||
sg = self.update_position(new_geometry)
|
||||
self.geometry = g = new_geometry
|
||||
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
|
||||
@@ -536,7 +629,17 @@ class Window:
|
||||
return ''.join((l.rstrip() or '\n') for l in lines)
|
||||
return ''.join(lines)
|
||||
|
||||
def call_watchers(self, which: Iterable[Watcher], data: Dict[str, Any]) -> None:
|
||||
boss = get_boss()
|
||||
for w in which:
|
||||
try:
|
||||
w(boss, self, data)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def destroy(self) -> None:
|
||||
self.call_watchers(self.watchers.on_close, {})
|
||||
self.destroyed = True
|
||||
if hasattr(self, 'screen'):
|
||||
# Remove cycles so that screen is de-allocated immediately
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
files = [x for x in sys.argv[1:] if not x.startswith('-')]
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
files = [os.path.relpath(x, base) for x in sys.argv[1:] if not x.startswith('-')]
|
||||
if not files:
|
||||
raise SystemExit(subprocess.Popen(['mypy'] + sys.argv[1:]).wait())
|
||||
|
||||
output = subprocess.run('dmypy run -- --follow-imports=error --show-column-numbers --no-color-output'.split(), stdout=subprocess.PIPE).stdout
|
||||
q = files[0] + ':'
|
||||
rc = 0
|
||||
for line in output.decode('utf-8').splitlines():
|
||||
if line.startswith(q):
|
||||
print(line)
|
||||
rc = 1
|
||||
raise SystemExit(rc)
|
||||
|
||||
@@ -10,7 +10,7 @@ set tabstop=4
|
||||
set shiftwidth=4
|
||||
set softtabstop=0
|
||||
set smarttab
|
||||
python <<endpython
|
||||
python3 <<endpython
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
import kitty
|
||||
|
||||
6
setup.py
6
setup.py
@@ -349,7 +349,7 @@ def run_tool(cmd: Union[str, List[str]], desc: Optional[str] = None) -> None:
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
def get_vcs_rev_defines() -> List[str]:
|
||||
def get_vcs_rev_defines(env: Env) -> List[str]:
|
||||
ans = []
|
||||
if os.path.exists('.git'):
|
||||
try:
|
||||
@@ -368,7 +368,7 @@ def get_vcs_rev_defines() -> List[str]:
|
||||
return ans
|
||||
|
||||
|
||||
SPECIAL_SOURCES: Dict[str, Tuple[str, Union[List[str], Callable[[], Union[List[str], Iterator[str]]]]]] = {
|
||||
SPECIAL_SOURCES: Dict[str, Tuple[str, Union[List[str], Callable[[Env], Union[List[str], Iterator[str]]]]]] = {
|
||||
'kitty/parser_dump.c': ('kitty/parser.c', ['DUMP_COMMANDS']),
|
||||
'kitty/data-types.c': ('kitty/data-types.c', get_vcs_rev_defines),
|
||||
}
|
||||
@@ -557,7 +557,7 @@ def compile_c_extension(
|
||||
if is_special:
|
||||
src, defines_ = SPECIAL_SOURCES[src]
|
||||
if callable(defines_):
|
||||
defines = defines_()
|
||||
defines = defines_(kenv)
|
||||
cppflags.extend(map(define, defines))
|
||||
else:
|
||||
cppflags.extend(map(define, defines_))
|
||||
|
||||
@@ -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.
Reference in New Issue
Block a user