Compare commits

...

94 Commits

Author SHA1 Message Date
Kovid Goyal
cd7b4fcd8e version 0.23.1 2021-08-17 11:37:24 +05:30
Kovid Goyal
f67995d5d3 See if we can mollify CodeQL 2021-08-16 22:53:46 +05:30
Kovid Goyal
f0e7344bc8 Use bundled CA certs on Linux binary builds as well
Makes it consistent with macOS
2021-08-16 22:47:53 +05:30
Kovid Goyal
aa525c68c7 Only test bundled CA certs on macOS 2021-08-16 22:26:10 +05:30
Kovid Goyal
a2e25331a5 Dont check Linux CA locations on macOS 2021-08-16 22:23:24 +05:30
Kovid Goyal
d8f09d377f Install ca-certificates on Linux CI machines 2021-08-16 22:15:27 +05:30
Kovid Goyal
cc96cb1c75 Check a few more locations for CA certificates 2021-08-16 22:09:39 +05:30
Kovid Goyal
5966f04656 Add a build test for CA certificate loading 2021-08-16 20:44:47 +05:30
Kovid Goyal
dbce9a8f29 themes kitten: Fix only the first custom theme file being loaded
Fixes #3938
2021-08-16 20:27:21 +05:30
Kovid Goyal
4333552523 Matching windows/tabs: allow matching by recency
``recent:0`` matches the active window/tab, ``recent:1`` matches the
previous window/tab and so on
2021-08-16 18:29:06 +05:30
Kovid Goyal
c00e945f6e Make the clipboard size limit configurable
Fixes #3937
2021-08-16 17:29:36 +05:30
Kovid Goyal
56cb628ee8 macOS: Bundle mozilla's root certificates with kitty
Apple doesnt provide root certificates in a form useable by openssl
which means all ssl based network requests fail, so bundle our own

Fixes #3936
2021-08-16 17:16:03 +05:30
Kovid Goyal
827b6598b2 version 0.23.0 2021-08-16 07:38:02 +05:30
Kovid Goyal
59bafea06d Add some docs for non-interactive theme changing 2021-08-16 07:37:59 +05:30
Kovid Goyal
d61aca40b8 Make link to template.conf a download link 2021-08-16 07:14:49 +05:30
Kovid Goyal
bf704d35a2 Add a link to kitty-themes in the changelog 2021-08-16 07:11:05 +05:30
Kovid Goyal
fbc8881a96 Merge branch 'master' of github.com:kovidgoyal/kitty 2021-08-15 08:28:00 +05:30
Kovid Goyal
8cfb1efb01 hints kitten: Make the --program option work with --linenum-action=self
Fixes #3931
2021-08-15 08:27:25 +05:30
Kovid Goyal
f399b8466c Merge branch 'patch-1' of https://github.com/con-f-use/kitty 2021-08-14 19:42:19 +05:30
con-f-use
437ba69049 docs: typo in hint.rst, adaption to new layout 2021-08-14 15:19:27 +02:00
Kovid Goyal
dbc7e8e85d When dragging word or line selections, ensure the initially selected item is never deselected
This matches behavior in most other programs
Fixes #3930
2021-08-13 20:23:43 +05:30
Kovid Goyal
f3333ce941 Fix getting connection data from ssh command line when -- is used in the command line
This broke the remote file kitten when using the ssh kitten as the later
was changed to use -- recently.

Fixes #3929
2021-08-13 17:14:43 +05:30
Kovid Goyal
97a9261096 ... 2021-08-13 17:04:49 +05:30
Kovid Goyal
5a76dab3c6 ControlMaster must not suppress exceptions 2021-08-13 17:02:27 +05:30
Kovid Goyal
5a92d3f312 Fix a regression that broke the edit action in the remote file kitten 2021-08-13 16:59:40 +05:30
Kovid Goyal
b173dd1c39 Fix error getting editor in remote file kitten not being reported 2021-08-13 16:57:01 +05:30
Kovid Goyal
473e1a6f22 Fix #3926
Apparently nvim assumes cnorm doesn't affect cursor shape. It's
ambiguous whether cnorm should affect it or not. So to prevent
regressions in case other programs assume that as well, have it only
make the cursor visible and blinking, without changing its shape.
2021-08-13 09:31:18 +05:30
Kovid Goyal
9c4a890688 Adjust attenuation based on color 2021-08-11 12:41:09 +05:30
Kovid Goyal
a803c8bcc5 Make the visual bell flash more gentle, especially on dark themes
Fixes #2937
2021-08-11 12:09:25 +05:30
Kovid Goyal
5c7ce18379 Move visual bell code into shaders.c 2021-08-11 12:04:52 +05:30
Andrew Mayorov
aff6fdfa84 Make visual bell flash much more gentle
Right now visual bell makes background flash sharply with bright white
(when configured with darkish color theme). This causes eye strain,
especially prominent in unlit environments.

This change makes background bounce smoothly between regular bg color
and highlight (selection) bg color for the configured visual bell
duration. Intensity is animated with cubic easing functions. It
currently peaks at 20% of the duration, this is hardcoded.

Mark functions computing intensity and easing as inline

Do smarter blending of visual bell flash

Blend highlight color with pegtop's softlight mode over both background
and half as much over foreground. This should help with flash visibility
in light themed and inverted colors contexts.

Blend flash in BACKGROUND pass also

Revert "Do smarter blending of visual bell flash"

This reverts commit 9a269d55c7d04334b1c4891c42914423517dfad6.

Revert "Blend flash in BACKGROUND pass also"

This reverts commit 756332cb9d7576b08db78406e37349aa6d1df156.

Revert "Make visual bell flash much more gentle"

This reverts commit cbfe5d59ada2da690e14c7532a7468b0c307c462.

Render flash overlay in a separate drawcall

Trigger frequent repaints to make for smooth animation

Attenuate flash a bit more
2021-08-11 11:54:43 +05:30
Kovid Goyal
477a652b44 Function to update a theme file 2021-08-11 09:09:07 +05:30
Kovid Goyal
15e0b42f04 Make the regex for commenting settings more robust 2021-08-11 08:46:42 +05:30
Kovid Goyal
b25ea9c863 Fix #3913 2021-08-09 08:37:53 +05:30
Kovid Goyal
c838cb03c9 Do not allow reloading the config to affect allow_remote_control 2021-08-08 15:34:34 +05:30
Kovid Goyal
ba9adc127e Use realpath when creating conf.bak as well 2021-08-08 10:49:41 +05:30
Kovid Goyal
b7d603c4de Atomic save should preserve stat if the file exists 2021-08-08 10:41:46 +05:30
Kovid Goyal
71f1f3aa64 Atomic save should use the target of symlinks 2021-08-08 10:35:38 +05:30
Kovid Goyal
7d1c26202a Add completion for options that take choices 2021-08-08 10:08:15 +05:30
Kovid Goyal
80db2f6558 Completion for theme names 2021-08-08 09:57:57 +05:30
Kovid Goyal
e376c79dda Show the current theme name in kitty.conf as well 2021-08-08 09:42:07 +05:30
Kovid Goyal
5a47e0d2e4 Make reload_conf_in_all_kitties re-useable 2021-08-08 09:38:29 +05:30
Kovid Goyal
ca9143bebc Allow reloading conf in all kitty instances as well 2021-08-08 09:33:36 +05:30
Kovid Goyal
a49f6799de Print usage when no args provided to +runpy 2021-08-08 09:10:47 +05:30
Kovid Goyal
d916ecc4f3 Add a non-interactive mode to the themes kitten 2021-08-08 07:57:14 +05:30
Kovid Goyal
e1ed9aca10 clear_all_mouse_shortcuts -> clear_all_mouse_actions 2021-08-07 21:34:16 +05:30
Kovid Goyal
0e2f1b9405 Merge branch 'fork' of https://github.com/bdeshi/kitty 2021-08-07 21:30:57 +05:30
bdeshi
db5a2d2141 add clear_all_mouse_shortcuts option to clear mouse_maps 2021-08-07 21:33:56 +06:00
Kovid Goyal
a9771dccba Change list position in kittens intro 2021-08-07 18:40:36 +05:30
Kovid Goyal
4849e07c26 Document the themes kitten 2021-08-07 18:38:33 +05:30
Kovid Goyal
4645e78fa7 Reload config in parent kitty when running the themes kitten 2021-08-07 16:54:47 +05:30
Kovid Goyal
9e2590eb15 Set the KITTY_PID env var when running child processes 2021-08-07 16:48:34 +05:30
Kovid Goyal
94575a5cf6 Implement modifying of kitty.conf
An include for the current theme is added and
all existing color related settings are commented out
so that the theme overlays on kitty's default settings.
2021-08-07 16:34:41 +05:30
Kovid Goyal
a597a8d86b Make the recent list functional 2021-08-07 15:31:03 +05:30
Kovid Goyal
3287798efe terminfo: Fix "cnorm" the property for setting the cursor to normal using a solid block rather than a blinking block cursor
cnorm should match the default cursor in kitty, which is a blinking
block. Fixes #3906
2021-08-07 15:15:59 +05:30
Kovid Goyal
d01ac17334 Implement placing of selected theme in kitty config dir 2021-08-07 15:04:32 +05:30
Kovid Goyal
2c96727c45 Implement searching in the themes kitten 2021-08-07 11:17:32 +05:30
Kovid Goyal
ca1b2454bd Fix position output in matcher 2021-08-07 10:45:50 +05:30
Kovid Goyal
064fc17ce3 More work on themes kitten 2021-08-05 21:38:39 +05:30
Kovid Goyal
43ccf9cb41 Draw theme demo 2021-08-05 13:17:16 +05:30
Kovid Goyal
96857a197c Allow default theme to have no settings 2021-08-05 11:20:01 +05:30
Kovid Goyal
079ff7785c ... 2021-08-05 07:47:32 +05:30
Kovid Goyal
d6a6cbe153 Migrate import in test 2021-08-05 07:05:19 +05:30
Kovid Goyal
2486cfd45d Move match() into a more re-useable location 2021-08-05 06:59:24 +05:30
Kovid Goyal
30cb7286db Fix building on macOS
macOS defines newlocale() in xlocale.h
2021-08-05 06:47:45 +05:30
Kovid Goyal
9e7253c179 macOS: Set LANG to en_US if cocoa reports language as en_* that is not a valid locale
Fixes #3899
2021-08-05 06:29:21 +05:30
Kovid Goyal
276a82d1f7 Make get_all_actions() work in binary builds 2021-08-05 06:10:41 +05:30
Kovid Goyal
57ced9bc83 Dont send press event for shift+middle when grabbed
Fixes #3902
2021-08-05 05:03:22 +05:30
Kovid Goyal
3c3662b032 Limit display length for theme names 2021-08-04 23:18:57 +05:30
Kovid Goyal
f490b9a8bd Better error message for get_all_actions() when docstrings are missing 2021-08-04 22:58:05 +05:30
Kovid Goyal
c7ccedae95 ... 2021-08-04 22:27:48 +05:30
Kovid Goyal
03517459db Type check set_mode and reset_mode 2021-08-04 22:25:27 +05:30
Kovid Goyal
96326280e5 Use a pending update when drawing the screen 2021-08-04 22:06:05 +05:30
Kovid Goyal
9b7899780b Implement drawing the list of themes 2021-08-04 21:56:33 +05:30
Kovid Goyal
e01cd057e8 Fix timestamp not being updated in downloaded zip file comment
Also clean up tab bar formatting
2021-08-04 20:34:00 +05:30
Kovid Goyal
ba85ca1991 Finish the tab bar for the themes kitten 2021-08-04 19:06:07 +05:30
Kovid Goyal
92a9b71f21 More work on the themes kitten 2021-08-04 18:43:26 +05:30
Kovid Goyal
e50c26d1b9 Allow using a non-toal dict to init Options objects 2021-08-04 17:06:50 +05:30
Kovid Goyal
7090c24321 More work on the themes kitten 2021-08-04 14:13:09 +05:30
Kovid Goyal
55319cd6d6 FreeType: Round glyph advances
The old code would simply add advances and store as a float with
individual glyph positioning using floor() based on the advance +
offset. This breaks rendering of infinite length ligatures at some font
sizes as they either under or overflow. Rounding the advances seems to
fix it. Dont know if it will have other ill-effects for non infinite
length ligatures. If it does we can test total ligature length and
use rounding only for long ligatures.

Fixes #3896
2021-08-04 08:40:49 +05:30
Kovid Goyal
8a6b51441c More work on themes kitten 2021-08-04 07:36:09 +05:30
Kovid Goyal
36670b49a0 Make creation of kitty-themes.zip atomic 2021-08-03 17:47:47 +05:30
Kovid Goyal
a402d848d2 Work on themes kitten UI 2021-08-03 17:32:15 +05:30
Kovid Goyal
37c563802c typo 2021-08-03 12:58:38 +05:30
Kovid Goyal
2d7032973c Change the semantics of --title slightly
Now it no longer overrides the titles of windows/tabs created in the
session. This allows windows/tabs to have their own titles while fixing
the OS Window's title.
2021-08-03 09:51:53 +05:30
Kovid Goyal
70b5f5bce3 Fix kitty --title not overriding tab titles
It must set the OS Window title permanently.

Fixes #3893
2021-08-03 09:34:13 +05:30
Kovid Goyal
be34af4555 Remove inline from .c files
It is ignored by compilers and hides unused static functions
2021-08-03 09:11:17 +05:30
Kovid Goyal
815539a933 Clean up the docs for tab_bar_style 2021-08-03 08:52:19 +05:30
Kovid Goyal
ab889e2945 A new "slant" style for the tab bar 2021-08-03 08:45:55 +05:30
Kovid Goyal
b3231c8003 Fix a link in the conf docs 2021-08-02 21:58:39 +05:30
Kovid Goyal
5f6cb34f77 placeholder 2021-08-02 21:54:06 +05:30
Kovid Goyal
a99a080c50 More work on theme loading 2021-08-02 21:46:33 +05:30
Kovid Goyal
4a1ca8d582 Move theme parser into kitty code 2021-08-02 18:54:48 +05:30
Kovid Goyal
050eb5660d Start work on themes kitten 2021-08-02 17:56:31 +05:30
99 changed files with 2367 additions and 763 deletions

View File

@@ -34,7 +34,7 @@ def install_deps():
run('brew', 'install', *items)
else:
run('sudo apt-get update')
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev'
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev'
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev uuid-dev')
if is_bundle:

View File

@@ -24,6 +24,8 @@ def remote_control(args: List[str]) -> None:
def runpy(args: List[str]) -> None:
if len(args) < 2:
raise SystemExit('Usage: kitty +runpy "some python code"')
sys.argv = ['kitty'] + args[2:]
exec(args[1])
@@ -117,21 +119,21 @@ namespaced_entry_points['complete'] = complete
def setup_openssl_environment() -> None:
# Workaround for Linux distros that have still failed to get their heads
# out of their asses and implement a common location for SSL certificates.
# It's not that hard people, there exists a wonderful tool called the symlink
# See https://www.mobileread.com/forums/showthread.php?t=256095
if 'SSL_CERT_FILE' not in os.environ and 'SSL_CERT_DIR' not in os.environ:
if os.access('/etc/pki/tls/certs/ca-bundle.crt', os.R_OK):
os.environ['SSL_CERT_FILE'] = '/etc/pki/tls/certs/ca-bundle.crt'
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE')
elif os.path.isdir('/etc/ssl/certs'):
os.environ['SSL_CERT_DIR'] = '/etc/ssl/certs'
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_DIR')
# Use our bundled CA certificates instead of the system ones, since
# many systems come with no certificates in a useable form or have various
# locations for the certificates.
d = os.path.dirname
ext_dir: str = getattr(sys, 'kitty_extensions_dir')
if 'darwin' in sys.platform.lower():
cert_file = os.path.join(d(d(d(ext_dir))), 'cacert.pem')
else:
cert_file = os.path.join(d(ext_dir), 'cacert.pem')
os.environ['SSL_CERT_FILE'] = cert_file
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE')
def main() -> None:
if getattr(sys, 'frozen', False) and 'darwin' not in sys.platform.lower():
if getattr(sys, 'frozen', False) and getattr(sys, 'kitty_extensions_dir', ''):
setup_openssl_environment()
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
func = entry_points.get(first_arg)

View File

@@ -30,6 +30,7 @@ def initialize_constants():
kitty_constants['appname'] = re.search(
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
).group(2)
kitty_constants['cacerts_url'] = 'https://curl.haxx.se/ca/cacert.pem'
return kitty_constants

View File

@@ -92,6 +92,15 @@ def copy_libs(env):
subprocess.check_call(['chrpath', '-d', dest])
def add_ca_certs(env):
print('Downloading CA certs...')
from urllib.request import urlopen
cdata = urlopen(kitty_constants['cacerts_url']).read()
dest = os.path.join(env.lib_dir, 'cacert.pem')
with open(dest, 'wb') as f:
f.write(cdata)
def copy_python(env):
print('Copying python...')
srcdir = j(PREFIX, 'lib/python' + py_ver)
@@ -220,6 +229,7 @@ def main():
build_launcher(env)
files = find_binaries(env)
fix_permissions(files)
add_ca_certs(env)
if not args.dont_strip:
strip_binaries(files)
if not args.skip_tests:

View File

@@ -170,6 +170,7 @@ class Freeze(object):
self.add_stdlib()
self.add_misc_libraries()
self.freeze_python()
self.add_ca_certs()
if not self.dont_strip:
self.strip_files()
if not self.skip_tests:
@@ -180,6 +181,15 @@ class Freeze(object):
return ret
@flush
def add_ca_certs(self):
print('\nDownloading CA certs...')
from urllib.request import urlopen
cdata = urlopen(kitty_constants['cacerts_url']).read()
dest = os.path.join(self.contents_dir, 'Resources', 'cacert.pem')
with open(dest, 'wb') as f:
f.write(cdata)
@flush
def strip_files(self):
print('\nStripping files...')

View File

@@ -4,6 +4,70 @@ Changelog
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.23.1 [2021-08-17]
----------------------
- macOS: Fix themes kitten failing to download themes because of missing SSL
root certificates (:iss:`3936`)
- A new option :opt:`clipboard_max_size` to control the maximum size
of data that kitty will transmit to the system clipboard on behalf of
programs running inside it (:iss:`3937`)
- When matching windows/tabs in kittens or using remote control, allow matching
by recency. ``recent:0`` matches the active window/tab, ``recent:1`` matches
the previous window/tab and so on
- themes kitten: Fix only the first custom theme file being loaded correctly
(:iss:`3938`)
0.23.0 [2021-08-16]
----------------------
- A new :doc:`themes kitten </kittens/themes>` to easily change kitty themes.
Choose from almost two hundred themes in the `kitty themes repository
<https://github.com/kovidgoyal/kitty-themes>`_
- A new style for the tab bar that makes tabs looks like the tabs in a physical
tabbed file, see :opt:`tab_bar_style`
- Make the visual bell flash more gentle, especially on dark themes
(:pull:`2937`)
- Fix :option:`kitty --title` not overriding the OS Window title when multiple
tabs are present. Also this option is no longer used as the default title for
windows, allowing individual tabs/windows to have their own titles, even when
the OS Window has a fixed overall title (:iss:`3893`)
- Linux: Fix some very long ligatures being rendered incorrectly at some font
sizes (:iss:`3896`)
- Fix shift+middle click to paste sending a mouse press event but no release
event which breaks some applications that grab the mouse but cant handle
mouse events (:iss:`3902`)
- macOS: When the language is set to English and the country to one for which
an English locale does not exist, set :envvar:`LANG` to ``en_US.UTF-8``
(:iss:`3899`)
- terminfo: Fix "cnorm" the property for setting the cursor to normal using a
solid block rather than a blinking block cursor (:iss:`3906`)
- Add :opt:`clear_all_mouse_actions` to clear all mouse actions defined to
that point (:iss:`3907`)
- Fix the remote file kitten not working when using -- with ssh. The ssh kitten
was recently changed to do this (:iss:`3929`)
- When dragging word or line selections, ensure the initially selected item is
never deselected. This matches behavior in most other programs (:iss:`3930`)
- hints kitten: Make copy/paste with the :option:`kitty +kitten hints
--program` option work when using the ``self``
:option:`kitty +kitten hints --linenum-action` (:iss:`3931`)
0.22.2 [2021-08-02]
----------------------

View File

@@ -117,7 +117,14 @@ explicitly set a UTF-8 locale, like::
How do I change the colors in a running kitty instance?
------------------------------------------------------------
You can either use the
The easiest way to do it is to use the :doc:`themes kitten </kittens/themes>`,
to choose a new color theme. Simply run::
kitty +kitten themes
And choose your theme from the list.
Additionally, You can use the
`OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands>`_
to set colors or you can define keyboard shortcuts to set colors, for example::

View File

@@ -66,12 +66,25 @@ Variables that influence kitty behavior
Variables that kitty sets when running child programs
.. envvar:: LANG
This is set only on macOS, and only if the country and language from the
macOS user settings form a valid locale.
.. envvar:: KITTY_WINDOW_ID
An integer that is the id for the kitty :term:`window` the program is running in.
Can be used with the :doc:`kitty remote control facility <remote-control>`.
.. envvar:: KITTY_PID
An integer that is the process id for the kitty process in which the program
is running. Allows programs to tell kitty to reload its config by sending it
the SIGUSR1 signal.
.. envvar:: WINDOWID
The id for the :term:`OS Window <os_window>` the program is running in. Only available

View File

@@ -92,10 +92,8 @@ look it up in the Google dictionary.
.. note::
To avoid having to specify the same command line options on ever invocation,
you can use the :opt:`kitten_alias` option in :file:`kitty.conf` to create aliases
To avoid having to specify the same command line options on every invocation,
you can use the :opt:`kitten_alias` option in :file:`kitty.conf`, creating aliases
that have common sets of options. For example::
kitten_alias myhints hints --alphabet qfjdkslaureitywovmcxzpq1234567890
Documentation for the full set of options is below.

66
docs/kittens/themes.rst Normal file
View File

@@ -0,0 +1,66 @@
Changing kitty colors
========================
The themes kitten allows you to easily change color themes, from a collection
of almost two hundred pre-built themes available at `kitty-themes
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::
kitty +kitten themes
The kitten allows you to pick a theme, with live previews of the colors. You
can choose between light and dark themes and search by theme name by just
typing a few characters from the name.
The kitten maintains a list of recently used themes to allow quick switching.
If you want to restore the colors to default, you can do so by choosing the
``Default`` theme.
.. versionadded:: 0.23.0
The themes kitten
How it works
----------------
A theme in kitty is just a :file:`.conf` file containing kitty settings.
When you select a theme, the kitten simply copies the :file:`.conf` file
to :file:`~/.config/kitty/current-theme.conf` and adds an include for
:file:`current-theme.conf` to :file:`kitty.conf`. It also comments out
any existing color settings in :file:`kitty.conf` so they do not interfere.
Once that's done, the kitten sends kitty a signal to make it reload its config.
Using your own themes
-----------------------
You can also create your own themes as :file:`.conf` files. Put them in the
:file:`themes` sub-directory of the kitty config directory, usually,
:file:`~/.config/kitty/themes` and the kitten will automatically add them to
the list of themes. You can use this to modify the builtin themes, by giving
the conf file the name :file:`Some theme name.conf` to override the builtin
theme of that name. Note that after doing so you have to run the kitten and
choose that theme once for your changes to be applied.
Contributing new themes
-------------------------
If you wish to contribute a new theme to the kitty theme repository, simply
go to `kitty-themes <https://github.com/kovidgoyal/kitty-themes>`_ and open a pull request
asking to add your contributions to the repository. Use the file
:download:`template.conf <https://github.com/kovidgoyal/kitty-themes/raw/master/template.conf>` as
a template when creating your theme.
Changing the theme non-interactively
---------------------------------------
You can specify the theme name as an argument when invoking the kitten
to have it change to that theme instantly. For example::
kitty +kitten theme --reload-in=all Dimmed Monokai
Will change the theme to ``Dimmed Monokai`` in all running kitty
instances. See below for more details on non-interactive operation.
.. include:: ../generated/cli-kitten-themes.rst

View File

@@ -10,6 +10,7 @@ Extend with kittens
kittens/icat
kittens/diff
kittens/unicode-input
kittens/themes
kittens/hints
kittens/remote_file
kittens/hyperlinked_grep

View File

@@ -116,7 +116,7 @@ endless. See the :doc:`tutorial <remote-control>` to get started.
Startup Sessions
------------------
You can control the :term:`tabs <tab>`, `:term:`kitty window <window>` layout,
You can control the :term:`tabs <tab>`, :term:`kitty window <window>` layout,
working directory, startup programs,
etc. by creating a "session" file and using the :option:`kitty --session`
command line flag or the :opt:`startup_session` option in :file:`kitty.conf`.

View File

@@ -91,7 +91,7 @@ compare_timers(const void *a_, const void *b_) {
return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0;
}
static inline void
static void
update_timers(EventLoopData *eld) {
if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers);
}
@@ -164,7 +164,7 @@ prepareForPoll(EventLoopData *eld, monotonic_t timeout) {
return timeout;
}
static inline struct timespec
static struct timespec
calc_time(monotonic_t nsec) {
struct timespec result;
result.tv_sec = nsec / (1000LL * 1000LL * 1000LL);
@@ -271,7 +271,7 @@ wakeupEventLoop(EventLoopData *eld) {
}
#ifndef HAS_EVENT_FD
static inline void
static void
closeFds(int *fds, size_t count) {
while(count--) {
if (*fds > 0) {

View File

@@ -453,7 +453,7 @@ void* _glfwLoadLocalVulkanLoaderNS(void)
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
static inline bool
static bool
is_ctrl_tab(NSEvent *event, NSEventModifierFlags modifierFlags) {
if (
(modifierFlags == NSEventModifierFlagControl &&
@@ -464,7 +464,7 @@ is_ctrl_tab(NSEvent *event, NSEventModifierFlags modifierFlags) {
return false;
}
static inline bool
static bool
is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) {
if (modifierFlags != NSEventModifierFlagCommand) return false;
if ([event.charactersIgnoringModifiers isEqualToString:@"."]) return true;
@@ -703,7 +703,7 @@ typedef struct {
static Timer timers[128] = {{0}};
static size_t num_timers = 0;
static inline void
static void
remove_timer_at(size_t idx) {
if (idx < num_timers) {
Timer *t = timers + idx;

View File

@@ -233,10 +233,10 @@ mac_ucode_to_functional(uint32_t key_code) { // {{{
}
} // }}}
static inline bool
static bool
is_surrogate(UniChar uc) { return (uc - 0xd800u) < 2048u; }
static inline uint32_t
static uint32_t
get_first_codepoint(UniChar *utf16, UniCharCount num) {
if (!num) return 0;
if (!is_surrogate(*utf16)) return *utf16;
@@ -244,7 +244,7 @@ get_first_codepoint(UniChar *utf16, UniCharCount num) {
return 0;
}
static inline bool
static bool
is_pua_char(uint32_t ch) {
return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
}
@@ -321,7 +321,7 @@ _glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data U
}
}
static inline void
static void
requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
if (!callback) {
w->ns.renderFrameRequested = false;
@@ -505,7 +505,7 @@ translateFlags(NSUInteger flags)
#define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
static inline const char*
static const char*
format_mods(int mods) {
static char buf[128];
char *p = buf, *s;
@@ -525,7 +525,7 @@ format_mods(int mods) {
return buf;
}
static inline const char*
static const char*
format_text(const char *src) {
static char buf[256];
char *p = buf;
@@ -1144,7 +1144,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[super updateTrackingAreas];
}
static inline UInt32
static UInt32
convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
UInt32 mods = 0;
if (flags & NSEventModifierFlagShift)
@@ -1161,7 +1161,7 @@ convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
return (mods >> 8) & 0xFF;
}
static inline void
static void
convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) {
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
src,
@@ -1174,12 +1174,12 @@ convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t
CFRelease(string);
}
static inline bool
static bool
alternate_key_is_ok(uint32_t key, uint32_t akey) {
return akey > 31 && akey != key && !is_pua_char(akey);
}
static inline void
static void
add_alternate_keys(GLFWkeyevent *ev, NSEvent *event) {
ev->alternate_key = translateKey(ev->native_key, false);
if (!alternate_key_is_ok(ev->key, ev->alternate_key)) ev->alternate_key = 0;
@@ -1198,7 +1198,7 @@ add_alternate_keys(GLFWkeyevent *ev, NSEvent *event) {
}
}
static inline bool
static bool
is_ascii_control_char(char x) {
return x == 0 || (1 <= x && x <= 31) || x == 127;
}

4
glfw/dbus_glfw.c vendored
View File

@@ -31,7 +31,7 @@
#include <stdlib.h>
#include <string.h>
static inline void
static void
report_error(DBusError *err, const char *fmt, ...) {
static char buf[1024];
va_list args;
@@ -64,7 +64,7 @@ on_dbus_watch_ready(int fd UNUSED, int events, void *data) {
dbus_watch_handle(watch, flags);
}
static inline int
static int
events_for_watch(DBusWatch *watch) {
int events = 0;
unsigned int flags = dbus_watch_get_flags(watch);

12
glfw/ibus_glfw.c vendored
View File

@@ -50,13 +50,13 @@ enum Capabilities {
};
static inline bool
static bool
test_env_var(const char *name, const char *val) {
const char *q = getenv(name);
return (q && strcmp(q, val) == 0) ? true : false;
}
static inline size_t
static size_t
GLFW_MIN(size_t a, size_t b) {
return a < b ? a : b;
}
@@ -106,7 +106,7 @@ get_ibus_text_from_message(DBusMessage *msg) {
return text;
}
static inline void
static void
send_text(const char *text, GLFWIMEState ime_state) {
_GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) {
@@ -147,7 +147,7 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static inline const char*
static const char*
get_ibus_address_file_name(void) {
const char *addr;
static char ans[PATH_MAX];
@@ -196,7 +196,7 @@ get_ibus_address_file_name(void) {
}
static inline bool
static bool
read_ibus_address(_GLFWIBUSData *ibus) {
static char buf[1024];
struct stat s;
@@ -379,7 +379,7 @@ typedef enum
} IBusModifierType;
static inline uint32_t
static uint32_t
ibus_key_state(unsigned int glfw_modifiers, int action) {
uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0;
#define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i

2
glfw/wl_init.c vendored
View File

@@ -54,7 +54,7 @@
#endif
static inline int min(int n1, int n2)
static int min(int n1, int n2)
{
return n1 < n2 ? n1 : n2;
}

View File

@@ -43,7 +43,7 @@ text_input_leave(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED,
}
}
static inline void
static void
send_text(const char *text, GLFWIMEState ime_state) {
_GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) {

2
glfw/wl_window.c vendored
View File

@@ -1762,7 +1762,7 @@ void _glfwSetupWaylandPrimarySelectionDevice() {
if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL);
}
static inline bool _glfwEnsureDataDevice(void) {
static bool _glfwEnsureDataDevice(void) {
if (!_glfw.wl.dataDeviceManager)
{
_glfwInputError(GLFW_PLATFORM_ERROR,

6
glfw/x11_window.c vendored
View File

@@ -281,7 +281,7 @@ updateNormalHints(_GLFWwindow* window, int width, int height)
XFree(hints);
}
static inline bool
static bool
is_window_fullscreen(_GLFWwindow* window)
{
Atom* states;
@@ -310,7 +310,7 @@ is_window_fullscreen(_GLFWwindow* window)
return ans;
}
static inline void
static void
set_fullscreen(_GLFWwindow *window, bool on) {
if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) {
sendEventToWM(window,
@@ -2646,7 +2646,7 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
PropModeReplace, (unsigned char*) &value, 1);
}
static inline unsigned
static unsigned
dispatch_x11_queued_events(int num_events) {
unsigned dispatched = num_events > 0 ? num_events : 0;
while (num_events-- > 0) {

8
glfw/xkb_glfw.c vendored
View File

@@ -490,7 +490,7 @@ load_compose_tables(_GLFWXKBData *xkb) {
xkb_compose_table_unref(compose_table);
}
static inline xkb_mod_mask_t
static xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
size_t i = 0;
xkb_mod_mask_t ans = 0;
@@ -577,7 +577,7 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode) {
}
static inline xkb_keysym_t
static xkb_keysym_t
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed, char *key_text, int n) {
*compose_completed = 0;
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
@@ -610,7 +610,7 @@ glfw_xkb_keysym_from_name(const char *name, bool case_sensitive) {
return (int)xkb_keysym_from_name(name, case_sensitive ? XKB_KEYSYM_NO_FLAGS : XKB_KEYSYM_CASE_INSENSITIVE);
}
static inline const char*
static const char*
format_mods(unsigned int mods) {
static char buf[128];
char *p = buf, *s;
@@ -632,7 +632,7 @@ format_mods(unsigned int mods) {
return buf;
}
static inline const char*
static const char*
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
static char buf[512];
char *p = buf, *s;

View File

@@ -3,47 +3,14 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
from typing import Iterable, List, Union
from typing import List
from kitty.key_encoding import KeyEvent
from . import subseq_matcher
from ..tui.handler import Handler
from ..tui.loop import Loop
def match(
input_data: Union[str, bytes, Iterable[Union[str, bytes]]],
query: str,
threads: int = 0,
positions: bool = False,
level1: str = '/',
level2: str = '-_0123456789',
level3: str = '.',
limit: int = 0,
mark_before: str = '',
mark_after: str = '',
delimiter: str = '\n'
) -> List[str]:
if isinstance(input_data, str):
idata = [x.encode('utf-8') for x in input_data.split(delimiter)]
elif isinstance(input_data, bytes):
idata = input_data.split(delimiter.encode('utf-8'))
else:
idata = [x.encode('utf-8') if isinstance(x, str) else x for x in input_data]
query = query.lower()
level1 = level1.lower()
level2 = level2.lower()
level3 = level3.lower()
data = subseq_matcher.match(
idata, (level1, level2, level3), query,
positions, limit, threads,
mark_before, mark_after, delimiter)
if data is None:
return []
return list(filter(None, data.split(delimiter or '\n')))
class ChooseHandler(Handler):
def initialize(self) -> None:

39
kittens/choose/match.py Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Iterable, List, Union
from . import subseq_matcher
def match(
input_data: Union[str, bytes, Iterable[Union[str, bytes]]],
query: str,
threads: int = 0,
positions: bool = False,
level1: str = '/',
level2: str = '-_0123456789',
level3: str = '.',
limit: int = 0,
mark_before: str = '',
mark_after: str = '',
delimiter: str = '\n'
) -> List[str]:
if isinstance(input_data, str):
idata = [x.encode('utf-8') for x in input_data.split(delimiter)]
elif isinstance(input_data, bytes):
idata = input_data.split(delimiter.encode('utf-8'))
else:
idata = [x.encode('utf-8') if isinstance(x, str) else x for x in input_data]
query = query.lower()
level1 = level1.lower()
level2 = level2.lower()
level3 = level3.lower()
data = subseq_matcher.match(
idata, (level1, level2, level3), query,
positions, limit, threads,
mark_before, mark_after, delimiter)
if data is None:
return []
return list(filter(None, data.split(delimiter or '\n')))

View File

@@ -14,7 +14,7 @@
#ifdef ISWINDOWS
#include <io.h>
#define STDOUT_FILENO 1
static inline ssize_t ms_write(int fd, const void* buf, size_t count) { return _write(fd, buf, (unsigned int)count); }
static ssize_t ms_write(int fd, const void* buf, size_t count) { return _write(fd, buf, (unsigned int)count); }
#define write ms_write
#else
#include <unistd.h>
@@ -24,7 +24,7 @@ static inline ssize_t ms_write(int fd, const void* buf, size_t count) { return _
#define FIELD(x, which) (((Candidate*)(x))->which)
static inline bool
static bool
ensure_space(GlobalData *global, size_t sz) {
if (global->output_sz < sz + global->output_pos || !global->output) {
size_t before = global->output_sz;
@@ -38,7 +38,7 @@ ensure_space(GlobalData *global, size_t sz) {
return true;
}
static inline void
static void
output_text(GlobalData *global, const text_t *data, size_t sz) {
if (ensure_space(global, sz)) {
memcpy(global->output + global->output_pos, data, sizeof(text_t) * sz);
@@ -66,10 +66,10 @@ static void
output_positions(GlobalData *global, len_t *positions, len_t num) {
wchar_t buf[128];
for (len_t i = 0; i < num; i++) {
int num = swprintf(buf, sizeof(buf)/sizeof(buf[0]), L"%u", positions[i]);
if (num > 0 && ensure_space(global, num + 1)) {
for (int i = 0; i < num; i++) global->output[global->output_pos++] = buf[i];
global->output[global->output_pos++] = (i == num - 1) ? ',' : ':';
int pnum = swprintf(buf, arraysz(buf), L"%u", positions[i]);
if (pnum > 0 && ensure_space(global, pnum + 1)) {
for (int k = 0; k < pnum; k++) global->output[global->output_pos++] = buf[k];
global->output[global->output_pos++] = (i == num - 1) ? ':' : ',';
}
}
}

View File

@@ -59,7 +59,7 @@ free_workspace(void *v) {
return NULL;
}
static inline bool
static bool
has_char(text_t *text, len_t sz, text_t ch) {
for(len_t i = 0; i < sz; i++) {
if(text[i] == ch) return true;
@@ -67,7 +67,7 @@ has_char(text_t *text, len_t sz, text_t ch) {
return false;
}
static inline uint8_t
static uint8_t
level_factor_for(text_t current, text_t last, WorkSpace *w) {
text_t lch = LOWERCASE(last);
if (has_char(w->level1, w->level1_len, lch)) return 90;
@@ -101,7 +101,7 @@ init_workspace(WorkSpace *w, text_t *haystack, len_t haystack_len) {
}
static inline bool
static bool
has_atleast_one_match(WorkSpace *w) {
int p = -1;
bool found;
@@ -118,7 +118,7 @@ has_atleast_one_match(WorkSpace *w) {
#define POSITION(x) w->positions[x][w->address[x]]
static inline bool
static bool
increment_address(WorkSpace *w) {
len_t pos = w->needle_len - 1;
while(true) {
@@ -130,7 +130,7 @@ increment_address(WorkSpace *w) {
return false;
}
static inline bool
static bool
address_is_monotonic(WorkSpace *w) {
// Check if the character positions pointed to by the current address are monotonic
for (len_t i = 1; i < w->needle_len; i++) {
@@ -139,7 +139,7 @@ address_is_monotonic(WorkSpace *w) {
return true;
}
static inline double
static double
calc_score(WorkSpace *w) {
double ans = 0;
len_t distance, pos;

View File

@@ -72,8 +72,11 @@ class Options:
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
if options_dict is not None:
null = object()
for key in option_names:
setattr(self, key, options_dict[key])
val = options_dict.get(key, null)
if val is not null:
setattr(self, key, val)
@property
def _fields(self) -> typing.Tuple[str, ...]:

View File

@@ -41,7 +41,7 @@ typedef struct {
static const Segment EMPTY_SEGMENT = { .current_pos = UINT_MAX };
static inline bool
static bool
convert_segment(PyObject *highlight, Segment *dest) {
PyObject *val = NULL;
#define I
@@ -62,7 +62,7 @@ convert_segment(PyObject *highlight, Segment *dest) {
return true;
}
static inline bool
static bool
next_segment(SegmentPointer *s, PyObject *highlights) {
if (s->pos < s->num) {
if (!convert_segment(PyList_GET_ITEM(highlights, s->pos), &s->sg)) return false;
@@ -89,7 +89,7 @@ ensure_space(LineBuffer *b, size_t num) {
return true;
}
static inline bool
static bool
insert_code(PyObject *code, LineBuffer *b) {
unsigned int csz = PyUnicode_GET_LENGTH(code);
if (!ensure_space(b, csz)) return false;
@@ -97,7 +97,7 @@ insert_code(PyObject *code, LineBuffer *b) {
return true;
}
static inline bool
static bool
add_line(Segment *bg_segment, Segment *fg_segment, LineBuffer *b, PyObject *ans) {
bool bg_is_active = bg_segment->current_pos == bg_segment->end_pos, fg_is_active = fg_segment->current_pos == fg_segment->end_pos;
if (bg_is_active) { if(!insert_code(bg_segment->end_code, b)) return false; }

View File

@@ -553,10 +553,12 @@ choices=self,window,tab,os_window,background
Where to perform the action on matched errors. :code:`self` means the current
window, :code:`window` a new kitty window, :code:`tab` a new tab,
:code:`os_window` a new OS window and :code:`background` run in the background.
The action to perform on the matched errors. The actual action is whatever
arguments are provided to the kitten, for example: :code:`kitty + kitten hints
--type=linenum --linenum-action=tab vim +{line} {path}` will open the matched
path at the matched line number in vim in a new kitty tab.
The actual action is whatever arguments are provided to the kitten, for
example:
:code:`kitty + kitten hints --type=linenum --linenum-action=tab vim +{line} {path}`
will open the matched path at the matched line number in vim in
a new kitty tab. Note that only when using :code:`self` are the special values for
:option:`kitty +kitten hints --program` to copy/paste the text respected.
--url-prefixes
@@ -705,9 +707,19 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
if action == 'self':
if w is not None:
import shlex
text = ' '.join(shlex.quote(arg) for arg in cmd)
w.paste_bytes(text + '\r')
is_copy_action = cmd[0] in ('-', '@', '*')
if is_copy_action:
text = ' '.join(cmd[1:])
if cmd[0] == '-':
w.paste_bytes(text)
elif cmd[0] == '@':
set_clipboard_string(text)
elif cmd[0] == '*':
set_primary_selection(text)
else:
import shlex
text = ' '.join(shlex.quote(arg) for arg in cmd)
w.paste_bytes(text + '\r')
elif action == 'background':
import subprocess
subprocess.Popen(cmd, cwd=data['cwd'])

View File

@@ -138,7 +138,6 @@ class ControlMaster:
).wait()
if self.tdir:
shutil.rmtree(self.tdir)
return True
@property
def is_alive(self) -> bool:
@@ -286,6 +285,7 @@ def handle_action(action: str, cli_opts: RemoteFileCLIOptions) -> Result:
show_error('Failed to copy file from remote machine')
elif action == 'edit':
print('Editing', cli_opts.path, 'from', cli_opts.hostname)
editor = get_editor()
with ControlMaster(conn_data, remote_path, cli_opts) as master:
if not master.check_hostname_matches():
return None
@@ -294,7 +294,6 @@ def handle_action(action: str, cli_opts: RemoteFileCLIOptions) -> Result:
return None
mtime = os.path.getmtime(master.dest)
print(reset_terminal(), end='', flush=True)
editor = get_editor()
editor_process = subprocess.Popen(editor + [master.dest])
while editor_process.poll() is None:
time.sleep(0.1)

View File

@@ -87,14 +87,14 @@ def launch(args: List[str]) -> None:
del args[:2]
args = [kitten] + args
os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir
from kittens.tui.operations import clear_screen, reset_mode
from kittens.tui.operations import clear_screen, reset_mode, Mode
set_debug(kitten)
m = import_kitten_main_module(config_dir, kitten)
try:
result = m['start'](args)
finally:
sys.stdin = sys.__stdin__
print(reset_mode('ALTERNATE_SCREEN') + clear_screen(), end='')
print(reset_mode(Mode.ALTERNATE_SCREEN) + clear_screen(), end='')
if result is not None:
import json
data = json.dumps(result)

View File

@@ -146,15 +146,20 @@ def get_connection_data(args: List[str]) -> Optional[SSHConnectionData]:
port: Optional[int] = None
expecting_port = False
expecting_option_val = False
expecting_hostname = False
for i, arg in enumerate(args):
if not found_ssh:
if os.path.basename(arg).lower() in ('ssh', 'ssh.exe'):
found_ssh = arg
continue
if expecting_hostname:
return SSHConnectionData(found_ssh, arg, port)
if arg.startswith('-') and not expecting_option_val:
if arg in boolean_ssh_args:
continue
if arg == '--':
expecting_hostname = True
if arg.startswith('-p'):
if arg[2:].isdigit():
with suppress(Exception):

View File

View File

@@ -0,0 +1,387 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import datetime
import http
import json
import os
import re
import shutil
import signal
import tempfile
import zipfile
from contextlib import suppress
from typing import Any, Callable, Dict, Iterator, Match, Optional, Tuple, Union, Type
from urllib.error import HTTPError
from urllib.request import Request, urlopen
from kitty.config import atomic_save, parse_config
from kitty.constants import cache_dir, config_dir
from kitty.options.types import Options as KittyOptions
from kitty.rgb import Color
from kitty.utils import reload_conf_in_all_kitties
from ..choose.match import match
MARK_BEFORE = '\033[33m'
MARK_AFTER = '\033[39m'
def patch_conf(raw: str, theme_name: str) -> str:
addition = f'# BEGIN_KITTY_THEME\n# {theme_name}\ninclude current-theme.conf\n# END_KITTY_THEME'
nraw, num = re.subn(r'^# BEGIN_KITTY_THEME.+?# END_KITTY_THEME', addition, raw, flags=re.MULTILINE | re.DOTALL)
if not num:
if raw:
raw += '\n\n'
nraw = raw + addition
# comment out all existing color definitions
color_conf_items = [f'color{i}' for i in range(256)] + list(filter(None, '''
foreground
background
selection_foreground
selection_background
cursor
cursor_text_color
url_color
active_border_color
inactive_border_color
bell_border_color
wayland_titlebar_color
macos_titlebar_color
active_tab_foreground
active_tab_background
inactive_tab_foreground
inactive_tab_background
tab_bar_background
mark1_foreground
mark1_background
mark2_foreground
mark2_background
mark3_foreground
mark3_background
'''.splitlines()))
pat = fr'^\s*({"|".join(color_conf_items)})\b'
return re.sub(pat, r'# \1', nraw, flags=re.MULTILINE)
def set_comment_in_zip_file(path: str, data: str) -> None:
with zipfile.ZipFile(path, 'a') as zf:
zf.comment = data.encode('utf-8')
class NoCacheFound(ValueError):
pass
def fetch_themes(
name: str = 'kitty-themes',
url: str = 'https://codeload.github.com/kovidgoyal/kitty-themes/zip/master',
cache_age: float = 1,
) -> str:
now = datetime.datetime.now(datetime.timezone.utc)
cache_age_delta = datetime.timedelta(days=cache_age)
class Metadata:
def __init__(self) -> None:
self.etag = ''
self.timestamp = now
def __str__(self) -> str:
return json.dumps({'etag': self.etag, 'timestamp': self.timestamp.isoformat()})
dest_path = os.path.join(cache_dir(), f'{name}.zip')
m = Metadata()
with suppress(Exception), zipfile.ZipFile(dest_path, 'r') as zf:
q = json.loads(zf.comment)
m.etag = str(q.get('etag') or '')
m.timestamp = datetime.datetime.fromisoformat(q['timestamp'])
if cache_age < 0 or (now - m.timestamp) < cache_age_delta:
return dest_path
if cache_age < 0:
raise NoCacheFound('No local themes cache found and negative cache age specified, aborting')
rq = Request(url)
m.timestamp = now
if m.etag:
rq.add_header('If-None-Match', m.etag)
try:
res = urlopen(rq, timeout=30)
except HTTPError as e:
if m.etag and e.code == http.HTTPStatus.NOT_MODIFIED:
set_comment_in_zip_file(dest_path, str(m))
return dest_path
raise
m.etag = res.headers.get('etag') or ''
needs_delete = False
try:
with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(dest_path), dir=os.path.dirname(dest_path), delete=False) as f:
needs_delete = True
shutil.copyfileobj(res, f)
f.flush()
set_comment_in_zip_file(f.name, str(m))
os.replace(f.name, dest_path)
needs_delete = False
finally:
if needs_delete:
os.unlink(f.name)
return dest_path
def zip_file_loader(path_to_zip: str, theme_file_name: str, file_name: str) -> Callable[[], str]:
name = os.path.join(os.path.dirname(theme_file_name), file_name)
def zip_loader() -> str:
with zipfile.ZipFile(path_to_zip, 'r') as zf, zf.open(name) as f:
return f.read().decode('utf-8')
return zip_loader
def theme_name_from_file_name(fname: str) -> str:
ans = fname.rsplit('.', 1)[0]
ans = ans.replace('_', ' ')
def camel_case(m: Match) -> str:
return str(m.group(1) + ' ' + m.group(2))
ans = re.sub(r'([a-z])([A-Z])', camel_case, ans)
return ' '.join(x.capitalize() for x in filter(None, ans.split()))
class LineParser:
def __init__(self) -> None:
self.in_metadata = False
self.in_blurb = False
self.keep_going = True
def __call__(self, line: str, ans: Dict[str, Any]) -> None:
is_block = line.startswith('## ')
if self.in_metadata and not is_block:
self.keep_going = False
return
if not self.in_metadata and is_block:
self.in_metadata = True
if not self.in_metadata:
return
line = line[3:]
if self.in_blurb:
ans['blurb'] += ' ' + line
return
try:
key, val = line.split(':', 1)
except Exception:
self.keep_going = False
return
key = key.strip().lower()
val = val.strip()
if val:
ans[key] = val
if key == 'blurb':
self.in_blurb = True
def parse_theme(fname: str, raw: str, exc_class: Type[BaseException] = SystemExit) -> Dict[str, Any]:
lines = raw.splitlines()
conf = parse_config(lines)
bg = conf.get('background', Color())
is_dark = max(bg) < 115
ans: Dict[str, Any] = {'name': theme_name_from_file_name(fname)}
parser = LineParser()
for i, line in enumerate(raw.splitlines()):
line = line.strip()
if not line:
continue
try:
parser(line, ans)
except Exception as e:
raise exc_class(
f'Failed to parse {fname} line {i+1} with error: {e}')
if not parser.keep_going:
break
if is_dark:
ans['is_dark'] = True
ans['num_settings'] = len(conf) - len(parse_config(()))
if ans['num_settings'] < 1 and fname != 'default.conf':
raise exc_class(f'The theme {fname} has no settings')
return ans
def update_theme_file(path: str) -> bool:
with open(path) as f:
raw = f.read()
td = parse_theme(os.path.basename(path), raw, exc_class=ValueError)
if 'upstream' not in td:
return False
nraw = urlopen(td['upstream']).read().decode('utf-8')
if raw == nraw:
return False
atomic_save(nraw.encode('utf-8'), path)
return True
class Theme:
name: str = ''
author: str = ''
license: str = ''
is_dark: bool = False
blurb: str = ''
num_settings: int = 0
def apply_dict(self, d: Dict[str, Any]) -> None:
self.name = str(d['name'])
for x in ('author', 'license', 'blurb'):
a = d.get(x)
if isinstance(a, str):
setattr(self, x, a)
for x in ('is_dark', 'num_settings'):
a = d.get(x)
if isinstance(a, int):
setattr(self, x, a)
def __init__(self, loader: Callable[[], str]):
self._loader = loader
self._raw: Optional[str] = None
self._opts: Optional[KittyOptions] = None
@property
def raw(self) -> str:
if self._raw is None:
self._raw = self._loader()
return self._raw
@property
def kitty_opts(self) -> KittyOptions:
if self._opts is None:
self._opts = KittyOptions(options_dict=parse_config(self.raw.splitlines()))
return self._opts
def save_in_dir(self, dirpath: str) -> None:
atomic_save(self.raw.encode('utf-8'), os.path.join(dirpath, f'{self.name}.conf'))
def save_in_conf(self, confdir: str, reload_in: str) -> None:
atomic_save(self.raw.encode('utf-8'), os.path.join(confdir, 'current-theme.conf'))
confpath = os.path.realpath(os.path.join(confdir, 'kitty.conf'))
try:
with open(confpath) as f:
raw = f.read()
except FileNotFoundError:
raw = ''
nraw = patch_conf(raw, self.name)
if raw:
with open(confpath + '.bak', 'w') as f:
f.write(raw)
atomic_save(nraw.encode('utf-8'), confpath)
if reload_in == 'parent':
if 'KITTY_PID' in os.environ:
os.kill(int(os.environ['KITTY_PID']), signal.SIGUSR1)
elif reload_in == 'all':
reload_conf_in_all_kitties()
class Themes:
def __init__(self) -> None:
self.themes: Dict[str, Theme] = {}
self.index_map: Tuple[str, ...] = ()
def __len__(self) -> int:
return len(self.themes)
def __iter__(self) -> Iterator[Theme]:
return iter(self.themes.values())
def __getitem__(self, key: Union[int, str]) -> Theme:
if isinstance(key, str):
return self.themes[key]
if key < 0:
key += len(self.index_map)
return self.themes[self.index_map[key]]
def load_from_zip(self, path_to_zip: str) -> None:
with zipfile.ZipFile(path_to_zip, 'r') as zf:
for name in zf.namelist():
if os.path.basename(name) == 'themes.json':
theme_file_name = name
with zf.open(theme_file_name) as f:
items = json.loads(f.read())
break
else:
raise ValueError(f'No themes.json found in {path_to_zip}')
for item in items:
t = Theme(zip_file_loader(path_to_zip, theme_file_name, item['file']))
t.apply_dict(item)
if t.name:
self.themes[t.name] = t
def load_from_dir(self, path: str) -> None:
if not os.path.isdir(path):
return
for name in os.listdir(path):
if name.endswith('.conf'):
with open(os.path.join(path, name), 'rb') as f:
raw = f.read().decode()
try:
d = parse_theme(name, raw)
except (Exception, SystemExit):
continue
t = Theme(raw.__str__)
t.apply_dict(d)
if t.name:
self.themes[t.name] = t
def filtered(self, is_ok: Callable[[Theme], bool]) -> 'Themes':
ans = Themes()
def sort_key(k: Tuple[str, Theme]) -> str:
return k[1].name.lower()
ans.themes = {k: v for k, v in sorted(self.themes.items(), key=sort_key) if is_ok(v)}
ans.index_map = tuple(ans.themes)
return ans
def copy(self) -> 'Themes':
ans = Themes()
ans.themes = self.themes.copy()
ans.index_map = self.index_map
return ans
def apply_search(
self, expression: str, mark_before: str = MARK_BEFORE, mark_after: str = MARK_AFTER
) -> Iterator[str]:
raw = '\n'.join(self.themes)
results = match(raw, expression, positions=True, level1=' ')
themes: Dict[str, Theme] = {}
for r in results:
pos, k = r.split(':', 1)
positions = tuple(map(int, pos.split(',')))
text = k
for p in reversed(positions):
text = text[:p] + mark_before + text[p] + mark_after + text[p+1:]
themes[k] = self.themes[k]
yield text
self.themes = themes
self.index_map = tuple(self.themes)
def load_themes(cache_age: float = 1., ignore_no_cache: bool = False) -> Themes:
ans = Themes()
try:
fetched = fetch_themes(cache_age=cache_age)
except NoCacheFound:
if not ignore_no_cache:
raise
ans.load_from_zip(fetched)
ans.load_from_dir(os.path.join(config_dir, 'themes'))
ans.index_map = tuple(ans.themes)
return ans

594
kittens/themes/main.py Normal file
View File

@@ -0,0 +1,594 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import sys
import traceback
from enum import Enum, auto
from gettext import gettext as _
from typing import (
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
)
from kitty.cli import create_default_opts, parse_args
from kitty.cli_stub import ThemesCLIOptions
from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.rgb import color_as_sharp, color_from_int
from kitty.typing import KeyEventType
from kitty.utils import ScreenSize
from ..tui.handler import Handler
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop
from ..tui.operations import color_code, styled
from .collection import MARK_AFTER, NoCacheFound, Theme, Themes, load_themes
separator = ''
def format_traceback(msg: str) -> str:
return traceback.format_exc() + '\n\n' + styled(msg, fg='red')
def limit_length(text: str, limit: int = 32) -> str:
x = truncate_point_for_length(text, limit - 1)
if x >= len(text):
return text
return text[:x] + ''
class State(Enum):
fetching = auto()
browsing = auto()
searching = auto()
accepting = auto()
def dark_filter(q: Theme) -> bool:
return q.is_dark
def light_filter(q: Theme) -> bool:
return not q.is_dark
def all_filter(q: Theme) -> bool:
return True
def create_recent_filter(names: Iterable[str]) -> Callable[[Theme], bool]:
allowed = frozenset(names)
def recent_filter(q: Theme) -> bool:
return q.name in allowed
return recent_filter
def mark_shortcut(text: str, acc: str) -> str:
acc_idx = text.lower().index(acc.lower())
return text[:acc_idx] + styled(text[acc_idx], underline='straight', bold=True, fg_intense=True) + text[acc_idx+1:]
class ThemesList:
def __init__(self) -> None:
self.themes = Themes()
self.current_search: str = ''
self.display_strings: Tuple[str, ...] = ()
self.widths: Tuple[int, ...] = ()
self.max_width = 0
self.current_idx = 0
def __bool__(self) -> bool:
return bool(self.display_strings)
def __len__(self) -> int:
return len(self.themes)
def next(self, delta: int = 1, allow_wrapping: bool = True) -> bool:
if not self:
return False
idx = self.current_idx + delta
if not allow_wrapping and (idx < 0 or idx >= len(self)):
return False
while idx < 0:
idx += len(self)
self.current_idx = idx % len(self)
return True
def update_themes(self, themes: Themes) -> None:
self.themes = self.all_themes = themes
if self.current_search:
self.themes = self.all_themes.copy()
self.display_strings = tuple(map(limit_length, self.themes.apply_search(self.current_search)))
else:
self.display_strings = tuple(map(limit_length, (t.name for t in self.themes)))
self.widths = tuple(map(wcswidth, self.display_strings))
self.max_width = max(self.widths) if self.widths else 0
self.current_idx = 0
def update_search(self, search: str = '') -> bool:
if search == self.current_search:
return False
self.current_search = search
self.update_themes(self.all_themes)
return True
def lines(self, num_rows: int) -> Iterator[Tuple[str, int, bool]]:
if num_rows < 1:
return
before_num = min(self.current_idx, num_rows - 1)
start = self.current_idx - before_num
for i in range(start, min(start + num_rows, len(self.display_strings))):
line = self.display_strings[i]
yield line, self.widths[i], i == self.current_idx
@property
def current_theme(self) -> Theme:
return self.themes[self.current_idx]
class ThemesHandler(Handler):
def __init__(self, cached_values: Dict[str, Any], cli_opts: ThemesCLIOptions) -> None:
self.cached_values = cached_values
self.cli_opts = cli_opts
self.state = State.fetching
self.report_traceback_on_exit: Optional[str] = None
self.filter_map: Dict[str, Callable[[Theme], bool]] = {
'dark': dark_filter, 'light': light_filter, 'all': all_filter,
'recent': create_recent_filter(self.cached_values.get('recent', ()))
}
self.themes_list = ThemesList()
self.colors_set_once = False
self.line_edit = LineEdit()
self.tabs = tuple('all dark light recent'.split())
def update_recent(self) -> None:
r = list(self.cached_values.get('recent', ()))
if self.themes_list:
name = self.themes_list.current_theme.name
r = [name] + [x for x in r if x != name]
self.cached_values['recent'] = r[:20]
def enforce_cursor_state(self) -> None:
self.cmd.set_cursor_visible(self.state == State.fetching)
def init_terminal_state(self) -> None:
self.cmd.save_colors()
self.cmd.set_line_wrapping(False)
self.cmd.set_window_title('Choose a theme for kitty')
self.cmd.set_cursor_shape('bar')
def initialize(self) -> None:
self.init_terminal_state()
self.draw_screen()
self.fetch_themes()
def finalize(self) -> None:
self.cmd.restore_colors()
self.cmd.set_cursor_visible(True)
@property
def current_category(self) -> str:
cat: str = self.cached_values.get('category', 'all')
if cat not in self.filter_map:
cat = 'all'
return cat
@current_category.setter
def current_category(self, cat: str) -> None:
if cat not in self.filter_map:
cat = 'all'
self.cached_values['category'] = cat
def set_colors_to_current_theme(self) -> bool:
if not self.themes_list and self.colors_set_once:
return False
self.colors_set_once = True
if self.themes_list:
o = self.themes_list.current_theme.kitty_opts
else:
o = create_default_opts()
self.cmd.set_default_colors(
fg=o.foreground, bg=o.background, cursor=o.cursor, select_bg=o.selection_background, select_fg=o.selection_foreground
)
self.current_opts = o
cmds = []
for i in range(256):
col = color_as_sharp(color_from_int(o.color_table[i]))
cmds.append(f'{i};{col}')
self.print(end='\033]4;' + ';'.join(cmds) + '\033\\')
def redraw_after_category_change(self) -> None:
self.themes_list.update_themes(self.all_themes.filtered(self.filter_map[self.current_category]))
self.set_colors_to_current_theme()
self.draw_screen()
# Theme fetching {{{
def fetch_themes(self) -> None:
def fetching_done(themes_or_exception: Union[Themes, str]) -> None:
if isinstance(themes_or_exception, str):
self.report_traceback_on_exit = themes_or_exception
self.quit_loop(1)
return
self.all_themes: Themes = themes_or_exception
self.state = State.browsing
self.redraw_after_category_change()
def fetch() -> None:
try:
themes: Union[Themes, str] = load_themes(self.cli_opts.cache_age)
except Exception:
themes = format_traceback('Failed to download themes')
self.asyncio_loop.call_soon_threadsafe(fetching_done, themes)
self.asyncio_loop.run_in_executor(None, fetch)
self.draw_screen()
def draw_fetching_screen(self) -> None:
self.print('Downloading themes from repository, please wait...')
def on_fetching_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('esc'):
self.quit_loop(0)
# }}}
# Theme browsing {{{
def draw_tab_bar(self) -> None:
self.print(styled(' ' * self.screen_size.cols, reverse=True), end='\r')
def draw_tab(text: str, name: str, acc: str) -> None:
is_active = name == self.current_category
if is_active:
text = styled(f'{text} #{len(self.themes_list)}', italic=True)
else:
text = mark_shortcut(text, acc)
self.cmd.styled(f' {text} ', reverse=not is_active)
for t in self.tabs:
draw_tab(t.capitalize(), t, t[0])
self.cmd.sgr('0')
self.print()
def draw_bottom_bar(self) -> None:
self.cmd.set_cursor_position(0, self.screen_size.rows)
self.print(styled(' ' * self.screen_size.cols, reverse=True), end='\r')
for (t, sc) in (('search (/)', 's'), ('accept (⏎)', 'c')):
text = mark_shortcut(t.capitalize(), sc)
self.cmd.styled(f' {text} ', reverse=True)
self.cmd.sgr('0')
def draw_search_bar(self) -> None:
self.cmd.set_cursor_position(0, self.screen_size.rows)
self.cmd.clear_to_eol()
self.line_edit.write(self.write)
def draw_theme_demo(self) -> None:
theme = self.themes_list.current_theme
xstart = self.themes_list.max_width + 3
sz = self.screen_size.cols - xstart
if sz < 20:
return
sz -= 1
y = 0
colors = 'black red green yellow blue magenta cyan white'.split()
trunc = sz // 8 - 1
def next_line() -> None:
nonlocal y
self.write('\r')
y += 1
self.cmd.set_cursor_position(xstart - 1, y)
self.write(separator + ' ')
def write_para(text: str) -> None:
text = re.sub(r'\s+', ' ', text)
while text:
sp = truncate_point_for_length(text, sz)
self.write(text[:sp])
next_line()
text = text[sp:]
def write_colors(bg: Optional[str] = None) -> None:
for intense in (False, True):
buf = []
for c in colors:
buf.append(styled(c[:trunc], fg=c, fg_intense=intense))
self.cmd.styled(' '.join(buf), bg=bg, bg_intense=intense)
next_line()
next_line()
def write_line(text: str) -> None:
self.write(text)
next_line()
self.cmd.set_cursor_position()
next_line()
self.cmd.styled(theme.name.center(sz), bold=True, fg='green')
next_line()
if theme.author:
self.cmd.styled(theme.author.center(sz), italic=True)
next_line()
if theme.blurb:
next_line()
write_para(theme.blurb)
next_line()
write_colors()
for bg in colors:
write_colors(bg)
def draw_browsing_screen(self) -> None:
self.draw_tab_bar()
num_rows = self.screen_size.rows - 2
mw = self.themes_list.max_width + 1
for line, width, is_current in self.themes_list.lines(num_rows):
num_rows -= 1
if is_current:
line = line.replace(MARK_AFTER, '\033[' + color_code('green') + 'm')
self.cmd.styled('>' if is_current else ' ', fg='green')
self.cmd.styled(line, bold=is_current, fg='green' if is_current else None)
self.cmd.move_cursor_by(mw - width, 'right')
self.print(separator)
if self.themes_list:
self.draw_theme_demo()
self.draw_bottom_bar() if self.state is State.browsing else self.draw_search_bar()
def on_searching_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('enter'):
self.state = State.browsing
self.draw_bottom_bar()
return
if key_event.matches('esc'):
self.state = State.browsing
self.themes_list.update_search('')
self.set_colors_to_current_theme()
return self.draw_screen()
if key_event.text:
self.line_edit.on_text(key_event.text, in_bracketed_paste)
else:
if not self.line_edit.on_key(key_event):
if key_event.matches('left') or key_event.matches('shift+tab'):
return self.next_category(-1)
if key_event.matches('right') or key_event.matches('tab'):
return self.next_category(1)
if key_event.matches('down'):
return self.next(delta=1)
if key_event.matches('up'):
return self.next(delta=-1)
if key_event.matches('page_down'):
return self.next(delta=self.screen_size.rows - 3, allow_wrapping=False)
if key_event.matches('page_up'):
return self.next(delta=3 - self.screen_size.rows, allow_wrapping=False)
return
if self.line_edit.current_input:
q = self.line_edit.current_input[1:]
if self.themes_list.update_search(q):
self.set_colors_to_current_theme()
self.draw_screen()
else:
self.draw_search_bar()
else:
self.state = State.browsing
self.draw_bottom_bar()
def on_browsing_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('esc') or key_event.matches('q'):
return self.quit_loop(0)
for cat in 'all dark light recent'.split():
if key_event.matches(cat[0]) or key_event.matches(f'alt+{cat[0]}'):
if cat != self.current_category:
self.current_category = cat
self.redraw_after_category_change()
return
if key_event.matches('left') or key_event.matches('shift+tab'):
return self.next_category(-1)
if key_event.matches('right') or key_event.matches('tab'):
return self.next_category(1)
if key_event.matches('j') or key_event.matches('down'):
return self.next(delta=1)
if key_event.matches('k') or key_event.matches('up'):
return self.next(delta=-1)
if key_event.matches('page_down'):
return self.next(delta=self.screen_size.rows - 3, allow_wrapping=False)
if key_event.matches('page_up'):
return self.next(delta=3 - self.screen_size.rows, allow_wrapping=False)
if key_event.matches('s') or key_event.matches('/'):
return self.start_search()
if key_event.matches('c') or key_event.matches('enter'):
if not self.themes_list:
self.cmd.beep()
return
self.state = State.accepting
return self.draw_screen()
def start_search(self) -> None:
self.line_edit.clear()
self.line_edit.add_text('/' + self.themes_list.current_search)
self.state = State.searching
self.draw_screen()
def next_category(self, delta: int = 1) -> None:
idx = self.tabs.index(self.current_category) + delta + len(self.tabs)
self.current_category = self.tabs[idx % len(self.tabs)]
self.redraw_after_category_change()
def next(self, delta: int = 1, allow_wrapping: bool = True) -> None:
if self.themes_list.next(delta, allow_wrapping):
self.set_colors_to_current_theme()
self.draw_screen()
else:
self.cmd.bell()
# }}}
# Accepting {{{
def draw_accepting_screen(self) -> None:
name = self.themes_list.current_theme.name
name = styled(name, bold=True, fg="green")
kc = styled('kitty.conf', italic=True)
def ac(x: str) -> str:
return styled(x, fg='red')
self.cmd.set_line_wrapping(True)
self.print(f'You have chosen the {name} theme')
self.print()
self.print('What would you like to do?')
self.print()
self.print(' ', f'{ac("M")}odify {kc} to load', styled(name, bold=True, fg="green"))
self.print()
self.print(' ', f'{ac("P")}lace the theme file in {config_dir} but do not modify {kc}')
self.print()
self.print(' ', f'{ac("A")}bort and return to list of themes')
self.print()
self.print(' ', f'{ac("Q")}uit')
def on_accepting_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('q') or key_event.matches('esc'):
self.quit_loop(0)
return
if key_event.matches('a'):
self.state = State.browsing
self.draw_screen()
return
if key_event.matches('p'):
self.themes_list.current_theme.save_in_dir(config_dir)
self.update_recent()
self.quit_loop(0)
return
if key_event.matches('m'):
self.themes_list.current_theme.save_in_conf(config_dir, self.cli_opts.reload_in)
self.update_recent()
self.quit_loop(0)
return
# }}}
def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if self.state is State.fetching:
self.on_fetching_key_event(key_event, in_bracketed_paste)
elif self.state is State.browsing:
self.on_browsing_key_event(key_event, in_bracketed_paste)
elif self.state is State.searching:
self.on_searching_key_event(key_event, in_bracketed_paste)
elif self.state is State.accepting:
self.on_accepting_key_event(key_event, in_bracketed_paste)
def draw_screen(self) -> None:
with self.pending_update():
self.cmd.clear_screen()
self.enforce_cursor_state()
self.cmd.set_line_wrapping(False)
if self.state is State.fetching:
self.draw_fetching_screen()
elif self.state in (State.browsing, State.searching):
self.draw_browsing_screen()
elif self.state is State.accepting:
self.draw_accepting_screen()
def on_resize(self, screen_size: ScreenSize) -> None:
self.screen_size = screen_size
self.draw_screen()
def on_interrupt(self) -> None:
self.quit_loop(1)
def on_eot(self) -> None:
self.quit_loop(1)
help_text = (
'Change the kitty theme. If no theme name is supplied, run interactively, otherwise'
' change the current theme to the specified theme name.'
)
usage = '[theme name to switch to]'
OPTIONS = '''
--cache-age
type=float
default=1
Check for new themes only after the specified number of days. A value of
zero will always check for new themes. A negative value will never check
for new themes, instead raising an error if a local copy of the themes
is not available.
--reload-in
default=parent
choices=none,parent,all
By default, this kitten will signal only the parent kitty instance it is
running in to reload its config, after making changes. Use this option
to instead either not reload the config at all or in all running
kitty instances.
--dump-theme
type=bool-set
default=false
When running non-interactively, dump the specified theme to STDOUT
instead of changing kitty.conf.
'''.format
def parse_themes_args(args: List[str]) -> Tuple[ThemesCLIOptions, List[str]]:
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten themes', result_class=ThemesCLIOptions)
def non_interactive(cli_opts: ThemesCLIOptions, theme_name: str) -> None:
try:
themes = load_themes(cli_opts.cache_age)
except NoCacheFound as e:
raise SystemExit(str(e))
try:
theme = themes[theme_name]
except KeyError:
raise SystemExit(f'No theme named: {theme_name}')
if cli_opts.dump_theme:
print(theme.raw)
return
theme.save_in_conf(config_dir, cli_opts.reload_in)
def main(args: List[str]) -> None:
try:
cli_opts, items = parse_themes_args(args[1:])
except SystemExit as e:
if e.code != 0:
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return None
if len(items) > 1:
items = [' '.join(items)]
if len(items) == 1:
return non_interactive(cli_opts, items[0])
loop = Loop()
with cached_values_for('themes-kitten') as cached_values:
handler = ThemesHandler(cached_values, cli_opts)
loop.loop(handler)
if loop.return_code != 0:
if handler.report_traceback_on_exit:
print(handler.report_traceback_on_exit, file=sys.stderr)
input('Press Enter to quit.')
if handler.state is State.fetching:
# asycio uses non-daemonic threads in its ThreadPoolExecutor
# so we will hang here till the download completes without
# os._exit
os._exit(loop.return_code)
raise SystemExit(loop.return_code)
if __name__ == '__main__':
main(sys.argv)
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text

View File

@@ -10,10 +10,12 @@ from typing import (
from kitty.types import ParsedShortcut
from kitty.typing import (
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType,
KeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyActionType,
KeyEventType, LoopType, MouseEvent, ScreenSize, TermManagerType
)
from .operations import pending_update
class Handler:
@@ -134,6 +136,9 @@ class Handler:
def suspend(self) -> ContextManager[TermManagerType]:
return self._term_manager.suspend()
def pending_update(self) -> ContextManager[None]:
return pending_update(self.write)
class HandleResult:

View File

@@ -5,6 +5,7 @@
import sys
from contextlib import contextmanager
from functools import wraps
from enum import Enum
from typing import (
IO, Any, Callable, Dict, Generator, Optional, Tuple, TypeVar, Union
)
@@ -22,44 +23,45 @@ SAVE_PRIVATE_MODE_VALUES = '\033[?s'
RESTORE_PRIVATE_MODE_VALUES = '\033[?r'
SAVE_COLORS = '\033[#P'
RESTORE_COLORS = '\033[#Q'
MODES = dict(
LNM=(20, ''),
IRM=(4, ''),
DECKM=(1, '?'),
DECSCNM=(5, '?'),
DECOM=(6, '?'),
DECAWM=(7, '?'),
DECARM=(8, '?'),
DECTCEM=(25, '?'),
MOUSE_BUTTON_TRACKING=(1000, '?'),
MOUSE_MOTION_TRACKING=(1002, '?'),
MOUSE_MOVE_TRACKING=(1003, '?'),
FOCUS_TRACKING=(1004, '?'),
MOUSE_UTF8_MODE=(1005, '?'),
MOUSE_SGR_MODE=(1006, '?'),
MOUSE_URXVT_MODE=(1015, '?'),
ALTERNATE_SCREEN=(1049, '?'),
BRACKETED_PASTE=(2004, '?'),
)
F = TypeVar('F')
all_cmds: Dict[str, Callable] = {}
class Mode(Enum):
LNM = 20, ''
IRM = 4, ''
DECKM = 1, '?'
DECSCNM = 5, '?'
DECOM = 6, '?'
DECAWM = 7, '?'
DECARM = 8, '?'
DECTCEM = 25, '?'
MOUSE_BUTTON_TRACKING = 1000, '?'
MOUSE_MOTION_TRACKING = 1002, '?'
MOUSE_MOVE_TRACKING = 1003, '?'
FOCUS_TRACKING = 1004, '?'
MOUSE_UTF8_MODE = 1005, '?'
MOUSE_SGR_MODE = 1006, '?'
MOUSE_URXVT_MODE = 1015, '?'
ALTERNATE_SCREEN = 1049, '?'
BRACKETED_PASTE = 2004, '?'
PENDING_UPDATE = 2026, '?'
def cmd(f: F) -> F:
all_cmds[f.__name__] = f # type: ignore
return f
@cmd
def set_mode(which: str, private: bool = True) -> str:
num, private_ = MODES[which]
return '\033[{}{}h'.format(private_, num)
def set_mode(which: Mode) -> str:
num, private = which.value
return '\033[{}{}h'.format(private, num)
@cmd
def reset_mode(which: str) -> str:
num, private = MODES[which]
def reset_mode(which: Mode) -> str:
num, private = which.value
return '\033[{}{}l'.format(private, num)
@@ -100,16 +102,16 @@ def set_window_title(value: str) -> str:
@cmd
def set_line_wrapping(yes_or_no: bool) -> str:
return set_mode('DECAWM') if yes_or_no else reset_mode('DECAWM')
return set_mode(Mode.DECAWM) if yes_or_no else reset_mode(Mode.DECAWM)
@cmd
def set_cursor_visible(yes_or_no: bool) -> str:
return set_mode('DECTCEM') if yes_or_no else reset_mode('DECTCEM')
return set_mode(Mode.DECTCEM) if yes_or_no else reset_mode(Mode.DECTCEM)
@cmd
def set_cursor_position(x: int, y: int) -> str: # (0, 0) is top left
def set_cursor_position(x: int = 0, y: int = 0) -> str: # (0, 0) is top left
return '\033[{};{}H'.format(y + 1, x + 1)
@@ -262,18 +264,18 @@ def clear_images_on_screen(delete_data: bool = False) -> str:
def init_state(alternate_screen: bool = True) -> str:
ans = (
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
set_mode('DECARM') + set_mode('DECAWM') +
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
set_mode('BRACKETED_PASTE') + SAVE_COLORS +
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode(Mode.LNM) +
reset_mode(Mode.IRM) + reset_mode(Mode.DECKM) + reset_mode(Mode.DECSCNM) +
set_mode(Mode.DECARM) + set_mode(Mode.DECAWM) +
set_mode(Mode.DECTCEM) + reset_mode(Mode.MOUSE_BUTTON_TRACKING) +
reset_mode(Mode.MOUSE_MOTION_TRACKING) + reset_mode(Mode.MOUSE_MOVE_TRACKING) +
reset_mode(Mode.FOCUS_TRACKING) + reset_mode(Mode.MOUSE_UTF8_MODE) +
reset_mode(Mode.MOUSE_SGR_MODE) + reset_mode(Mode.MOUSE_UTF8_MODE) +
set_mode(Mode.BRACKETED_PASTE) + SAVE_COLORS +
'\033[*x' # reset DECSACE to default region select
)
if alternate_screen:
ans += set_mode('ALTERNATE_SCREEN') + reset_mode('DECOM')
ans += set_mode(Mode.ALTERNATE_SCREEN) + reset_mode(Mode.DECOM)
ans += clear_screen()
ans += '\033[>31u' # extended keyboard mode
return ans
@@ -283,13 +285,20 @@ def reset_state(normal_screen: bool = True) -> str:
ans = ''
ans += '\033[<u' # restore keyboard mode
if normal_screen:
ans += reset_mode('ALTERNATE_SCREEN')
ans += reset_mode(Mode.ALTERNATE_SCREEN)
ans += RESTORE_PRIVATE_MODE_VALUES
ans += RESTORE_CURSOR
ans += RESTORE_COLORS
return ans
@contextmanager
def pending_update(write: Callable[[str], None]) -> Generator[None, None, None]:
write(set_mode(Mode.PENDING_UPDATE))
yield
write(reset_mode(Mode.PENDING_UPDATE))
@contextmanager
def cursor(write: Callable[[str], None]) -> Generator[None, None, None]:
write(SAVE_CURSOR)
@@ -300,9 +309,9 @@ def cursor(write: Callable[[str], None]) -> Generator[None, None, None]:
@contextmanager
def alternate_screen(f: Optional[IO[str]] = None) -> Generator[None, None, None]:
f = f or sys.stdout
print(set_mode('ALTERNATE_SCREEN'), end='', file=f)
print(set_mode(Mode.ALTERNATE_SCREEN), end='', file=f)
yield
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
print(reset_mode(Mode.ALTERNATE_SCREEN), end='', file=f)
@contextmanager
@@ -350,6 +359,16 @@ def set_default_colors(
return ans
@cmd
def save_colors() -> str:
return '\x1b[#P'
@cmd
def restore_colors() -> str:
return '\x1b[#Q'
@cmd
def write_to_clipboard(data: Union[str, bytes], use_primary: bool = False) -> str:
from base64 import standard_b64encode
@@ -396,6 +415,7 @@ def as_type_stub() -> str:
'from kitty.typing import GraphicsCommandType, ScreenSize',
'from kitty.rgb import Color',
'import kitty.rgb',
'import kittens.tui.operations',
]
methods = []
for name, func in all_cmds.items():

View File

@@ -7,7 +7,7 @@ from typing import Dict, List, NamedTuple
from .boss import Boss
from .tabs import Tab
from .types import run_once
from .types import run_once, ActionGroup, ActionSpec
from .window import Window
@@ -18,7 +18,7 @@ class Action(NamedTuple):
long_help: str
groups = {
groups: Dict[ActionGroup, str] = {
'cp': 'Copy/paste',
'sc': 'Scrolling',
'win': 'Window management',
@@ -33,22 +33,21 @@ group_title = groups.__getitem__
@run_once
def get_all_actions() -> Dict[str, List[Action]]:
' test docstring '
ans: Dict[str, List[Action]] = {}
def is_action(x: object) -> bool:
doc = getattr(x, '__doc__', '')
return bool(doc and doc.strip().startswith('@ac:'))
return isinstance(getattr(x, 'action_spec', None), ActionSpec)
def as_action(x: object) -> Action:
doc = inspect.cleandoc(x.__doc__ or '')
spec: ActionSpec = getattr(x, 'action_spec')
doc = inspect.cleandoc(spec.doc)
lines = doc.splitlines()
first = lines.pop(0)
parts = first.split(':', 2)
grp = parts[1].strip()
short_help = parts[2].strip()
short_help = first
long_help = '\n'.join(lines).strip()
return Action(getattr(x, '__name__'), grp, short_help, long_help)
return Action(getattr(x, '__name__'), spec.group, short_help, long_help)
seen = set()
for cls in (Window, Tab, Boss):

View File

@@ -50,7 +50,7 @@ from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
)
from .types import SingleKey
from .types import SingleKey, ac
from .typing import PopenType, TypedDict
from .utils import (
func_name, get_editor, get_new_os_window_size, get_primary_selection,
@@ -162,7 +162,10 @@ class Boss:
talk_fd = getattr(single_instance, 'socket', None)
talk_fd = -1 if talk_fd is None else talk_fd.fileno()
listen_fd = -1
if args.listen_on and (opts.allow_remote_control in ('y', 'socket-only')):
# we dont allow reloading the config file to change
# allow_remote_control
self.allow_remote_control = opts.allow_remote_control
if args.listen_on and (self.allow_remote_control in ('y', 'socket-only')):
listen_fd = listen_on(args.listen_on)
self.child_monitor = ChildMonitor(
self.on_child_death,
@@ -213,7 +216,7 @@ class Boss:
os_window_id = create_os_window(
initial_window_size_func(size_data, self.cached_values),
pre_show_callback,
self.args.title or appname, wname, wclass)
self.args.title or appname, wname, wclass, disallow_override_title=bool(self.args.title))
else:
wname = self.args.name or self.args.cls or appname
wclass = self.args.cls or appname
@@ -264,6 +267,17 @@ class Boss:
if w is not None:
yield w
return
if field == 'recent':
tab = self.active_tab
if tab is not None:
try:
num = int(exp)
except Exception:
return
w = self.window_id_map.get(tab.nth_active_window_id(num))
if w is not None:
yield w
return
if field != 'env':
pat: MatchPatternType = re.compile(exp)
else:
@@ -307,6 +321,17 @@ class Boss:
idx = (int(pat.pattern) + len(tm.tabs)) % len(tm.tabs)
found = True
yield tm.tabs[idx]
elif field == 'recent':
tm = self.active_tab_manager
if tm is not None and len(tm.tabs) > 0:
try:
num = int(exp)
except Exception:
return
q = tm.nth_active_tab(num)
if q is not None:
found = True
yield q
if not found:
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
for q in tabs:
@@ -333,8 +358,8 @@ class Boss:
startup_session = next(create_sessions(get_options(), special_window=sw, cwd_from=cwd_from))
return self.add_os_window(startup_session)
@ac('win', 'New OS Window')
def new_os_window(self, *args: str) -> None:
'@ac:win: New OS Window'
self._new_os_window(args)
@property
@@ -343,8 +368,8 @@ class Boss:
if t is not None:
return t.active_window_for_cwd
@ac('win', 'New OS Window with the same working directory as the currently active window')
def new_os_window_with_cwd(self, *args: str) -> None:
'@ac:win: New OS Window with the same working directory as the currently active window'
w = self.active_window_for_cwd
cwd_from = w.child.pid_for_cwd if w is not None else None
self._new_os_window(args, cwd_from)
@@ -362,7 +387,7 @@ class Boss:
from .remote_control import handle_cmd
response = None
window = window or None
if get_options().allow_remote_control == 'y' or from_peer or getattr(window, 'allow_remote_control', False):
if self.allow_remote_control == 'y' or from_peer or getattr(window, 'allow_remote_control', False):
try:
response = handle_cmd(self, window, cmd)
except Exception as err:
@@ -380,16 +405,16 @@ class Boss:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
return response
def remote_control(self, *args: str) -> None:
'''
@ac:misc: Run a remote control command
@ac('misc', '''
Run a remote control command
For example::
map F1 remote_control set-spacing margin=30
See :ref:`rc_mapping` for details.
'''
''')
def remote_control(self, *args: str) -> None:
from .rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli
)
@@ -501,8 +526,8 @@ class Boss:
if window:
self.child_monitor.mark_for_close(window.id)
@ac('tab', 'Close the current tab')
def close_tab(self, tab: Optional[Tab] = None) -> None:
'@ac:tab: Close the current tab'
tab = tab or self.active_tab
if tab:
self.confirm_tab_close(tab)
@@ -534,12 +559,12 @@ class Boss:
for window in tab:
self.close_window(window)
@ac('win', 'Toggle the fullscreen status of the active OS Window')
def toggle_fullscreen(self, os_window_id: int = 0) -> None:
'@ac:win: Toggle the fullscreen status of the active OS Window'
toggle_fullscreen(os_window_id)
@ac('win', 'Toggle the maximized status of the active OS Window')
def toggle_maximized(self, os_window_id: int = 0) -> None:
'@ac:win: Toggle the maximized status of the active OS Window'
toggle_maximized(os_window_id)
def start(self, first_os_window_id: int) -> None:
@@ -566,9 +591,8 @@ class Boss:
if tm is not None:
tm.resize()
def clear_terminal(self, action: str, only_active: bool) -> None:
'''
@ac:misc: Clear the terminal
@ac('misc', '''
Clear the terminal
See :sc:`reset_terminal` for details. For example::
@@ -581,7 +605,8 @@ class Boss:
# Scroll the contents of the screen into the scrollback
map kitty_mod+f12 clear_terminal scroll active
'''
''')
def clear_terminal(self, action: str, only_active: bool) -> None:
if only_active:
windows = []
w = self.active_window
@@ -615,12 +640,12 @@ class Boss:
def set_font_size(self, new_size: float) -> None: # legacy
self.change_font_size(True, None, new_size)
def change_font_size(self, all_windows: bool, increment_operation: Optional[str], amt: float) -> None:
'''
@ac:win: Change the font size for the current or all OS Windows
@ac('win', '''
Change the font size for the current or all OS Windows
See :ref:`conf-kitty-shortcuts.fonts` for details.
'''
''')
def change_font_size(self, all_windows: bool, increment_operation: Optional[str], amt: float) -> None:
def calc_new_size(old_size: float) -> float:
new_size = old_size
if amt == 0:
@@ -676,16 +701,16 @@ class Boss:
def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None:
change_background_opacity(os_window_id, max(0.1, min(opacity, 1.0)))
def set_background_opacity(self, opacity: str) -> None:
'''
@ac:win: Set the background opacity for the active OS Window
@ac('win', '''
Set the background opacity for the active OS Window
For example::
map f1 set_background_opacity +0.1
map f2 set_background_opacity -0.1
map f3 set_background_opacity 0.5
'''
''')
def set_background_opacity(self, opacity: str) -> None:
window = self.active_window
if window is None or not opacity:
return
@@ -761,12 +786,12 @@ class Boss:
if matched_action is not None:
self.dispatch_action(matched_action)
def start_resizing_window(self) -> None:
'''
@ac:win: Resize the active window interactively
@ac('win', '''
Resize the active window interactively
See :ref:`window_resizing` for details.
'''
''')
def start_resizing_window(self) -> None:
w = self.active_window
if w is None:
return
@@ -843,9 +868,8 @@ class Boss:
return True
return False
def combine(self, *actions: KeyAction) -> None:
'''
@ac:misc: Combine multiple actions and map to a single keypress
@ac('misc', '''
Combine multiple actions and map to a single keypress
The syntax is::
@@ -854,7 +878,8 @@ class Boss:
For example::
map kitty_mod+e combine : new_window : next_layout
'''
''')
def combine(self, *actions: KeyAction) -> None:
for key_action in actions:
self.dispatch_action(key_action)
@@ -889,8 +914,8 @@ class Boss:
text = '\n'.join(parse_uri_list(text))
w.paste(text)
@ac('win', 'Close the currently active OS Window')
def close_os_window(self) -> None:
'@ac:win: Close the currently active OS Window'
tm = self.active_tab_manager
if tm is not None:
self.confirm_os_window_close(tm.os_window_id)
@@ -929,8 +954,8 @@ class Boss:
if action is not None:
action()
@ac('win', 'Quit, closing all windows')
def quit(self, *args: Any) -> None:
'@ac:win: Quit, closing all windows'
tm = self.active_tab
num = 0
for q in self.os_window_map.values():
@@ -990,8 +1015,8 @@ class Boss:
copy_colors_from=self.active_window
)
@ac('misc', 'Edit the kitty.conf config file in your favorite text editor')
def edit_config_file(self, *a: Any) -> None:
'@ac:misc: Edit the kitty.conf config file in your favorite text editor'
confpath = prepare_config_file_for_editing()
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
# small delay.
@@ -1081,8 +1106,8 @@ class Boss:
overlay_window.action_on_removal = callback_wrapper
return overlay_window
@ac('misc', 'Run the specified kitten. See :doc:`/kittens/custom` for details')
def kitten(self, kitten: str, *args: str) -> None:
'@ac:misc: Run the specified kitten. See :doc:`/kittens/custom` for details'
import shlex
cmdline = args[0] if args else ''
kargs = shlex.split(cmdline) if cmdline else []
@@ -1098,12 +1123,12 @@ class Boss:
if data is not None:
end_kitten(data, target_window_id, self)
@ac('misc', 'Input an arbitrary unicode character. See :doc:`/kittens/unicode-input` for details.')
def input_unicode_character(self) -> None:
'@ac:misc: Input an arbitrary unicode character. See :doc:`/kittens/unicode-input` for details.'
self._run_kitten('unicode_input')
@ac('tab', 'Change the title of the active tab')
def set_tab_title(self) -> None:
'@ac:tab: Change the title of the active tab'
tab = self.active_tab
if tab:
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
@@ -1121,8 +1146,8 @@ class Boss:
def show_error(self, title: str, msg: str) -> None:
self._run_kitten('show_error', args=['--title', title], input_data=msg)
@ac('mk', 'Create a new marker')
def create_marker(self) -> None:
'@ac:mk: Create a new marker'
w = self.active_window
if w:
spec = None
@@ -1145,8 +1170,8 @@ class Boss:
],
custom_callback=done, action_on_removal=done2)
@ac('misc', 'Run the kitty shell to control kitty with commands')
def kitty_shell(self, window_type: str = 'window') -> None:
'@ac:misc: Run the kitty shell to control kitty with commands'
kw: Dict[str, Any] = {}
cmd = [kitty_exe(), '@']
aw = self.active_window
@@ -1194,8 +1219,8 @@ class Boss:
if not found_action:
open_url(url, program or get_options().open_url_with, cwd=cwd)
@ac('misc', 'Click a URL using the keyboard')
def open_url_with_hints(self) -> None:
'@ac:misc: Click a URL using the keyboard'
self._run_kitten('hints')
def drain_actions(self, actions: List) -> None:
@@ -1223,8 +1248,8 @@ class Boss:
if w is not None:
w.paste(text)
@ac('cp', 'Paste from the clipboard to the active window')
def paste_from_clipboard(self) -> None:
'@ac:cp: Paste from the clipboard to the active window'
text = get_clipboard_string()
self.paste_to_active_window(text)
@@ -1234,8 +1259,8 @@ class Boss:
def current_primary_selection_or_clipboard(self) -> str:
return get_primary_selection() if supports_primary_selection else get_clipboard_string()
@ac('cp', 'Paste from the clipboard to the active window')
def paste_from_selection(self) -> None:
'@ac:cp: Paste from the clipboard to the active window'
text = self.current_primary_selection_or_clipboard()
self.paste_to_active_window(text)
@@ -1248,12 +1273,12 @@ class Boss:
if get_options().copy_on_select:
self.copy_to_buffer(get_options().copy_on_select)
def copy_to_buffer(self, buffer_name: str) -> None:
'''
@ac:cp: Copy the selection from the active window to the specified buffer
@ac('cp', '''
Copy the selection from the active window to the specified buffer
See :ref:`cpbuf` for details.
'''
''')
def copy_to_buffer(self, buffer_name: str) -> None:
w = self.active_window
if w is not None and not w.destroyed:
text = w.text_for_selection()
@@ -1265,12 +1290,12 @@ class Boss:
else:
self.clipboard_buffers[buffer_name] = text
def paste_from_buffer(self, buffer_name: str) -> None:
'''
@ac:cp: Paste from the specified buffer to the active window
@ac('cp', '''
Paste from the specified buffer to the active window
See :ref:`cpbuf` for details.
'''
''')
def paste_from_buffer(self, buffer_name: str) -> None:
if buffer_name == 'clipboard':
text: Optional[str] = get_clipboard_string()
elif buffer_name == 'primary':
@@ -1280,12 +1305,12 @@ class Boss:
if text:
self.paste_to_active_window(text)
def goto_tab(self, tab_num: int) -> None:
'''
@ac:tab: Go to the specified tab, by number, starting with 1
@ac('tab', '''
Go to the specified tab, by number, starting with 1
Zero and negative numbers go to previously active tabs
'''
''')
def goto_tab(self, tab_num: int) -> None:
tm = self.active_tab_manager
if tm is not None:
tm.goto_tab(tab_num - 1)
@@ -1296,14 +1321,14 @@ class Boss:
return tm.set_active_tab(tab)
return False
@ac('tab', 'Make the next tab active')
def next_tab(self) -> None:
'@ac:tab: Make the next tab active'
tm = self.active_tab_manager
if tm is not None:
tm.next_tab()
@ac('tab', 'Make the previous tab active')
def previous_tab(self) -> None:
'@ac:tab: Make the previous tab active'
tm = self.active_tab_manager
if tm is not None:
tm.next_tab(-1)
@@ -1462,12 +1487,12 @@ class Boss:
args = args[1:]
self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from)
@ac('tab', 'Create a new tab')
def new_tab(self, *args: str) -> None:
'@ac:tab: Create a new tab'
self._create_tab(list(args))
@ac('tab', 'Create a new tab with working directory for the window in it set to the same as the active window')
def new_tab_with_cwd(self, *args: str) -> None:
'@ac:tab: Create a new tab with working directory for the window in it set to the same as the active window'
w = self.active_window_for_cwd
cwd_from = w.child.pid_for_cwd if w is not None else None
self._create_tab(list(args), cwd_from=cwd_from)
@@ -1494,46 +1519,46 @@ class Boss:
else:
return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control)
@ac('win', 'Create a new window')
def new_window(self, *args: str) -> None:
'@ac:win: Create a new window'
self._new_window(list(args))
@ac('win', 'Create a new window with working directory same as that of the active window')
def new_window_with_cwd(self, *args: str) -> None:
'@ac:win: Create a new window with working directory same as that of the active window'
w = self.active_window_for_cwd
if w is None:
return self.new_window(*args)
cwd_from = w.child.pid_for_cwd
self._new_window(list(args), cwd_from=cwd_from)
def launch(self, *args: str) -> None:
'''
@ac:misc: Launch the specified program in a new window/tab/etc.
@ac('misc', '''
Launch the specified program in a new window/tab/etc.
See :doc:`launch` for details
'''
''')
def launch(self, *args: str) -> None:
from kitty.launch import launch, parse_launch_args
opts, args_ = parse_launch_args(args)
launch(self, opts, args_)
@ac('tab', 'Move the active tab forward')
def move_tab_forward(self) -> None:
'@ac:tab: Move the active tab forward'
tm = self.active_tab_manager
if tm is not None:
tm.move_tab(1)
@ac('tab', 'Move the active tab backward')
def move_tab_backward(self) -> None:
'@ac:tab: Move the active tab backward'
tm = self.active_tab_manager
if tm is not None:
tm.move_tab(-1)
def disable_ligatures_in(self, where: Union[str, Iterable[Window]], strategy: int) -> None:
'''
@ac:misc: Turn on/off ligatures in the specified window
@ac('misc', '''
Turn on/off ligatures in the specified window
See :opt:`disable_ligatures` for details
'''
''')
def disable_ligatures_in(self, where: Union[str, Iterable[Window]], strategy: int) -> None:
if isinstance(where, str):
windows: List[Window] = []
if where == 'active':
@@ -1590,16 +1615,16 @@ class Boss:
self.default_bg_changed_for(w.id)
w.refresh()
def load_config_file(self, *paths: str, apply_overrides: bool = True) -> None:
'''
@ac:misc: Reload the config file
@ac('misc', '''
Reload the config file
If mapped without arguments reloads the default config file, otherwise loads
the specified config files, in order. Loading a config file *replaces* all
config options. For example::
map f5 load_config_file /path/to/some/kitty.conf
'''
''')
def load_config_file(self, *paths: str, apply_overrides: bool = True) -> None:
from .config import load_config
old_opts = get_options()
paths = paths or old_opts.config_paths
@@ -1656,14 +1681,14 @@ class Boss:
msg = '\n'.join(map(format_bad_line, bad_lines)).rstrip()
self.show_error(_('Errors in kitty.conf'), msg)
def set_colors(self, *args: str) -> None:
'''
@ac:misc: Change colors in the specified windows
@ac('misc', '''
Change colors in the specified windows
For details, see :ref:`at_set-colors`. For example::
map f5 set_colors --configured /path/to/some/config/file/colors.conf
'''
''')
def set_colors(self, *args: str) -> None:
from kitty.rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli
)
@@ -1728,8 +1753,8 @@ class Boss:
self._cleanup_tab_after_window_removal(tab)
target_tab.make_active()
@ac('tab', 'Interactively select a tab to switch to')
def select_tab(self) -> None:
'@ac:tab: Interactively select a tab to switch to'
title = 'Choose a tab to switch to'
lines = [title, '']
fmt = ': {1}'
@@ -1761,12 +1786,12 @@ class Boss:
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
)
def detach_window(self, *args: str) -> None:
'''
@ac:win: Detach a window, moving it to another tab or OS Window
@ac('win', '''
Detach a window, moving it to another tab or OS Window
See :ref:`detaching windows <detach_window>` for details.
'''
''')
def detach_window(self, *args: str) -> None:
if not args or args[0] == 'new':
return self._move_window_to(target_os_window_id='new')
if args[0] in ('new-tab', 'tab-prev', 'tab-left', 'tab-right'):
@@ -1817,12 +1842,12 @@ class Boss:
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
)
def detach_tab(self, *args: str) -> None:
'''
@ac:tab: Detach a tab, moving it to another OS Window
@ac('tab', '''
Detach a tab, moving it to another OS Window
See :ref:`detaching windows <detach_window>` for details.
'''
''')
def detach_tab(self, *args: str) -> None:
if not args or args[0] == 'new':
return self._move_tab_to()
@@ -1894,8 +1919,8 @@ class Boss:
if report:
w.report_notification_activated(identifier)
@ac('misc', 'Show the environment variables that the kitty process sees')
def show_kitty_env_vars(self) -> None:
'@ac:misc: Show the environment variables that the kitty process sees'
w = self.active_window
if w:
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items())
@@ -1919,8 +1944,8 @@ class Boss:
if w is not None:
tab.remove_window(w)
@ac('misc', 'Show the effective configuration kitty is running with')
def debug_config(self) -> None:
'@ac:misc: Show the effective configuration kitty is running with'
from .debug_config import debug_config
w = self.active_window
if w is not None:
@@ -1929,7 +1954,7 @@ class Boss:
output += '\n\x1b[35mThis debug output has been copied to the clipboard\x1b[m'
self.display_scrollback(w, output, title=_('Current kitty options'))
@ac('misc', 'Discard this event completely ignoring it')
def discard_event(self) -> None:
'@ac:misc: Discard this event completely ignoring it'
pass
mouse_discard_event = discard_event

View File

@@ -111,7 +111,7 @@ static size_t reaped_pids_count = 0;
// before ticking over the main loop. Negative values mean wait forever.
static monotonic_t maximum_wait = -1;
static inline void
static void
set_maximum_wait(monotonic_t val) {
if (val >= 0 && (val < maximum_wait || maximum_wait < 0)) maximum_wait = val;
}
@@ -307,7 +307,7 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
Py_RETURN_NONE;
}
static inline bool
static bool
do_parse(ChildMonitor *self, Screen *screen, monotonic_t now) {
bool input_read = false;
screen_mutex(lock, read);
@@ -414,7 +414,7 @@ parse_input(ChildMonitor *self) {
return input_read;
}
static inline void
static void
mark_child_for_close(ChildMonitor *self, id_type window_id) {
children_mutex(lock);
for (size_t i = 0; i < self->count; i++) {
@@ -437,7 +437,7 @@ mark_for_close(ChildMonitor *self, PyObject *args) {
Py_RETURN_NONE;
}
static inline bool
static bool
pty_resize(int fd, struct winsize *dim) {
while(true) {
if (ioctl(fd, TIOCSWINSZ, dim) == -1) {
@@ -515,7 +515,7 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
extern void cocoa_update_menu_bar_title(PyObject*);
static inline void
static void
collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) {
ScreenRenderData *rd = &w->render_data;
Cursor *cursor = rd->screen->cursor;
@@ -542,7 +542,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow
ans->is_focused = os_window->is_focused;
}
static inline void
static void
change_menubar_title(PyObject *title UNUSED) {
#ifdef __APPLE__
static PyObject *current_title = NULL;
@@ -553,7 +553,7 @@ change_menubar_title(PyObject *title UNUSED) {
#endif
}
static inline bool
static 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, bool scan_for_animated_images) {
#define TD os_window->tab_bar_render_data
bool needs_render = os_window->needs_render;
@@ -614,8 +614,8 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
return needs_render;
}
static inline void
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) {
static void
render_os_window(OSWindow *os_window, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) {
// ensure all pixels are cleared to background color at least once in every buffer
if (os_window->clear_count++ < 3) blank_os_window(os_window);
Tab *tab = os_window->tabs + os_window->active_tab;
@@ -637,8 +637,7 @@ render_os_window(OSWindow *os_window, monotonic_t now, unsigned int active_windo
bool is_active_window = i == tab->active_window;
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx * x_ratio, WD.dy * y_ratio, WD.screen, os_window, is_active_window, true);
if (WD.screen->start_visual_bell_at != 0) {
monotonic_t bell_left = OPT(visual_bell_duration) - (now - WD.screen->start_visual_bell_at);
set_maximum_wait(bell_left);
set_maximum_wait(OPT(repaint_delay));
}
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;
}
@@ -664,7 +663,7 @@ draw_resizing_text(OSWindow *w) {
}
}
static inline bool
static 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 && global_state.debug_rendering) {
@@ -677,7 +676,7 @@ no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_
return ans;
}
static inline void
static void
render(monotonic_t now, bool input_read) {
EVDBG("input_read: %d, check_for_active_animated_images: %d", input_read, global_state.check_for_active_animated_images);
static monotonic_t last_render_at = MONOTONIC_T_MIN;
@@ -726,7 +725,7 @@ render(monotonic_t now, bool input_read) {
if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg, scan_for_animated_images)) needs_render = true;
if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true;
if (w->render_calls < 3 && w->bgimage && w->bgimage->texture_id) needs_render = true;
if (needs_render) render_os_window(w, now, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg);
if (needs_render) render_os_window(w, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg);
if (w->is_focused) change_menubar_title(w->window_title);
}
last_render_at = now;
@@ -736,7 +735,7 @@ render(monotonic_t now, bool input_read) {
typedef struct { int fd; uint8_t *buf; size_t sz; } ThreadWriteData;
static inline ThreadWriteData*
static ThreadWriteData*
alloc_twd(size_t sz) {
ThreadWriteData *data = calloc(1, sizeof(ThreadWriteData));
if (data != NULL) {
@@ -747,7 +746,7 @@ alloc_twd(size_t sz) {
return data;
}
static inline void
static void
free_twd(ThreadWriteData *x) {
if (x != NULL) free(x->buf);
free(x);
@@ -770,7 +769,7 @@ monitor_pid(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_NONE;
}
static inline void
static void
report_reaped_pids(void) {
children_mutex(lock);
if (reaped_pids_count) {
@@ -860,7 +859,7 @@ remove_python_timer(PyObject *self UNUSED, PyObject *args) {
}
static inline void
static void
process_pending_resizes(monotonic_t now) {
global_state.has_pending_resizes = false;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
@@ -890,7 +889,7 @@ process_pending_resizes(monotonic_t now) {
}
}
static inline void
static 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);
@@ -901,7 +900,7 @@ close_os_window(ChildMonitor *self, OSWindow *os_window) {
remove_os_window(os_window->id);
}
static inline bool
static bool
process_pending_closes(ChildMonitor *self) {
if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) {
call_boss(quit, "");
@@ -1075,7 +1074,7 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
// I/O thread functions {{{
static inline void
static void
add_children(ChildMonitor *self) {
for (; add_queue_count > 0 && self->count < MAX_CHILDREN;) {
add_queue_count--;
@@ -1088,7 +1087,7 @@ add_children(ChildMonitor *self) {
}
static inline void
static void
hangup(pid_t pid) {
errno = 0;
pid_t pgid = getpgid(pid);
@@ -1100,14 +1099,14 @@ hangup(pid_t pid) {
}
static inline void
static void
cleanup_child(ssize_t i) {
safe_close(children[i].fd, __FILE__, __LINE__);
hangup(children[i].pid);
}
static inline void
static void
remove_children(ChildMonitor *self) {
if (self->count > 0) {
size_t count = 0;
@@ -1186,7 +1185,7 @@ handle_signal(int signum, void *data) {
}
}
static inline void
static void
mark_child_for_removal(ChildMonitor *self, pid_t pid) {
children_mutex(lock);
for (size_t i = 0; i < self->count; i++) {
@@ -1198,7 +1197,7 @@ mark_child_for_removal(ChildMonitor *self, pid_t pid) {
children_mutex(unlock);
}
static inline void
static void
mark_monitored_pids(pid_t pid, int status) {
children_mutex(lock);
for (ssize_t i = monitored_pids_count - 1; i >= 0; i--) {
@@ -1213,7 +1212,7 @@ mark_monitored_pids(pid_t pid, int status) {
children_mutex(unlock);
}
static inline void
static void
reap_children(ChildMonitor *self, bool enable_close_on_child_death) {
int status;
pid_t pid;
@@ -1229,7 +1228,7 @@ reap_children(ChildMonitor *self, bool enable_close_on_child_death) {
}
}
static inline void
static void
write_to_child(int fd, Screen *screen) {
size_t written = 0;
ssize_t ret = 0;
@@ -1387,7 +1386,7 @@ typedef struct pollfd PollFD;
#define PEER_LIMIT 256
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
static inline bool
static bool
accept_peer(int listen_fd, bool shutting_down) {
int peer = accept(listen_fd, NULL, NULL);
if (UNLIKELY(peer == -1)) {
@@ -1408,7 +1407,7 @@ accept_peer(int listen_fd, bool shutting_down) {
return true;
}
static inline void
static void
free_peer(Peer *peer) {
free(peer->read.data); peer->read.data = NULL;
free(peer->write.data); peer->write.data = NULL;
@@ -1417,7 +1416,7 @@ free_peer(Peer *peer) {
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
static inline void
static void
queue_peer_message(ChildMonitor *self, Peer *peer) {
talk_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
@@ -1436,7 +1435,7 @@ queue_peer_message(ChildMonitor *self, Peer *peer) {
wakeup_main_loop();
}
static inline bool
static bool
has_complete_peer_command(Peer *peer) {
peer->read.command_end = 0;
if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) {
@@ -1451,7 +1450,7 @@ has_complete_peer_command(Peer *peer) {
}
static inline void
static void
dispatch_peer_command(ChildMonitor *self, Peer *peer) {
if (peer->read.command_end) {
size_t used = peer->read.used;
@@ -1466,7 +1465,7 @@ dispatch_peer_command(ChildMonitor *self, Peer *peer) {
}
}
static inline void
static void
read_from_peer(ChildMonitor *self, Peer *peer) {
#define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; }
if (peer->read.used >= peer->read.capacity) {
@@ -1492,7 +1491,7 @@ read_from_peer(ChildMonitor *self, Peer *peer) {
#undef failed
}
static inline void
static void
write_to_peer(Peer *peer) {
talk_mutex(lock);
ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL);
@@ -1512,7 +1511,7 @@ wakeup_talk_loop(bool in_signal_handler) {
}
static inline void
static void
prune_peers(void) {
for (size_t i = 0; i < talk_data.num_peers; i++) {
size_t idx = talk_data.num_peers - 1 - i;

View File

@@ -16,7 +16,7 @@
#include <sys/ioctl.h>
#include <termios.h>
static inline char**
static char**
serialize_string_tuple(PyObject *src) {
Py_ssize_t sz = PyTuple_GET_SIZE(src);
@@ -32,7 +32,7 @@ serialize_string_tuple(PyObject *src) {
return ans;
}
static inline void
static void
free_string_tuple(char** data) {
size_t i = 0;
while(data[i]) free(data[i++]);
@@ -41,7 +41,7 @@ free_string_tuple(char** data) {
extern char **environ;
static inline void
static void
write_to_stderr(const char *text) {
size_t sz = strlen(text);
size_t written = 0;
@@ -58,7 +58,7 @@ write_to_stderr(const char *text) {
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
static inline void
static void
wait_for_terminal_ready(int fd) {
char data;
while(1) {

View File

@@ -14,6 +14,7 @@ from typing import (
import kitty.fast_data_types as fast_data_types
from .constants import is_macos, shell_path, terminfo_dir
from .types import run_once
try:
from typing import TypedDict
@@ -23,8 +24,9 @@ except ImportError:
if is_macos:
from kitty.fast_data_types import (
cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process,
process_group_map as _process_group_map
cmdline_of_process, cwd_of_process as _cwd,
environ_of_process as _environ_of_process,
process_group_map as _process_group_map
)
def cwd_of_process(pid: int) -> str:
@@ -170,6 +172,11 @@ def openpty() -> Tuple[int, int]:
return master, slave
@run_once
def getpid() -> str:
return str(os.getpid())
class ProcessDesc(TypedDict):
cwd: Optional[str]
pid: int
@@ -216,6 +223,7 @@ class Child:
env.update(self.env)
env['TERM'] = fast_data_types.get_options().term
env['COLORTERM'] = 'truecolor'
env['KITTY_PID'] = getpid()
if self.cwd:
# needed in case cwd is a symlink, in which case shells
# can use it to display the current directory name rather

View File

@@ -596,10 +596,8 @@ Set the name part of the :italic:`WM_CLASS` property (defaults to using the valu
--title -T
Set the window title. This will override any title set by the program running inside kitty. So
only use this if you are running a program that does not set titles. If combined
with :option:`{appname} --session` the title will be used for all windows created by the
session, that do not set their own titles.
Set the OS window title. This will override any title set by the program running inside kitty, permanently
fixing the OS Window's title. So only use this if you are running a program that does not set titles.
--config -c

View File

@@ -14,6 +14,7 @@ LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOpt
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
ThemesCLIOptions = CLIOptions
def generate_stub() -> None:
@@ -72,6 +73,9 @@ def generate_stub() -> None:
from kittens.unicode_input.main import OPTIONS
do(OPTIONS(), 'UnicodeCLIOptions')
from kittens.themes.main import OPTIONS
do(OPTIONS(), 'ThemesCLIOptions')
from kitty.rc.base import all_command_names, command_for_name
for cmd_name in all_command_names():
cmd = command_for_name(cmd_name)

View File

@@ -18,7 +18,6 @@
// Needed for _NSGetProgname
#include <crt_externs.h>
#include <objc/runtime.h>
#include <xlocale.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
@@ -574,12 +573,7 @@ cocoa_get_lang(PyObject UNUSED *self) {
locale = [[NSLocale currentLocale] localeIdentifier];
}
if (!locale) { Py_RETURN_NONE; }
// Make sure the locale value is valid, that is it can be used
// to construct an actual locale
const char* locale_utf8 = [locale UTF8String];
locale_t test_locale = newlocale(LC_ALL_MASK, locale_utf8, NULL);
if (!test_locale) { Py_RETURN_NONE; }
freelocale(test_locale);
return Py_BuildValue("s", locale_utf8);
} // autoreleasepool

View File

@@ -30,7 +30,7 @@ static uint32_t FG_BG_256[256] = {
0xffffff, // 15
};
static inline void
static void
init_FG_BG_table(void) {
if (UNLIKELY(FG_BG_256[255] == 0)) {
// colors 16..232: the 6x6x6 color cube
@@ -111,7 +111,7 @@ copy_color_profile(ColorProfile *dest, ColorProfile *src) {
dest->dirty = true;
}
static inline void
static void
patch_color_table(const char *key, PyObject *profiles, PyObject *spec, size_t which, int change_configured) {
PyObject *v = PyDict_GetItemString(spec, key);
if (v) {

View File

@@ -338,15 +338,14 @@ def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix:
complete_files_and_dirs(ans, prefix, files_group_name='Watcher files')
elif dest == 'directory':
complete_files_and_dirs(ans, prefix, files_group_name='Directories', predicate=os.path.isdir)
elif dest == 'start_as':
k = 'Start as'
ans.add_match_group(k, {x: x for x in 'normal,fullscreen,maximized,minimized'.split(',') if x.startswith(prefix)}, trailing_space=False)
elif dest == 'listen_on':
if ':' not in prefix:
k = 'Address type'
ans.add_match_group(k, {x: x for x in ('unix:', 'tcp:') if x.startswith(prefix)}, trailing_space=False)
elif prefix.startswith('unix:') and not prefix.startswith('@'):
complete_files_and_dirs(ans, prefix[len('unix:'):], files_group_name='UNIX sockets', add_prefix='unix:')
else:
complete_basic_option_args(ans, opt, prefix)
CompleteArgsFunc = Callable[[Completions, Optional[OptionDict], str, Delegate], None]
@@ -474,6 +473,11 @@ def complete_files_and_dirs(
ans.add_match_group(files_group_name, files, is_files=True)
def complete_basic_option_args(ans: Completions, opt: OptionDict, prefix: str) -> None:
if opt['choices']:
ans.add_match_group(f'Choices for {opt["dest"]}', tuple(k for k in opt['choices'] if k.startswith(prefix)))
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
from .guess_mime_type import guess_type
@@ -485,6 +489,18 @@ def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str,
if opt is None:
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
else:
complete_basic_option_args(ans, opt, prefix)
def complete_themes_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
if opt is None:
from kittens.themes.collection import load_themes
themes = load_themes(cache_age=-1, ignore_no_cache=True)
names = tuple(t.name for t in themes if t.name.startswith(prefix))
ans.add_match_group('Themes', names)
else:
complete_basic_option_args(ans, opt, prefix)
def remote_files_completer(name: str, matchers: Tuple[str, ...]) -> CompleteArgsFunc:
@@ -500,6 +516,8 @@ def remote_files_completer(name: str, matchers: Tuple[str, ...]) -> CompleteArgs
if opt is None:
complete_files_and_dirs(ans, prefix, name, predicate)
else:
complete_basic_option_args(ans, opt, prefix)
return complete_files_map
@@ -509,6 +527,8 @@ def remote_args_completer(title: str, words: Iterable[str]) -> CompleteArgsFunc:
def complete_names_for_arg(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
if opt is None:
ans.add_match_group(title, {c: '' for c in items if c.startswith(prefix)})
else:
complete_basic_option_args(ans, opt, prefix)
return complete_names_for_arg
@@ -522,6 +542,8 @@ def complete_diff_args(ans: Completions, opt: Optional[OptionDict], prefix: str,
complete_files_and_dirs(ans, prefix, 'Files')
elif opt['dest'] == 'config':
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
else:
complete_basic_option_args(ans, opt, prefix)
def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_word: bool) -> None:
@@ -548,6 +570,7 @@ def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_wor
complete_alias_map(ans, words, new_word, option_map, {
'icat': complete_icat_args,
'diff': complete_diff_args,
'themes': complete_themes_args,
}.get(kitten))

View File

@@ -185,8 +185,11 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
if defn.has_color_table:
a(' self.color_table = array(self.color_table.typecode, self.color_table)')
a(' if options_dict is not None:')
a(' null = object()')
a(' for key in option_names:')
a(' setattr(self, key, options_dict[key])')
a(' val = options_dict.get(key, null)')
a(' if val is not null:')
a(' setattr(self, key, val)')
a('')
a(' @property')

View File

@@ -44,8 +44,9 @@ def expand_opt_references(conf_name: str, text: str) -> str:
def remove_markup(text: str) -> str:
ref_map = {
'layouts': f'{website_url("overview")}#layouts',
'sessions': f'{website_url("overview")}#layouts',
'sessions': f'{website_url("overview")}#startup-sessions',
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
'action-select_tab': f'{website_url("actions")}#select-tab',
}
def sub(m: Match) -> str:

View File

@@ -35,12 +35,17 @@ def build_ansi_color_table(opts: Optional[Options] = None) -> int:
def atomic_save(data: bytes, path: str) -> None:
import shutil
import tempfile
path = os.path.realpath(path)
fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
try:
with os.fdopen(fd, 'wb') as f:
f.write(data)
os.rename(p, path)
with suppress(FileNotFoundError):
shutil.copystat(path, p)
os.utime(p)
os.replace(p, path)
finally:
try:
os.remove(p)
@@ -123,9 +128,12 @@ def finalize_keys(opts: Options) -> None:
def finalize_mouse_mappings(opts: Options) -> None:
defns: List[MouseMapping] = []
for d in opts.mouse_map:
defns.append(d.resolve_and_copy(opts.kitty_mod, opts.kitten_alias))
if d is None: # clear_all_mouse_actions
defns = [] # type: ignore
else:
defns.append(d.resolve_and_copy(opts.kitty_mod, opts.kitten_alias))
mousemap: MouseMap = {}
for defn in defns:
is_no_op = defn.action.func in no_op_actions
if is_no_op:

View File

@@ -23,7 +23,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 22, 2)
version: Version = Version(0, 23, 1)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -34,7 +34,7 @@ typedef struct {
} CTFace;
PyTypeObject CTFace_Type;
static inline char*
static char*
convert_cfstring(CFStringRef src, int free_src) {
#define SZ 4094
static char buf[SZ+2] = {0};
@@ -46,7 +46,7 @@ convert_cfstring(CFStringRef src, int free_src) {
#undef SZ
}
static inline void
static void
init_face(CTFace *self, CTFontRef font, FONTS_DATA_HANDLE fg UNUSED) {
if (self->hb_font) hb_font_destroy(self->hb_font);
self->hb_font = NULL;
@@ -61,7 +61,7 @@ init_face(CTFace *self, CTFontRef font, FONTS_DATA_HANDLE fg UNUSED) {
self->scaled_point_sz = CTFontGetSize(self->ct_font);
}
static inline CTFace*
static CTFace*
ct_face(CTFontRef font, FONTS_DATA_HANDLE fg) {
CTFace *self = (CTFace *)CTFace_Type.tp_alloc(&CTFace_Type, 0);
if (self) {
@@ -174,7 +174,7 @@ coretext_all_fonts(PyObject UNUSED *_self) {
return ans;
}
static inline unsigned int
static unsigned int
glyph_id_for_codepoint_ctfont(CTFontRef ct_font, char_type ch) {
unichar chars[2] = {0};
CGGlyph glyphs[2] = {0};
@@ -183,7 +183,7 @@ glyph_id_for_codepoint_ctfont(CTFontRef ct_font, char_type ch) {
return glyphs[0];
}
static inline bool
static bool
is_last_resort_font(CTFontRef new_font) {
CFStringRef name = CTFontCopyPostScriptName(new_font);
CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0);
@@ -191,7 +191,7 @@ is_last_resort_font(CTFontRef new_font) {
return cr == kCFCompareEqualTo;
}
static inline CTFontRef
static CTFontRef
manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
CFArrayRef fonts = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
CTFontRef ans = NULL;
@@ -220,7 +220,7 @@ manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
return ans;
}
static inline CTFontRef
static CTFontRef
find_substitute_face(CFStringRef str, CTFontRef old_font, CPUCell *cpu_cell) {
// CTFontCreateForString returns the original font when there are combining
// diacritics in the font and the base character is in the original font,
@@ -296,7 +296,7 @@ get_glyph_width(PyObject *s, glyph_index g) {
return (int)ceil(bounds.size.width);
}
static inline float
static float
scaled_point_sz(FONTS_DATA_HANDLE fg) {
return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts;
}
@@ -448,7 +448,7 @@ finalize(void) {
}
static inline void
static void
render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int width, unsigned int height, unsigned int baseline) {
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
if (color_space == NULL) fatal("Out of memory");
@@ -492,7 +492,7 @@ ensure_render_space(size_t width, size_t height, size_t num_glyphs) {
}
}
static inline void
static void
render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned int baseline, unsigned int num_glyphs) {
memset(buffers.render_buf, 0, width * height);
CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray();
@@ -538,7 +538,7 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
}
static inline bool
static bool
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_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, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
unsigned int canvas_width = cell_width * num_cells;
ensure_render_space(canvas_width, cell_height, num_glyphs);

View File

@@ -46,7 +46,7 @@ cursor_reset_display_attrs(Cursor *self) {
}
static inline void
static void
parse_color(int *params, unsigned int *i, unsigned int count, uint32_t *result) {
unsigned int attr;
uint8_t r, g, b;

View File

@@ -11,6 +11,7 @@
#include <unistd.h>
#undef _DARWIN_C_SOURCE
#endif
#include "data-types.h"
#include "cleanup.h"
#include "safe-wrappers.h"
@@ -23,13 +24,17 @@
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <locale.h>
#ifdef WITH_PROFILER
#include <gperftools/profiler.h>
#endif
#include "monotonic.h"
#ifdef __APPLE__
#include <libproc.h>
#include <xlocale.h>
static PyObject*
user_cache_dir() {
@@ -91,7 +96,7 @@ stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
}
#endif
static inline bool
static bool
put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) {
struct termios raw_termios = *termios_p;
cfmakeraw(&raw_termios);
@@ -156,6 +161,15 @@ wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
}
static PyObject*
locale_is_valid(PyObject *self UNUSED, PyObject *args) {
char *name;
if (!PyArg_ParseTuple(args, "s", &name)) return NULL;
locale_t test_locale = newlocale(LC_ALL_MASK, name, NULL);
if (!test_locale) { Py_RETURN_FALSE; }
freelocale(test_locale);
Py_RETURN_TRUE;
}
static PyMethodDef module_methods[] = {
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
@@ -169,6 +183,7 @@ static PyMethodDef module_methods[] = {
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
{"locale_is_valid", (PyCFunction)locale_is_valid, METH_VARARGS, ""},
#ifdef __APPLE__
METHODB(user_cache_dir, METH_NOARGS),
METHODB(process_group_map, METH_NOARGS),

View File

@@ -84,7 +84,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
static int
open_cache_file_without_tmpfile(const char *cache_path) {
int fd = -1;
static const char *template = "%s/disk-cache-XXXXXXXXXXXX";
static const char template[] = "%s/disk-cache-XXXXXXXXXXXX";
const size_t sz = strlen(cache_path) + sizeof(template) + 4;
FREE_AFTER_FUNCTION char *buf = calloc(1, sz);
if (!buf) { errno = ENOMEM; return -1; }
@@ -158,7 +158,7 @@ copy_between_files(int infd, int outfd, off_t in_pos, size_t len, uint8_t *buf,
return true;
}
static inline off_t
static off_t
size_of_cache_file(DiskCache *self) {
return lseek(self->cache_file_fd, 0, SEEK_END);
}
@@ -255,7 +255,7 @@ cmp_pos_in_cache_file(void *a_, void *b_) {
return a->pos_in_cache_file - b->pos_in_cache_file;
}
static inline void
static void
find_hole(DiskCache *self) {
off_t required_size = self->currently_writing.data_sz, prev = -100;
HASH_SORT(self->entries, cmp_pos_in_cache_file);
@@ -271,7 +271,7 @@ find_hole(DiskCache *self) {
}
}
static inline bool
static bool
find_cache_entry_to_write(DiskCache *self) {
CacheEntry *tmp, *s;
off_t size_on_disk = size_of_cache_file(self);
@@ -298,7 +298,7 @@ find_cache_entry_to_write(DiskCache *self) {
return false;
}
static inline bool
static bool
write_dirty_entry(DiskCache *self) {
size_t left = self->currently_writing.data_sz;
uint8_t *p = self->currently_writing.data;
@@ -330,7 +330,7 @@ write_dirty_entry(DiskCache *self) {
return true;
}
static inline void
static void
retire_currently_writing(DiskCache *self) {
CacheEntry *s = NULL;
HASH_FIND(hh, self->entries, self->currently_writing.hash_key, self->currently_writing.hash_keylen, s);
@@ -477,7 +477,7 @@ dealloc(DiskCache* self) {
Py_TYPE(self)->tp_free((PyObject*)self);
}
static inline CacheEntry*
static CacheEntry*
create_cache_entry(const void *key, const size_t key_sz) {
CacheEntry *s = calloc(1, sizeof(CacheEntry));
if (!s) return (CacheEntry*)PyErr_NoMemory();

View File

@@ -494,7 +494,8 @@ def create_os_window(
wm_class_class: str,
load_programs: Optional[Callable[[bool], None]] = None,
x: int = -1,
y: int = -1
y: int = -1,
disallow_override_title: bool = False,
) -> int:
pass
@@ -671,6 +672,10 @@ def cocoa_get_lang() -> Optional[str]:
pass
def locale_is_valid(name: str) -> bool:
pass
def mark_os_window_for_close(os_window_id: int, cr_type: int = 2) -> bool:
pass
@@ -1208,3 +1213,7 @@ class OSWindowSize(TypedDict):
def get_os_window_size(os_window_id: int) -> Optional[OSWindowSize]:
pass
def get_all_processes() -> Tuple[int, ...]:
pass

View File

@@ -35,10 +35,10 @@ finalize(void) {
}
}
static inline PyObject*
static PyObject*
pybool(FcBool x) { PyObject *ans = x ? Py_True: Py_False; Py_INCREF(ans); return ans; }
static inline PyObject*
static PyObject*
pyspacing(int val) {
#define S(x) case FC_##x: return PyUnicode_FromString(#x)
switch(val) { S(PROPORTIONAL); S(DUAL); S(MONO); S(CHARCELL); default: return PyUnicode_FromString("UNKNOWN"); }
@@ -46,7 +46,7 @@ pyspacing(int val) {
}
static inline PyObject*
static PyObject*
pattern_as_dict(FcPattern *pat) {
PyObject *ans = PyDict_New(), *p = NULL, *list = NULL;
if (ans == NULL) return NULL;
@@ -112,7 +112,7 @@ exit:
#undef LS
}
static inline PyObject*
static PyObject*
font_set(FcFontSet *fs) {
PyObject *ans = PyTuple_New(fs->nfont);
if (ans == NULL) return NULL;
@@ -154,7 +154,7 @@ end:
return ans;
}
static inline PyObject*
static PyObject*
_fc_match(FcPattern *pat) {
FcPattern *match = NULL;
PyObject *ans = NULL;
@@ -172,7 +172,7 @@ end:
static char_type char_buf[1024];
static inline void
static void
add_charset(FcPattern *pat, size_t num) {
FcCharSet *charset = NULL;
if (num) {
@@ -190,7 +190,7 @@ end:
if (charset != NULL) FcCharSetDestroy(charset);
}
static inline bool
static bool
_native_fc_match(FcPattern *pat, FontConfigFace *ans) {
bool ok = false;
FcPattern *match = NULL;

View File

@@ -101,7 +101,7 @@ ensure_canvas_can_fit(FontGroup *fg, unsigned cells) {
}
static inline void
static void
save_window_font_groups(void) {
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *w = global_state.os_windows + o;
@@ -109,7 +109,7 @@ save_window_font_groups(void) {
}
}
static inline void
static void
restore_window_font_groups(void) {
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *w = global_state.os_windows + o;
@@ -123,7 +123,7 @@ restore_window_font_groups(void) {
}
}
static inline bool
static bool
font_group_is_unused(FontGroup *fg) {
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *w = global_state.os_windows + o;
@@ -140,7 +140,7 @@ free_maps(Font *font) {
font->glyph_properties_hash_table = NULL;
}
static inline void
static void
del_font(Font *f) {
Py_CLEAR(f->face);
free(f->ffs_hb_features); f->ffs_hb_features = NULL;
@@ -148,7 +148,7 @@ del_font(Font *f) {
f->bold = false; f->italic = false;
}
static inline void
static void
del_font_group(FontGroup *fg) {
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
fg->sprite_map = free_sprite_map(fg->sprite_map);
@@ -156,7 +156,7 @@ del_font_group(FontGroup *fg) {
free(fg->fonts); fg->fonts = NULL;
}
static inline void
static void
trim_unused_font_groups(void) {
save_window_font_groups();
size_t i = 0;
@@ -171,7 +171,7 @@ trim_unused_font_groups(void) {
restore_window_font_groups();
}
static inline void
static void
add_font_group(void) {
if (num_font_groups) trim_unused_font_groups();
if (num_font_groups >= font_groups_capacity) {
@@ -184,7 +184,7 @@ add_font_group(void) {
num_font_groups++;
}
static inline FontGroup*
static FontGroup*
font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y) {
for (size_t i = 0; i < num_font_groups; i++) {
FontGroup *fg = font_groups + i;
@@ -205,7 +205,7 @@ font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y
// Sprites {{{
static inline void
static void
sprite_map_set_error(int error) {
switch(error) {
case 1:
@@ -223,7 +223,7 @@ sprite_tracker_set_limits(size_t max_texture_size_, size_t max_array_len_) {
max_array_len = MIN(0xfffu, max_array_len_);
}
static inline void
static void
do_increment(FontGroup *fg, int *error) {
fg->sprite_tracker.x++;
if (fg->sprite_tracker.x >= fg->sprite_tracker.xnum) {
@@ -265,7 +265,7 @@ sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_wi
}
// }}}
static inline PyObject*
static PyObject*
desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
PyObject *d = specialize_font_descriptor(desc, fg);
if (d == NULL) return NULL;
@@ -274,7 +274,7 @@ desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
return ans;
}
static inline bool
static bool
init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) {
f->face = face; Py_INCREF(f->face);
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
@@ -311,7 +311,7 @@ init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentati
return true;
}
static inline void
static void
free_font_groups(void) {
if (font_groups) {
for (size_t i = 0; i < num_font_groups; i++) del_font_group(font_groups + i);
@@ -332,7 +332,7 @@ python_send_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigne
}
static inline void
static void
calc_cell_metrics(FontGroup *fg) {
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);
@@ -371,17 +371,17 @@ calc_cell_metrics(FontGroup *fg) {
ensure_canvas_can_fit(fg, 8);
}
static inline bool
static bool
face_has_codepoint(PyObject* face, char_type cp) {
return glyph_id_for_codepoint(face, cp) > 0;
}
static inline bool
static bool
has_emoji_presentation(CPUCell *cpu_cell, GPUCell *gpu_cell) {
return (gpu_cell->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15;
}
static inline bool
static bool
has_cell_text(Font *self, CPUCell *cell) {
if (!face_has_codepoint(self->face, cell->ch)) return false;
char_type combining_chars[arraysz(cell->cc_idx)];
@@ -403,7 +403,7 @@ has_cell_text(Font *self, CPUCell *cell) {
return true;
}
static inline void
static void
output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face, bool new_face) {
printf("U+%x ", cell->ch);
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
@@ -417,7 +417,7 @@ output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_pres
printf("\n");
}
static inline ssize_t
static ssize_t
load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) {
if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
ssize_t f;
@@ -456,7 +456,7 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em
return ans;
}
static inline ssize_t
static ssize_t
fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
bool bold = (gpu_cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (gpu_cell->attrs >> ITALIC_SHIFT) & 1;
@@ -474,7 +474,7 @@ fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
return load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation);
}
static inline ssize_t
static ssize_t
in_symbol_maps(FontGroup *fg, char_type ch) {
for (size_t i = 0; i < num_symbol_maps; i++) {
if (symbol_maps[i].left <= ch && ch <= symbol_maps[i].right) return fg->first_symbol_font_idx + symbol_maps[i].font_idx;
@@ -491,7 +491,7 @@ in_symbol_maps(FontGroup *fg, char_type ch) {
// - BLANK_FONT
// - BOX_FONT
// - an index in the fonts list
static inline ssize_t
static ssize_t
font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, bool *is_fallback_font, bool *is_emoji_presentation) {
*is_fallback_font = false;
*is_emoji_presentation = false;
@@ -531,13 +531,13 @@ START_ALLOW_CASE_RANGE
END_ALLOW_CASE_RANGE
}
static inline void
static void
set_sprite(GPUCell *cell, sprite_index x, sprite_index y, sprite_index z) {
cell->sprite_x = x; cell->sprite_y = y; cell->sprite_z = z;
}
// Gives a unique (arbitrary) id to a box glyph
static inline glyph_index
static glyph_index
box_glyph_id(char_type ch) {
START_ALLOW_CASE_RANGE
switch(ch) {
@@ -596,7 +596,7 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
Py_DECREF(ret);
}
static inline void
static void
load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells) {
index_type num;
hb_buffer_clear_contents(harfbuzz_buffer);
@@ -617,13 +617,13 @@ load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_
}
static inline void
static void
set_cell_sprite(GPUCell *cell, const SpritePosition *sp) {
cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z;
if (sp->colored) cell->sprite_z |= 0x4000;
}
static inline pixel*
static pixel*
extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) {
pixel *ans = fg->canvas.buf + (fg->cell_width * fg->cell_height * (fg->canvas.current_cells - 1)), *dest = ans, *src = fg->canvas.buf + (i * fg->cell_width);
unsigned int stride = fg->cell_width * num_cells;
@@ -638,7 +638,7 @@ typedef struct GlyphRenderScratch {
} GlyphRenderScratch;
static GlyphRenderScratch global_glyph_render_scratch = {0};
static inline void
static void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index *glyphs, unsigned glyph_count, bool center_glyph) {
#define sp global_glyph_render_scratch.sprite_positions
int error = 0;
@@ -703,14 +703,14 @@ typedef struct {
static GroupState group_state = {0};
static inline unsigned int
static unsigned int
num_codepoints_in_cell(CPUCell *cell) {
unsigned int ans = 1;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++;
return ans;
}
static inline void
static void
shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature) {
if (group_state.groups_capacity <= 2 * num_cells) {
group_state.groups_capacity = MAX(128u, 2 * num_cells); // avoid unnecessary reallocs
@@ -747,7 +747,7 @@ shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb
else group_state.num_glyphs = MIN(info_length, positions_length);
}
static inline bool
static bool
is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) {
// A glyph is special if the codepoint it corresponds to matches a
// different glyph in the font
@@ -764,7 +764,7 @@ is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) {
return s->data & SPECIAL_VALUE_MASK;
}
static inline bool
static bool
is_empty_glyph(glyph_index glyph_id, Font *font) {
// A glyph is empty if its metrics have a width of zero
GlyphProperties *s = find_or_create_glyph_properties(&font->glyph_properties_hash_table, glyph_id);
@@ -776,7 +776,7 @@ is_empty_glyph(glyph_index glyph_id, Font *font) {
return s->data & EMPTY_VALUE_MASK;
}
static inline unsigned int
static unsigned int
check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
cell_data->codepoints_consumed++;
if (cell_data->codepoints_consumed >= cell_data->num_codepoints) {
@@ -806,7 +806,7 @@ check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
return 0;
}
static inline LigatureType
static LigatureType
ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) {
const char *p, *m, *s, *e;
if (strategy == SPACERS_IOSEVKA) {
@@ -826,7 +826,7 @@ ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) {
#define G(x) (group_state.x)
static inline void
static void
detect_spacer_strategy(hb_font_t *hbf, Font *font) {
CPUCell cpu_cells[3] = {{.ch = '='}, {.ch = '='}, {.ch = '='}};
GPUCell gpu_cells[3] = {{.attrs = 1}, {.attrs = 1}, {.attrs = 1}};
@@ -1071,7 +1071,7 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
#endif
}
static inline void
static void
merge_groups_for_pua_space_ligature(void) {
while (G(group_idx) > 0) {
Group *g = G(groups), *g1 = G(groups) + 1;
@@ -1084,14 +1084,14 @@ merge_groups_for_pua_space_ligature(void) {
#undef MOVE_GLYPH_TO_NEXT_GROUP
static inline bool
static 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
static 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++) {
@@ -1107,7 +1107,7 @@ split_run_at_offset(index_type cursor_offset, index_type *left, index_type *righ
}
static inline void
static void
render_groups(FontGroup *fg, Font *font, bool center_glyph) {
unsigned idx = 0;
while (idx <= G(group_idx)) {
@@ -1174,7 +1174,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
}
#undef G
static inline void
static void
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy) {
switch(font_idx) {
default:
@@ -1211,7 +1211,7 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
}
}
static inline bool
static bool
is_non_emoji_dingbat(char_type ch) {
return 0x2700 <= ch && ch <= 0x27bf && !is_emoji(ch);
}
@@ -1295,7 +1295,7 @@ render_simple_text(FONTS_DATA_HANDLE fg_, const char *text) {
return ans;
}
static inline void
static void
clear_symbol_maps(void) {
if (symbol_maps) { free(symbol_maps); symbol_maps = NULL; num_symbol_maps = 0; }
}
@@ -1329,7 +1329,7 @@ set_font_data(PyObject UNUSED *m, PyObject *args) {
Py_RETURN_NONE;
}
static inline void
static void
send_prerendered_sprites(FontGroup *fg) {
int error = 0;
sprite_index x = 0, y = 0, z = 0;
@@ -1354,7 +1354,7 @@ send_prerendered_sprites(FontGroup *fg) {
Py_CLEAR(args);
}
static inline size_t
static size_t
initialize_font(FontGroup *fg, unsigned int desc_idx, const char *ftype) {
PyObject *d = PyObject_CallFunction(descriptor_for_idx, "I", desc_idx);
if (d == NULL) { PyErr_Print(); fatal("Failed for %s font", ftype); }

View File

@@ -76,18 +76,18 @@ static FT_Library library;
FT_Library
freetype_library(void) { return library; }
static inline int
static int
font_units_to_pixels_y(Face *self, int x) {
return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0);
}
static inline int
static int
font_units_to_pixels_x(Face *self, int x) {
return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0);
}
static inline int
static int
get_load_flags(int hinting, int hintstyle, int base) {
int flags = base;
if (hinting) {
@@ -98,7 +98,7 @@ get_load_flags(int hinting, int hintstyle, int base) {
}
static inline bool
static bool
load_glyph(Face *self, int glyph_index, int load_type) {
int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
int error = FT_Load_Glyph(self->face, glyph_index, flags);
@@ -110,7 +110,7 @@ load_glyph(Face *self, int glyph_index, int load_type) {
return true;
}
static inline unsigned int
static unsigned int
get_height_for_char(Face *self, char ch) {
unsigned int ans = 0;
int glyph_index = FT_Get_Char_Index(self->face, ch);
@@ -125,7 +125,7 @@ get_height_for_char(Face *self, char ch) {
return ans;
}
static inline unsigned int
static unsigned int
calc_cell_height(Face *self, bool for_metrics) {
unsigned int ans = font_units_to_pixels_y(self, self->height);
if (for_metrics) {
@@ -139,7 +139,7 @@ calc_cell_height(Face *self, bool for_metrics) {
return ans;
}
static inline bool
static bool
set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt xdpi, FT_UInt ydpi, unsigned int desired_height, unsigned int cell_height) {
int error = FT_Set_Char_Size(self->face, 0, char_height, xdpi, ydpi);
if (!error) {
@@ -189,7 +189,7 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA
return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->cell_height);
}
static inline bool
static bool
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) {
#define CPY(n) self->n = self->face->n;
CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness);
@@ -290,7 +290,7 @@ postscript_name_for_face(const PyObject *face_) {
return ps_name ? ps_name : "";
}
static inline unsigned int
static unsigned int
calc_cell_width(Face *self) {
unsigned int ans = 0;
for (char_type i = 32; i < 128; i++) {
@@ -382,7 +382,7 @@ typedef struct {
int bitmap_left, bitmap_top;
} ProcessedBitmap;
static inline void
static void
free_processed_bitmap(ProcessedBitmap *bm) {
if (bm->needs_free) {
bm->needs_free = false;
@@ -390,7 +390,7 @@ free_processed_bitmap(ProcessedBitmap *bm) {
}
}
static inline void
static void
trim_borders(ProcessedBitmap *ans, size_t extra) {
bool column_has_text = false;
@@ -407,7 +407,7 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
ans->width -= extra;
}
static inline void
static void
populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) {
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
ans->rows = bitmap->rows;
@@ -438,7 +438,7 @@ freetype_convert_mono_bitmap(FT_Bitmap *src, FT_Bitmap *dest) {
return true;
}
static inline bool
static bool
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) {
if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false;
unsigned int max_width = cell_width * num_cells;
@@ -508,7 +508,7 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
bm->buf = dest; bm->needs_free = true; bm->stride = 4 * width; bm->width = width; bm->rows = cell_height;
}
static inline void
static void
detect_right_edge(ProcessedBitmap *ans) {
ans->right_edge = 0;
for (ssize_t x = ans->width - 1; !ans->right_edge && x > -1; x--) {
@@ -519,7 +519,7 @@ detect_right_edge(ProcessedBitmap *ans) {
}
}
static inline bool
static bool
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline UNUSED) {
unsigned short best = 0, diff = USHRT_MAX;
const short limit = self->face->num_fixed_sizes;
@@ -550,7 +550,7 @@ render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int
}
static inline void
static void
copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) {
for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) {
pixel *d = dest + dest_stride * dr;
@@ -566,7 +566,9 @@ copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect
}
}
static inline void
static const bool debug_placement = false;
static void
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline, unsigned int glyph_num) {
// We want the glyph to be positioned inside the cell based on the bearingX
// and bearingY values, making sure that it does not overflow the cell.
@@ -575,6 +577,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
// Calculate column bounds
int32_t xoff = (int32_t)(x_offset + bm->bitmap_left);
if (debug_placement) printf(" bitmap_left: %d xoff: %d", bm->bitmap_left, xoff);
if (xoff < 0) src.left += -xoff;
else dest.left = xoff;
// Move the dest start column back if the width overflows because of it, but only if we are not in a very long/infinite ligature
@@ -629,10 +632,17 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
}
x_offset = x + (float)positions[i].x_offset / 64.0f;
y = (float)positions[i].y_offset / 64.0f;
if (debug_placement) printf("%d: x=%f canvas: %u", i, x_offset, canvas_width);
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) {
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i);
}
x += (float)positions[i].x_advance / 64.0f;
if (debug_placement) printf(" adv: %f\n", (float)positions[i].x_advance / 64.0f);
// the roundf() below is needed for infinite length ligatures, for a test case
// use: kitty --config None -o 'font_family Fira Code' -o 'font_size 4.5' sh -c
// "echo '|---|--------|-------|-------------|-------------|HH'; read"
// if this causes issues with non-infinite ligatures, we could choose this behavior
// based on num_glyphs and/or num_cells
x += roundf((float)positions[i].x_advance / 64.0f);
free_processed_bitmap(&bm);
}

View File

@@ -226,7 +226,7 @@ unbind_buffer(ssize_t buf_idx) {
glBindBuffer(buffers[buf_idx].usage, 0);
}
static inline void
static void
alloc_buffer(ssize_t idx, GLsizeiptr size, GLenum usage) {
Buffer *b = buffers + idx;
if (b->size == size) return;
@@ -234,13 +234,13 @@ alloc_buffer(ssize_t idx, GLsizeiptr size, GLenum usage) {
glBufferData(b->usage, size, NULL, usage);
}
static inline void*
static void*
map_buffer(ssize_t idx, GLenum access) {
void *ans = glMapBuffer(buffers[idx].usage, access);
return ans;
}
static inline void
static void
unmap_buffer(ssize_t idx) {
glUnmapBuffer(buffers[idx].usage);
}

View File

@@ -49,7 +49,7 @@ request_tick_callback(void) {
glfwPostEmptyEvent();
}
static inline void
static void
min_size_for_os_window(OSWindow *window, int *min_width, int *min_height) {
*min_width = MAX(8u, window->fonts_data->cell_width + 1);
*min_height = MAX(8u, window->fonts_data->cell_height + 1);
@@ -124,7 +124,7 @@ update_os_window_references() {
}
}
static inline bool
static bool
set_callback_window(GLFWwindow *w) {
global_state.callback_os_window = glfwGetWindowUserPointer(w);
if (global_state.callback_os_window) return true;
@@ -137,7 +137,7 @@ set_callback_window(GLFWwindow *w) {
return false;
}
static inline bool
static bool
is_window_ready_for_callbacks(void) {
OSWindow *w = global_state.callback_os_window;
if (w->num_tabs == 0) return false;
@@ -148,7 +148,7 @@ is_window_ready_for_callbacks(void) {
#define WINDOW_CALLBACK(name, fmt, ...) call_boss(name, "K" fmt, global_state.callback_os_window->id, __VA_ARGS__)
static inline void
static void
show_mouse_cursor(GLFWwindow *w) {
glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
@@ -250,7 +250,7 @@ refresh_callback(GLFWwindow *w) {
static int mods_at_last_key_or_button_event = 0;
static inline int
static int
key_to_modifier(uint32_t key) {
switch(key) {
case GLFW_FKEY_LEFT_SHIFT:
@@ -403,7 +403,7 @@ application_close_requested_callback(int flags) {
}
}
static inline void get_window_dpi(GLFWwindow *w, double *x, double *y);
static void get_window_dpi(GLFWwindow *w, double *x, double *y);
#ifdef __APPLE__
static bool
@@ -492,7 +492,7 @@ set_os_window_size(OSWindow *os_window, int x, int y) {
glfwSetWindowSize(os_window->handle, x, y);
}
static inline void
static void
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
*xscale = 1; *yscale = 1;
if (w) glfwGetWindowContentScale(w, xscale, yscale);
@@ -611,7 +611,7 @@ set_titlebar_color(OSWindow *w, color_type color, bool use_system_color) {
}
}
static inline PyObject*
static PyObject*
native_window_handle(GLFWwindow *w) {
#ifdef __APPLE__
void *ans = glfwGetCocoaWindow(w);
@@ -623,12 +623,12 @@ native_window_handle(GLFWwindow *w) {
static PyObject*
create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
int x = -1, y = -1;
int x = -1, y = -1, disallow_override_title = 0;
char *title, *wm_class_class, *wm_class_name;
PyObject *load_programs = NULL, *get_window_size, *pre_show_callback;
static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "load_programs", "x", "y", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|Oii", (char**)kwlist,
&get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "load_programs", "x", "y", "disallow_override_title", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|Oiip", (char**)kwlist,
&get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y, &disallow_override_title)) return NULL;
static bool is_first_window = true;
if (is_first_window) {
@@ -742,6 +742,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
}
OSWindow *w = add_os_window();
w->handle = glfw_window;
w->disallow_title_changes = disallow_override_title;
update_os_window_references();
for (size_t i = 0; i < global_state.num_os_windows; i++) {
// On some platforms (macOS) newly created windows don't get the initial focus in event
@@ -815,7 +816,7 @@ os_window_update_size_increments(OSWindow *window) {
}
#ifdef __APPLE__
static inline bool
static bool
window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source_workspace_count) {
static size_t workspaces[64];
size_t workspace_count = cocoa_get_workspace_ids(w, workspaces, arraysz(workspaces));
@@ -827,7 +828,7 @@ window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source
return false;
}
static inline void
static void
cocoa_focus_last_window(id_type source_window_id, size_t *source_workspaces, size_t source_workspace_count) {
id_type highest_focus_number = 0;
OSWindow *window_to_focus = NULL;

View File

@@ -29,31 +29,31 @@ PyTypeObject GraphicsManager_Type;
// caching {{{
#define CACHE_KEY_BUFFER_SIZE 32
static inline size_t
static size_t
cache_key(const ImageAndFrame x, char *key) {
return snprintf(key, CACHE_KEY_BUFFER_SIZE, "%llx:%x", x.image_id, x.frame_id);
}
#define CK(x) key, cache_key(x, key)
static inline bool
static bool
add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz) {
char key[CACHE_KEY_BUFFER_SIZE];
return add_to_disk_cache(self->disk_cache, CK(x), data, sz);
}
static inline bool
static bool
remove_from_cache(GraphicsManager *self, const ImageAndFrame x) {
char key[CACHE_KEY_BUFFER_SIZE];
return remove_from_disk_cache(self->disk_cache, CK(x));
}
static inline bool
static bool
read_from_cache(const GraphicsManager *self, const ImageAndFrame x, void **data, size_t *sz) {
char key[CACHE_KEY_BUFFER_SIZE];
return read_from_disk_cache_simple(self->disk_cache, CK(x), data, sz, false);
}
static inline size_t
static size_t
cache_size(const GraphicsManager *self) { return disk_cache_total_size(self->disk_cache); }
#undef CK
// }}}
@@ -75,13 +75,13 @@ grman_alloc() {
return self;
}
static inline void
static void
free_refs_data(Image *img) {
free(img->refs); img->refs = NULL;
img->refcnt = 0; img->refcap = 0;
}
static inline void
static void
free_load_data(LoadData *ld) {
free(ld->buf); ld->buf_used = 0; ld->buf_capacity = 0; ld->buf = NULL;
if (ld->mapped_file) munmap(ld->mapped_file, ld->mapped_file_sz);
@@ -89,7 +89,7 @@ free_load_data(LoadData *ld) {
ld->loading_for = (const ImageAndFrame){0};
}
static inline void
static void
free_image(GraphicsManager *self, Image *img) {
if (img->texture_id) free_texture(&img->texture_id);
ImageAndFrame key = { .image_id=img->internal_id, .frame_id = img->root_frame.id };
@@ -121,7 +121,7 @@ dealloc(GraphicsManager* self) {
static id_type internal_id_counter = 1;
static inline Image*
static Image*
img_by_internal_id(GraphicsManager *self, id_type id) {
for (size_t i = 0; i < self->image_count; i++) {
if (self->images[i].internal_id == id) return self->images + i;
@@ -129,7 +129,7 @@ img_by_internal_id(GraphicsManager *self, id_type id) {
return NULL;
}
static inline Image*
static Image*
img_by_client_id(GraphicsManager *self, uint32_t id) {
for (size_t i = 0; i < self->image_count; i++) {
if (self->images[i].client_id == id) return self->images + i;
@@ -137,7 +137,7 @@ img_by_client_id(GraphicsManager *self, uint32_t id) {
return NULL;
}
static inline Image*
static Image*
img_by_client_number(GraphicsManager *self, uint32_t number) {
// get the newest image with the specified number
for (size_t i = self->image_count; i-- > 0; ) {
@@ -147,14 +147,14 @@ img_by_client_number(GraphicsManager *self, uint32_t number) {
}
static inline void
static void
remove_image(GraphicsManager *self, size_t idx) {
free_image(self, self->images + idx);
remove_i_from_array(self->images, idx, self->image_count);
self->layers_dirty = true;
}
static inline void
static void
remove_images(GraphicsManager *self, bool(*predicate)(Image*), id_type skip_image_internal_id) {
for (size_t i = self->image_count; i-- > 0;) {
Image *img = self->images + i;
@@ -173,7 +173,7 @@ trim_predicate(Image *img) {
}
static inline void
static void
apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type currently_added_image_internal_id) {
// First remove unreferenced images, even if they have an id
remove_images(self, trim_predicate, currently_added_image_internal_id);
@@ -190,7 +190,7 @@ apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type current
static char command_response[512] = {0};
static inline void
static void
set_command_failed_response(const char *code, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
@@ -203,7 +203,7 @@ set_command_failed_response(const char *code, const char *fmt, ...) {
// Decode formats {{{
#define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); goto err; }
static inline bool
static bool
mmap_img_file(GraphicsManager *self, int fd, size_t sz, off_t offset) {
if (!sz) {
struct stat s;
@@ -220,7 +220,7 @@ err:
}
static inline const char*
static const char*
zlib_strerror(int ret) {
#define Z(x) case x: return #x;
static char buf[128];
@@ -239,7 +239,7 @@ zlib_strerror(int ret) {
#undef Z
}
static inline bool
static bool
inflate_zlib(LoadData *load_data, uint8_t *buf, size_t bufsz) {
bool ok = false;
z_stream z;
@@ -272,7 +272,7 @@ png_error_handler(const char *code, const char *msg) {
set_command_failed_response(code, "%s", msg);
}
static inline bool
static bool
inflate_png(LoadData *load_data, uint8_t *buf, size_t bufsz) {
png_read_data d = {.err_handler=png_error_handler};
inflate_png_inner(&d, buf, bufsz);
@@ -342,7 +342,7 @@ png_path_to_bitmap(const char* path, uint8_t** data, unsigned int* width, unsign
}
static inline Image*
static Image*
find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
if (id) {
for (size_t i = 0; i < self->image_count; i++) {
@@ -359,7 +359,7 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
return ans;
}
static inline uint32_t
static uint32_t
get_free_client_id(const GraphicsManager *self) {
if (!self->image_count) return 1;
uint32_t *client_ids = malloc(sizeof(uint32_t) * self->image_count);
@@ -608,7 +608,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
#undef MAX_DATA_SZ
}
static inline const char*
static const char*
finish_command_response(const GraphicsCommand *g, bool data_loaded) {
static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128];
bool is_ok_response = !command_response[0];
@@ -638,7 +638,7 @@ finish_command_response(const GraphicsCommand *g, bool data_loaded) {
// Displaying images {{{
static inline void
static void
update_src_rect(ImageRef *ref, Image *img) {
// The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image
ref->src_rect.left = (float)ref->src_x / (float)img->width;
@@ -647,7 +647,7 @@ update_src_rect(ImageRef *ref, Image *img) {
ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height;
}
static inline void
static void
update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) {
uint32_t t;
if (num_cols == 0) {
@@ -708,7 +708,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
return img->client_id;
}
static inline void
static void
set_vertex_data(ImageRenderData *rd, const ImageRef *ref, const ImageRect *dest_rect) {
#define R(n, a, b) rd->vertices[n*4] = ref->src_rect.a; rd->vertices[n*4 + 1] = ref->src_rect.b; rd->vertices[n*4 + 2] = dest_rect->a; rd->vertices[n*4 + 3] = dest_rect->b;
R(0, right, top); R(1, right, bottom); R(2, left, bottom); R(3, left, top);
@@ -804,13 +804,13 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
// Animation {{{
#define DEFAULT_GAP 40
static inline Frame*
static Frame*
current_frame(Image *img) {
if (img->current_frame_index > img->extra_framecnt) return NULL;
return img->current_frame_index ? img->extra_frames + img->current_frame_index - 1 : &img->root_frame;
}
static inline Frame*
static Frame*
frame_for_id(Image *img, const uint32_t frame_id) {
if (img->root_frame.id == frame_id) return &img->root_frame;
for (unsigned i = 0; i < img->extra_framecnt; i++) {
@@ -832,7 +832,7 @@ frame_for_number(Image *img, const uint32_t frame_number) {
}
}
static inline void
static void
change_gap(Image *img, Frame *f, int32_t gap) {
uint32_t prev_gap = f->gap;
f->gap = MAX(0, gap);
@@ -845,14 +845,14 @@ typedef struct {
bool is_4byte_aligned, is_opaque;
} CoalescedFrameData;
static inline void
static void
blend_on_opaque(uint8_t *under_px, const uint8_t *over_px) {
const float alpha = (float)over_px[3] / 255.f;
const float alpha_op = 1.f - alpha;
for (unsigned i = 0; i < 3; i++) under_px[i] = (uint8_t)(over_px[i] * alpha + under_px[i] * alpha_op);
}
static inline void
static void
alpha_blend(uint8_t *dest_px, const uint8_t *src_px) {
if (src_px[3]) {
const float dest_a = (float)dest_px[3] / 255.f, src_a = (float)src_px[3] / 255.f;
@@ -1255,7 +1255,7 @@ handle_animation_control_command(GraphicsManager *self, bool *is_dirty, const Gr
}
}
static inline bool
static bool
image_is_animatable(const Image *img) {
return img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->is_drawn && img->animation_duration && (
!img->max_loops || img->current_loop < img->max_loops);
@@ -1368,7 +1368,7 @@ handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsComm
// Image lifetime/scrolling {{{
static inline void
static void
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell, bool only_first_image) {
bool matched = false;
for (size_t i = self->image_count; i-- > 0;) {
@@ -1403,24 +1403,24 @@ modify_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe
}
static inline bool
static bool
scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
ScrollData *d = (ScrollData*)data;
ref->start_row += d->amt;
return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit;
}
static inline bool
static bool
ref_within_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
return ref->start_row >= (int32_t)margin_top && ref->start_row + ref->effective_num_rows <= margin_bottom;
}
static inline bool
static bool
ref_outside_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
return ref->start_row + ref->effective_num_rows <= margin_top || ref->start_row > (int32_t)margin_bottom;
}
static inline bool
static bool
scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) {
ScrollData *d = (ScrollData*)data;
if (ref_within_region(ref, d->margin_top, d->margin_bottom)) {
@@ -1459,12 +1459,12 @@ grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize
}
}
static inline bool
static bool
clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
return ref->start_row + (int32_t)ref->effective_num_rows > 0;
}
static inline bool
static bool
clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
return true;
}
@@ -1474,14 +1474,14 @@ grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) {
filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell, false);
}
static inline bool
static bool
id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data;
if (g->id && img->client_id == g->id) return !g->placement_id || ref->client_id == g->placement_id;
return false;
}
static inline bool
static bool
number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data;
if (g->image_number && img->client_number == g->image_number) return !g->placement_id || ref->client_id == g->placement_id;
@@ -1489,31 +1489,31 @@ number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelS
}
static inline bool
static bool
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data;
return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
}
static inline bool
static bool
y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data;
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows));
}
static inline bool
static bool
z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data;
return ref->z_index == g->z_index;
}
static inline bool
static bool
point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell);
}
static inline bool
static bool
point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell);
}
@@ -1682,7 +1682,7 @@ new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
return ans;
}
static inline PyObject*
static PyObject*
image_as_dict(GraphicsManager *self, Image *img) {
#define U(x) #x, (unsigned int)(img->x)
#define B(x) #x, img->x ? Py_True : Py_False

View File

@@ -14,7 +14,7 @@
extern PyTypeObject Line_Type;
#define SEGMENT_SIZE 2048
static inline void
static void
add_segment(HistoryBuf *self) {
self->num_segments += 1;
self->segments = realloc(self->segments, sizeof(HistoryBufSegment) * self->num_segments);
@@ -28,7 +28,7 @@ add_segment(HistoryBuf *self) {
s->line_attrs = (line_attrs_type*)(((uint8_t*)s->gpu_cells) + gpu_cells_size);
}
static inline index_type
static index_type
segment_for(HistoryBuf *self, index_type y) {
index_type seg_num = y / SEGMENT_SIZE;
while (UNLIKELY(seg_num >= self->num_segments && SEGMENT_SIZE * self->num_segments < self->ynum)) add_segment(self);
@@ -42,23 +42,23 @@ segment_for(HistoryBuf *self, index_type y) {
return self->segments[seg_num].which + y * stride; \
}
static inline CPUCell*
static CPUCell*
cpu_lineptr(HistoryBuf *self, index_type y) {
seg_ptr(cpu_cells, self->xnum);
}
static inline GPUCell*
static GPUCell*
gpu_lineptr(HistoryBuf *self, index_type y) {
seg_ptr(gpu_cells, self->xnum);
}
static inline line_attrs_type*
static line_attrs_type*
attrptr(HistoryBuf *self, index_type y) {
seg_ptr(line_attrs, 1);
}
static inline PagerHistoryBuf*
static PagerHistoryBuf*
alloc_pagerhist(size_t pagerhist_sz) {
PagerHistoryBuf *ph;
if (!pagerhist_sz) return NULL;
@@ -71,14 +71,14 @@ alloc_pagerhist(size_t pagerhist_sz) {
return ph;
}
static inline void
static void
free_pagerhist(HistoryBuf *self) {
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf);
free(self->pagerhist);
self->pagerhist = NULL;
}
static inline bool
static bool
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
size_t buffer_size = ringbuf_capacity(ph->ringbuf);
if (buffer_size >= ph->maximum_size) return false;
@@ -92,7 +92,7 @@ pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
return true;
}
static inline void
static void
pagerhist_clear(HistoryBuf *self) {
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_reset(self->pagerhist->ringbuf);
}
@@ -133,7 +133,7 @@ dealloc(HistoryBuf* self) {
Py_TYPE(self)->tp_free((PyObject*)self);
}
static inline index_type
static index_type
index_of(HistoryBuf *self, index_type lnum) {
// The index (buffer position) of the line with line number lnum
// This is reverse indexing, i.e. lnum = 0 corresponds to the *last* line in the buffer.
@@ -142,7 +142,7 @@ index_of(HistoryBuf *self, index_type lnum) {
return (self->start_of_data + idx) % self->ynum;
}
static inline void
static void
init_line(HistoryBuf *self, index_type num, Line *l) {
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
l->cpu_cells = cpu_lineptr(self, num);
@@ -180,7 +180,7 @@ historybuf_clear(HistoryBuf *self) {
self->start_of_data = 0;
}
static inline bool
static bool
pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) {
if (sz > ph->maximum_size) return false;
if (!sz) return true;
@@ -190,7 +190,7 @@ pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) {
return true;
}
static inline bool
static bool
pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) {
uint8_t scratch[8];
size_t num = ringbuf_memcpy_from(scratch, ph->ringbuf, arraysz(scratch));
@@ -210,7 +210,7 @@ pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) {
return false;
}
static inline bool
static bool
pagerhist_write_ucs4(PagerHistoryBuf *ph, const Py_UCS4 *buf, size_t sz) {
uint8_t scratch[4];
for (size_t i = 0; i < sz; i++) {
@@ -220,7 +220,7 @@ pagerhist_write_ucs4(PagerHistoryBuf *ph, const Py_UCS4 *buf, size_t sz) {
return true;
}
static inline void
static void
pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
PagerHistoryBuf *ph = self->pagerhist;
if (!ph) return;
@@ -233,7 +233,7 @@ pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1);
}
static inline index_type
static index_type
historybuf_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
index_type idx = (self->start_of_data + self->count) % self->ynum;
init_line(self, idx, self->line);
@@ -327,10 +327,10 @@ end:
Py_RETURN_NONE;
}
static inline Line*
static Line*
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
static inline char_type
static char_type
pagerhist_remove_char(PagerHistoryBuf *ph, unsigned *count, uint8_t record[8]) {
uint32_t codep; UTF8State state = UTF8_ACCEPT;
*count = 0;

View File

@@ -46,7 +46,7 @@ is_modifier_key(const uint32_t key) {
END_ALLOW_CASE_RANGE
}
static inline void
static void
convert_glfw_mods(int mods, KeyEvent *ev, const unsigned key_encoding_flags) {
if (!key_encoding_flags) mods &= ~GLFW_LOCK_MASK;
ev->mods.alt = (mods & GLFW_MOD_ALT) > 0, ev->mods.ctrl = (mods & GLFW_MOD_CONTROL) > 0, ev->mods.shift = (mods & GLFW_MOD_SHIFT) > 0, ev->mods.super = (mods & GLFW_MOD_SUPER) > 0, ev->mods.hyper = (mods & GLFW_MOD_HYPER) > 0, ev->mods.meta = (mods & GLFW_MOD_META) > 0;
@@ -63,7 +63,7 @@ convert_glfw_mods(int mods, KeyEvent *ev, const unsigned key_encoding_flags) {
}
static inline void
static void
init_encoding_data(EncodingData *ans, const KeyEvent *ev) {
ans->add_actions = ev->report_all_event_types && ev->action != PRESS;
ans->has_mods = ev->mods.encoded[0] && ( ev->mods.encoded[0] != '1' || ev->mods.encoded[1] );
@@ -76,7 +76,7 @@ init_encoding_data(EncodingData *ans, const KeyEvent *ev) {
memcpy(ans->encoded_mods, ev->mods.encoded, sizeof(ans->encoded_mods));
}
static inline int
static int
serialize(const EncodingData *data, char *output, const char csi_trailer) {
int pos = 0;
bool second_field_not_empty = data->has_mods || data->add_actions;
@@ -113,7 +113,7 @@ serialize(const EncodingData *data, char *output, const char csi_trailer) {
return pos;
}
static inline uint32_t
static uint32_t
convert_kp_key_to_normal_key(uint32_t key_number) {
switch(key_number) {
#define S(x) case GLFW_FKEY_KP_##x: key_number = GLFW_FKEY_##x; break;
@@ -329,7 +329,7 @@ encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) {
return 0;
}
static inline bool
static bool
is_legacy_ascii_key(uint32_t key) {
START_ALLOW_CASE_RANGE
switch (key) {
@@ -408,7 +408,7 @@ encode_key(const KeyEvent *ev, char *output) {
return serialize(&ed, output, 'u');
}
static inline bool
static bool
startswith_ascii_control_char(const char *p) {
if (!p || !*p) return true;
uint32_t codep; UTF8State state = UTF8_ACCEPT;

View File

@@ -20,7 +20,7 @@ typedef struct {
PyObject *text;
} PyKeyEvent;
static inline PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev);
static PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev);
static PyObject*
new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) {
@@ -58,7 +58,7 @@ PyTypeObject PyKeyEvent_Type = {
.tp_new = new,
};
static inline PyObject*
static PyObject*
convert_glfw_key_event_to_python(const GLFWkeyevent *ev) {
PyKeyEvent *self = (PyKeyEvent*)PyKeyEvent_Type.tp_alloc(&PyKeyEvent_Type, 0);
if (!self) return NULL;
@@ -71,7 +71,7 @@ convert_glfw_key_event_to_python(const GLFWkeyevent *ev) {
}
// }}}
static inline Window*
static Window*
active_window(void) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
Window *w = t->windows + t->active_window;
@@ -79,7 +79,7 @@ active_window(void) {
return NULL;
}
static inline void
static void
update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
unsigned int cell_width = os_window->fonts_data->cell_width, cell_height = os_window->fonts_data->cell_height;
unsigned int left = w->geometry.left, top = w->geometry.top;

View File

@@ -11,7 +11,7 @@
#define CMD_BUF_SZ 2048
static inline bool
static bool
append_buf(char buf[CMD_BUF_SZ], size_t *pos, PyObject *ans) {
if (*pos) {
PyObject *bytes = PyBytes_FromStringAndSize(buf, *pos);
@@ -24,7 +24,7 @@ append_buf(char buf[CMD_BUF_SZ], size_t *pos, PyObject *ans) {
return true;
}
static inline bool
static bool
add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) {
if (*pos >= CMD_BUF_SZ) {
if (!append_buf(buf, pos, ans)) return false;
@@ -34,7 +34,7 @@ add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) {
return true;
}
static inline bool
static bool
read_response(int fd, monotonic_t timeout, PyObject *ans) {
static char buf[CMD_BUF_SZ];
size_t pos = 0;

View File

@@ -21,7 +21,7 @@ from .window import Watchers, Window
try:
from typing import TypedDict
except ImportError:
TypedDict = Dict[str, Any]
TypedDict = dict
class LaunchSpec(NamedTuple):

View File

@@ -12,17 +12,17 @@
extern PyTypeObject Line_Type;
extern PyTypeObject HistoryBuf_Type;
static inline CPUCell*
static CPUCell*
cpu_lineptr(LineBuf *linebuf, index_type y) {
return linebuf->cpu_cell_buf + y * linebuf->xnum;
}
static inline GPUCell*
static GPUCell*
gpu_lineptr(LineBuf *linebuf, index_type y) {
return linebuf->gpu_cell_buf + y * linebuf->xnum;
}
static inline void
static void
clear_chars_to(LineBuf* linebuf, index_type y, char_type ch) {
clear_chars_in_line(cpu_lineptr(linebuf, y), gpu_lineptr(linebuf, y), linebuf->xnum, ch);
}
@@ -118,7 +118,7 @@ dealloc(LineBuf* self) {
Py_TYPE(self)->tp_free((PyObject*)self);
}
static inline void
static void
init_line(LineBuf *lb, Line *l, index_type ynum) {
l->cpu_cells = cpu_lineptr(lb, ynum);
l->gpu_cells = gpu_lineptr(lb, ynum);
@@ -193,7 +193,7 @@ dirty_lines(LineBuf *self, PyObject *a UNUSED) {
return ans;
}
static inline bool
static bool
allocate_line_storage(Line *line, bool initialize) {
if (initialize) {
line->cpu_cells = PyMem_Calloc(line->xnum, sizeof(CPUCell));
@@ -209,7 +209,7 @@ allocate_line_storage(Line *line, bool initialize) {
return true;
}
static inline PyObject*
static PyObject*
create_line_copy_inner(LineBuf* self, index_type y) {
Line src, *line;
line = alloc_line();
@@ -247,7 +247,7 @@ copy_line_to(LineBuf *self, PyObject *args) {
Py_RETURN_NONE;
}
static inline void
static void
clear_line_(Line *l, index_type xnum) {
zero_at_ptr_count(l->cpu_cells, xnum);
zero_at_ptr_count(l->gpu_cells, xnum);
@@ -439,7 +439,7 @@ end:
Py_RETURN_NONE;
}
static inline Line*
static Line*
get_line(void *x, int y) {
LineBuf *self = (LineBuf*)x;
linebuf_init_line(self, MAX(0, y));

View File

@@ -49,7 +49,7 @@ cell_text(CPUCell *cell) {
// URL detection {{{
static inline index_type
static index_type
find_colon_slash(Line *self, index_type x, index_type limit) {
// Find :// at or before x
index_type pos = MIN(x, self->xnum - 1);
@@ -84,7 +84,7 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
return 0;
}
static inline bool
static bool
prefix_matches(Line *self, index_type at, const char_type* prefix, index_type prefix_len) {
if (prefix_len > at) return false;
index_type p, i;
@@ -94,7 +94,7 @@ prefix_matches(Line *self, index_type at, const char_type* prefix, index_type pr
return i == prefix_len;
}
static inline bool
static bool
has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_type *ans) {
for (size_t i = 0; i < OPT(url_prefixes.num); i++) {
index_type prefix_len = OPT(url_prefixes.values[i].len);
@@ -106,7 +106,7 @@ has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_ty
#define MIN_URL_LEN 5
static inline bool
static bool
has_url_beyond(Line *self, index_type x) {
for (index_type i = x; i < MIN(x + MIN_URL_LEN + 3, self->xnum); i++) {
if (!is_url_char(self->cpu_cells[i].ch)) return false;
@@ -268,7 +268,7 @@ sprite_at(Line* self, PyObject *x) {
return Py_BuildValue("HHH", c->sprite_x, c->sprite_y, c->sprite_z);
}
static inline void
static void
write_sgr(const char *val, ANSIBuf *output) {
#define W(c) output->buf[output->len++] = c
W(0x1b); W('[');
@@ -277,7 +277,7 @@ write_sgr(const char *val, ANSIBuf *output) {
#undef W
}
static inline void
static void
write_hyperlink(hyperlink_id_type hid, ANSIBuf *output) {
#define W(c) output->buf[output->len++] = c
const char *key = hid ? get_hyperlink_for_id(output->hyperlink_pool, hid, false) : NULL;
@@ -636,7 +636,7 @@ set_attribute(Line *self, PyObject *args) {
Py_RETURN_NONE;
}
static inline int
static int
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
switch(val & 0xff) {
case 1:
@@ -652,7 +652,7 @@ color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsi
}
}
static inline const char*
static const char*
decoration_as_sgr(uint8_t decoration) {
switch(decoration) {
case 1: return "4;";
@@ -722,7 +722,7 @@ line_has_mark(Line *line, attrs_type mark) {
return false;
}
static inline void
static void
report_marker_error(PyObject *marker) {
if (!PyObject_HasAttrString(marker, "error_reported")) {
PyErr_Print();
@@ -730,7 +730,7 @@ report_marker_error(PyObject *marker) {
} else PyErr_Clear();
}
static inline void
static 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;
@@ -757,7 +757,7 @@ apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int
#undef MARK
}
static inline void
static void
apply_marker(PyObject *marker, Line *line, const PyObject *text) {
unsigned int l=0, r=0, col=0, match_pos=0;
PyObject *pl = PyLong_FromVoidPtr(&l), *pr = PyLong_FromVoidPtr(&r), *pcol = PyLong_FromVoidPtr(&col);

View File

@@ -23,7 +23,7 @@ cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
}
// Read the maximum argument size for processes
static inline int
static int
get_argmax() {
int argmax;
int mib[] = { CTL_KERN, KERN_ARGMAX };
@@ -34,6 +34,25 @@ get_argmax() {
return 0;
}
static PyObject*
get_all_processes(PyObject *self UNUSED, PyObject *args UNUSED) {
pid_t num = proc_listallpids(NULL, 0);
if (num <= 0) return PyTuple_New(0);
size_t sz = sizeof(pid_t) * num * 2;
pid_t *buf = malloc(sz);
if (!buf) return PyErr_NoMemory();
num = proc_listallpids(buf, sz);
if (num <= 0) { free(buf); return PyTuple_New(0); }
PyObject *ans = PyTuple_New(num);
if (!ans) { free(buf); return NULL; }
for (pid_t i = 0; i < num; i++) {
long long pid = buf[i];
PyObject *t = PyLong_FromLongLong(pid);
if (!t) { free(buf); Py_CLEAR(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, t);
}
return ans;
}
static PyObject*
cmdline_of_process(PyObject *self UNUSED, PyObject *pid_) {
@@ -255,6 +274,7 @@ static PyMethodDef module_methods[] = {
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{"cmdline_of_process", (PyCFunction)cmdline_of_process, METH_O, ""},
{"environ_of_process", (PyCFunction)environ_of_process, METH_O, ""},
{"get_all_processes", (PyCFunction)get_all_processes, METH_NOARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};

View File

@@ -157,7 +157,7 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ())
run_app.initial_window_size_func(get_os_window_sizing_data(opts), cached_values),
pre_show_callback,
args.title or appname, args.name or args.cls or appname,
args.cls or appname, load_all_shaders)
args.cls or appname, load_all_shaders, disallow_override_title=bool(args.title))
boss = Boss(opts, args, cached_values, global_shortcuts)
boss.start(window_id)
if bad_lines:
@@ -195,10 +195,15 @@ run_app = AppRunner()
def ensure_macos_locale() -> None:
# Ensure the LANG env var is set. See
# https://github.com/kovidgoyal/kitty/issues/90
from .fast_data_types import cocoa_get_lang
from .fast_data_types import cocoa_get_lang, locale_is_valid
if 'LANG' not in os.environ:
lang = cocoa_get_lang()
if lang is not None:
if not locale_is_valid(lang):
if lang.startswith('en_'):
lang = 'en_US'
else:
log_error(f'Could not set LANG Cocoa returns language as: {lang}')
os.environ['LANG'] = lang + '.UTF-8'

View File

@@ -30,7 +30,7 @@ typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
#define EXTRA_BUTTON_INDICATOR (1 << 7)
static inline unsigned int
static unsigned int
button_map(int button) {
switch(button) {
case GLFW_MOUSE_BUTTON_LEFT:
@@ -50,7 +50,7 @@ button_map(int button) {
}
}
static inline unsigned int
static unsigned int
encode_button(unsigned int button) {
if (button >= 8 && button <= 11) {
return (button - 8) | EXTRA_BUTTON_INDICATOR;
@@ -65,7 +65,7 @@ encode_button(unsigned int button) {
static char mouse_event_buf[64];
static inline int
static int
encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_protocol, int button, MouseAction action, int mods) {
unsigned int cb = 0;
if (action == MOVE) {
@@ -161,33 +161,33 @@ dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabb
return handled;
}
static inline unsigned int
static unsigned int
window_left(Window *w) {
return w->geometry.left - w->padding.left;
}
static inline unsigned int
static unsigned int
window_right(Window *w) {
return w->geometry.right + w->padding.right;
}
static inline unsigned int
static unsigned int
window_top(Window *w) {
return w->geometry.top - w->padding.top;
}
static inline unsigned int
static unsigned int
window_bottom(Window *w) {
return w->geometry.bottom + w->padding.bottom;
}
static inline bool
static bool
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) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w));
}
static inline double
static double
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) + window_right(w)) / 2.0;
@@ -197,7 +197,7 @@ distance_to_window(Window *w) {
static bool clamp_to_window = false;
static inline bool
static bool
cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_cell, OSWindow *os_window) {
WindowGeometry *g = &w->geometry;
Screen *screen = w->render_data.screen;
@@ -232,9 +232,9 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_
return false;
}
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
#define HANDLER(name) static void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
static inline void
static void
set_mouse_cursor_when_dragging(void) {
if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) {
mouse_cursor_shape = OPT(pointer_shape_when_dragging);
@@ -242,7 +242,7 @@ set_mouse_cursor_when_dragging(void) {
}
}
static inline void
static void
update_drag(Window *w) {
Screen *screen = w->render_data.screen;
if (screen && screen->selections.in_progress) {
@@ -251,7 +251,7 @@ update_drag(Window *w) {
set_mouse_cursor_when_dragging();
}
static inline bool
static bool
do_drag_scroll(Window *w, bool upwards) {
Screen *screen = w->render_data.screen;
if (screen->linebuf == screen->main_linebuf) {
@@ -280,7 +280,7 @@ drag_scroll(Window *w, OSWindow *frame) {
return false;
}
static inline void
static void
extend_selection(Window *w, bool ended, bool extend_nearest) {
Screen *screen = w->render_data.screen;
if (screen_has_selection(screen)) {
@@ -289,12 +289,12 @@ extend_selection(Window *w, bool ended, bool extend_nearest) {
}
}
static inline void
static void
set_mouse_cursor_for_screen(Screen *screen) {
mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? OPT(default_pointer_shape): OPT(pointer_shape_when_grabbed);
}
static inline void
static void
handle_mouse_movement_in_kitty(Window *w, int button, bool mouse_cell_changed) {
Screen *screen = w->render_data.screen;
if (screen->selections.in_progress && (button == global_state.active_drag_button)) {
@@ -343,12 +343,12 @@ HANDLER(handle_move_event) {
}
}
static inline double
static double
distance(double x1, double y1, double x2, double y2) {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
static inline void
static void
clear_click_queue(Window *w, int button) {
if (0 <= button && button <= (ssize_t)arraysz(w->click_queues)) w->click_queues[button].length = 0;
}
@@ -467,7 +467,7 @@ HANDLER(handle_button_event) {
else add_press(w, button, modifiers);
}
static inline int
static int
currently_pressed_button(void) {
for (int i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++) {
if (global_state.callback_os_window->mouse_button_pressed[i]) return i;
@@ -485,7 +485,7 @@ HANDLER(handle_event) {
}
}
static inline void
static void
handle_tab_bar_mouse(int button, int UNUSED modifiers) {
static monotonic_t last_click_at = 0;
if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.callback_os_window->mouse_button_pressed[button]) return;
@@ -495,7 +495,7 @@ handle_tab_bar_mouse(int button, int UNUSED modifiers) {
call_boss(activate_tab_at, "KdO", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, is_double ? Py_True : Py_False);
}
static inline bool
static bool
mouse_in_region(Region *r) {
if (r->left == r->right) return false;
if (global_state.callback_os_window->mouse_y < r->top || global_state.callback_os_window->mouse_y > r->bottom) return false;
@@ -503,7 +503,7 @@ mouse_in_region(Region *r) {
return true;
}
static inline Window*
static Window*
window_for_id(id_type window_id) {
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++) {
@@ -513,7 +513,7 @@ window_for_id(id_type window_id) {
return NULL;
}
static inline Window*
static Window*
window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
Region central, tab_bar;
os_window_regions(global_state.callback_os_window, &central, &tab_bar);
@@ -530,7 +530,7 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
return NULL;
}
static inline Window*
static Window*
closest_window_for_event(unsigned int *window_idx) {
Window *ans = NULL;
double closest_distance = UINT_MAX;

View File

@@ -499,6 +499,14 @@ of URLs with a plain click::
automatically end it and no release event will be dispatched.
''')
opt('clear_all_mouse_actions', 'no',
option_type='clear_all_mouse_actions',
long_text='''
You can have kitty remove all mouse actions seen up to this point.
Useful, for instance, to remove the default mouse actions.
'''
)
mma('Click the link under the mouse cursor when no selection is created',
'click_url_or_select left click ungrabbed mouse_click_url_or_select',
)
@@ -555,6 +563,10 @@ mma('Paste from the primary selection even when grabbed',
'paste_selection_grabbed shift+middle release ungrabbed,grabbed paste_selection',
)
mma('Discard press event for middle click paste',
'paste_selection_grabbed shift+middle press grabbed discard_event',
)
mma('Start selecting text even when grabbed',
'start_simple_selection_grabbed shift+left press ungrabbed,grabbed mouse_selection normal',
)
@@ -872,15 +884,22 @@ tab.
opt('tab_bar_style', 'fade',
choices=('fade', 'hidden', 'powerline', 'separator'), ctype='!tab_bar_style',
choices=('fade', 'hidden', 'powerline', 'separator', 'slant'), ctype='!tab_bar_style',
long_text='''
The tab bar style, can be one of: :code:`fade`, :code:`separator`,
:code:`powerline`, or :code:`hidden`. In the fade style, each tab's edges fade
into the background color, in the separator style, tabs are separated by a
configurable separator, and the powerline shows the tabs as a continuous line.
If you use the hidden style, you might want to create a mapping for the
:code:`select_tab` action which presents you with a list of tabs and allows for
easy switching to a tab.
The tab bar style, can be one of:
:code:`fade`
Each tab's edges fade into the background color (see :opt:`tab_fade`)
:code:`slant`
Tabs look like the tabs in a physical file
:code:`separator`
Tabs are separated by a configurable separator (see :opt:`tab_separator`)
:code:`powerline`
Tabs are shown as a continuous line with "fancy" separators (see :opt:`tab_powerline_style`)
:code:`hidden`
The tab bar is hidden. If you use this, you might want to create a mapping
for the :ref:`action-select_tab` action which presents you with a list
of tabs and allows for easy switching to a tab.
'''
)
@@ -2451,8 +2470,7 @@ running within kitty to control it, with :code:`yes` or only programs that
connect to the socket specified with the :option:`kitty --listen-on` command
line option, if you use the value :code:`socket-only`. The latter is useful if
you want to prevent programs running on a remote computer over ssh from
controlling kitty. Changing this option by reloading the config will only affect
newly created windows.
controlling kitty. Reloading the config will not affect this setting.
'''
)
@@ -2515,10 +2533,17 @@ control exactly which actions are allowed. The set of possible actions is:
write-clipboard read-clipboard write-primary read-primary. The default is to
allow writing to the clipboard and primary selection. Note that enabling the
read functionality is a security risk as it means that any program, even one
running on a remote server via SSH can read your clipboard.
running on a remote server via SSH can read your clipboard. See also :opt:`
clipboard_max_size`.
'''
)
opt('clipboard_max_size', 64, option_type='positive_float', long_text='''
The maximum size (in MB) of data from programs running in kitty that will be
stored for writing to the system clipboard. See also :opt:`clipboard_control`.
A value of zero means no size limit is applied.
''')
opt('allow_hyperlinks', 'yes',
option_type='allow_hyperlinks', ctype='bool',
long_text='''

12
kitty/options/parse.py generated
View File

@@ -8,8 +8,8 @@ from kitty.conf.utils import (
)
from kitty.options.utils import (
active_tab_title_template, adjust_baseline, adjust_line_height, allow_hyperlinks,
allow_remote_control, box_drawing_scale, clear_all_shortcuts, clipboard_control,
config_or_absolute_path, copy_on_select, cursor_text_color,
allow_remote_control, box_drawing_scale, clear_all_mouse_actions, clear_all_shortcuts,
clipboard_control, config_or_absolute_path, copy_on_select, cursor_text_color,
deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias,
deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations,
kitten_alias, macos_option_as_alt, macos_titlebar_color, optional_edge_width, parse_map,
@@ -90,6 +90,9 @@ class Parser:
def box_drawing_scale(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['box_drawing_scale'] = box_drawing_scale(val)
def clear_all_mouse_actions(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
clear_all_mouse_actions(val, ans)
def clear_all_shortcuts(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
clear_all_shortcuts(val, ans)
@@ -99,6 +102,9 @@ class Parser:
def clipboard_control(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['clipboard_control'] = clipboard_control(val)
def clipboard_max_size(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['clipboard_max_size'] = positive_float(val)
def close_on_child_death(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['close_on_child_death'] = to_bool(val)
@@ -1167,7 +1173,7 @@ class Parser:
raise ValueError(f"The value {val} is not a valid choice for tab_bar_style")
ans["tab_bar_style"] = val
choices_for_tab_bar_style = frozenset(('fade', 'hidden', 'powerline', 'separator'))
choices_for_tab_bar_style = frozenset(('fade', 'hidden', 'powerline', 'separator', 'slant'))
def tab_fade(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['tab_fade'] = tab_fade(val)

13
kitty/options/types.py generated
View File

@@ -23,7 +23,7 @@ if typing.TYPE_CHECKING:
choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'hand']
choices_for_pointer_shape_when_grabbed = typing.Literal['arrow', 'beam', 'hand']
choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart']
choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator']
choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator', 'slant']
choices_for_tab_powerline_style = typing.Literal['angled', 'round', 'slanted']
choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right']
else:
@@ -61,9 +61,11 @@ option_names = ( # {{{
'bold_font',
'bold_italic_font',
'box_drawing_scale',
'clear_all_mouse_actions',
'clear_all_shortcuts',
'click_interval',
'clipboard_control',
'clipboard_max_size',
'close_on_child_death',
'color0',
'color1',
@@ -453,9 +455,11 @@ class Options:
bold_font: str = 'auto'
bold_italic_font: str = 'auto'
box_drawing_scale: typing.Tuple[float, float, float, float] = (0.001, 1.0, 1.5, 2.0)
clear_all_mouse_actions: bool = False
clear_all_shortcuts: bool = False
click_interval: float = -1.0
clipboard_control: typing.Tuple[str, ...] = ('write-clipboard', 'write-primary')
clipboard_max_size: float = 64.0
close_on_child_death: bool = False
command_on_bell: typing.List[str] = ['none']
confirm_os_window_close: int = 0
@@ -608,8 +612,11 @@ class Options:
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
self.color_table = array(self.color_table.typecode, self.color_table)
if options_dict is not None:
null = object()
for key in option_names:
setattr(self, key, options_dict[key])
val = options_dict.get(key, null)
if val is not null:
setattr(self, key, val)
@property
def _fields(self) -> typing.Tuple[str, ...]:
@@ -886,6 +893,8 @@ defaults.mouse_map = [
MouseMapping(2, 1, -1, True, KeyAction('paste_selection')),
# paste_selection_grabbed
MouseMapping(2, 1, -1, False, KeyAction('paste_selection')),
# paste_selection_grabbed
MouseMapping(2, 1, 1, True, KeyAction('discard_event')),
# start_simple_selection_grabbed
MouseMapping(0, 1, 1, True, KeyAction('mouse_selection', (0,))),
# start_simple_selection_grabbed

View File

@@ -696,6 +696,13 @@ def tab_bar_margin_height(x: str) -> TabBarMarginHeight:
return TabBarMarginHeight(next(ans), next(ans))
def clear_all_mouse_actions(val: str, dict_with_parse_results: Optional[Dict[str, Any]] = None) -> bool:
ans = to_bool(val)
if ans and dict_with_parse_results is not None:
dict_with_parse_results['mouse_map'] = [None]
return ans
def clear_all_shortcuts(val: str, dict_with_parse_results: Optional[Dict[str, Any]] = None) -> bool:
ans = to_bool(val)
if ans and dict_with_parse_results is not None:

View File

@@ -23,7 +23,7 @@ static const uint64_t pow_10_array[] = {
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000
};
static inline int64_t
static int64_t
utoi(const uint32_t *buf, unsigned int sz) {
int64_t ans = 0;
const uint32_t *p = buf;
@@ -45,7 +45,7 @@ utoi(const uint32_t *buf, unsigned int sz) {
}
static inline const char*
static const char*
utf8(char_type codepoint) {
if (!codepoint) return "";
static char buf[8];
@@ -313,7 +313,7 @@ dispatch_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_c
// OSC mode {{{
static inline bool
static bool
parse_osc_8(char *buf, char **id, char **url) {
char *boundary = strstr(buf, ";");
if (boundary == NULL) return false;
@@ -331,7 +331,7 @@ parse_osc_8(char *buf, char **id, char **url) {
return true;
}
static inline void
static void
dispatch_hyperlink(Screen *screen, size_t pos, size_t size, PyObject DUMP_UNUSED *dump_callback) {
// since the spec says only ASCII printable chars are allowed in OSC 8, we
// can just convert to char* directly
@@ -481,14 +481,14 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case '/':
static inline void
static void
screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, false, -1); }
static inline void
static void
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
static inline void
static void
screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1u, count); i++) screen_tab(s); }
static inline const char*
static const char*
repr_csi_params(int *params, unsigned int num_params) {
if (!num_params) return "";
static char buf[256];
@@ -633,7 +633,7 @@ parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, int *params, PyObject
#undef SEND_SGR
}
static inline unsigned int
static unsigned int
parse_region(Region *r, uint32_t *buf, unsigned int num) {
unsigned int i, start, num_params = 0;
int params[8] = {0};
@@ -668,7 +668,7 @@ parse_region(Region *r, uint32_t *buf, unsigned int num) {
return i;
}
static inline const char*
static const char*
csi_letter(unsigned code) {
static char buf[8];
if (33 <= code && code <= 126) snprintf(buf, sizeof(buf), "%c", code);
@@ -676,7 +676,7 @@ csi_letter(unsigned code) {
return buf;
}
static inline void
static void
dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#define AT_MOST_ONE_PARAMETER { \
if (num_params > 1) { \
@@ -996,7 +996,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
// DCS mode {{{
static inline bool
static bool
startswith(const uint32_t *string, size_t sz, const char *prefix) {
size_t l = strlen(prefix);
if (sz < l) return false;
@@ -1008,7 +1008,7 @@ startswith(const uint32_t *string, size_t sz, const char *prefix) {
#define PENDING_MODE_CHAR '='
static inline void
static void
dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (screen->parser_buf_pos < 2) return;
switch(screen->parser_buf[0]) {
@@ -1077,7 +1077,7 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#include "parse-graphics-command.h"
static inline void
static void
dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (screen->parser_buf_pos < 2) return;
switch(screen->parser_buf[0]) {
@@ -1093,7 +1093,7 @@ dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
// }}}
// PM mode {{{
static inline void
static void
dispatch_pm(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (screen->parser_buf_pos < 2) return;
switch(screen->parser_buf[0]) {
@@ -1144,7 +1144,7 @@ accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback,
return false;
}
static inline bool
static bool
accumulate_dcs(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
switch(ch) {
case ST:
@@ -1174,7 +1174,7 @@ END_ALLOW_CASE_RANGE
}
static inline bool
static bool
accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
switch(ch) {
case ST:
@@ -1200,7 +1200,7 @@ accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
}
static inline bool
static bool
accumulate_csi(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
#define ENSURE_SPACE \
if (screen->parser_buf_pos > PARSER_BUF_SZ - 1) { \
@@ -1329,14 +1329,14 @@ extern uint32_t *latin1_charset;
} \
}
static inline void
static void
_parse_bytes(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
unsigned int i;
decode_loop(dispatch, ;);
FLUSH_DRAW;
}
static inline size_t
static size_t
_parse_bytes_watching_for_pending(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
unsigned int i;
decode_loop(dispatch, if (screen->pending_mode.activated_at) goto end);

View File

@@ -76,13 +76,15 @@ ArgsType = List[str]
MATCH_WINDOW_OPTION = '''\
--match -m
The window to match. Match specifications are of the form:
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env.
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env and recent.
You can use the :italic:`ls` command to get a list of windows. Note that for
numeric fields such as id, pid and num the expression is interpreted as a number,
numeric fields such as id, pid, recent and num the expression is interpreted as a number,
not a regular expression. The field num refers to the window position in the current tab,
starting from zero and counting clockwise (this is the same as the order in which the
windows are reported by the :italic:`ls` command). The window id of the current window
is available as the KITTY_WINDOW_ID environment variable. When using the :italic:`env` field
is available as the KITTY_WINDOW_ID environment variable. The field recent refers to recently
active windows in the currently active tab, with zero being the currently active window, one being the previously active
window and so on. When using the :italic:`env` field
to match on environment variables you can specify only the environment variable name or a name
and value, for example, :italic:`env:MY_ENV_VAR=2`
'''
@@ -90,14 +92,16 @@ MATCH_TAB_OPTION = '''\
--match -m
The tab to match. Match specifications are of the form:
:italic:`field:regexp`. Where field can be one of:
id, index, title, window_id, window_title, pid, cwd, env, cmdline.
id, index, title, window_id, window_title, pid, cwd, env, cmdline and recent.
You can use the :italic:`ls` command to get a list of tabs. Note that for
numeric fields such as id, index and pid the expression is interpreted as a number,
numeric fields such as id, recent, index and pid the expression is interpreted as a number,
not a regular expression. When using title or id, first a matching tab is
looked for and if not found a matching window is looked for, and the tab
for that window is used. You can also use window_id and window_title to match
the tab that contains the window with the specified id or title. The index number
is used to match the nth tab in the currently active OS window.
is used to match the nth tab in the currently active OS window. The recent number
matches recently active tabs in the currently active OS window, with zero being the currently
active tab, one the previously active tab and so on.
'''

12
kitty/rgb.py generated
View File

@@ -4,7 +4,7 @@
import re
from contextlib import suppress
from typing import Optional, NamedTuple
from typing import NamedTuple, Optional, Tuple
class Color(NamedTuple):
@@ -12,6 +12,12 @@ class Color(NamedTuple):
green: int = 0
blue: int = 0
def __truediv__(self, denom: float) -> Tuple[float, float, float]:
return self.red / denom, self.green / denom, self.blue / denom
def as_sgr(self) -> str:
return ':2:{}:{}:{}'.format(*self)
def luminance(self) -> float:
return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
@@ -77,7 +83,7 @@ def color_as_sharp(x: Color) -> str:
def color_as_sgr(x: Color) -> str:
return ':2:{}:{}:{}'.format(*x)
return x.as_sgr()
def to_color(raw: str, validate: bool = False) -> Optional[Color]:
@@ -856,8 +862,8 @@ color_names = {
if __name__ == '__main__':
# Read RGB color table from specified rgb.txt file
import sys
import pprint
import sys
data = {}
with open(sys.argv[-1]) as f:
for line in f:

View File

@@ -26,7 +26,7 @@
#include <sys/param.h>
#include <assert.h>
static inline size_t
static size_t
size_t_min(size_t x, size_t y) {
return x > y ? y : x;
}

View File

@@ -32,14 +32,14 @@ static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECA
// Constructor/destructor {{{
static inline void
static void
clear_selection(Selections *selections) {
selections->in_progress = false;
selections->extend_mode = EXTEND_CELL;
selections->count = 0;
}
static inline void
static void
init_tabstops(bool *tabstops, index_type count) {
// In terminfo we specify the number of initial tabstops (it) as 8
for (unsigned int t=0; t < count; t++) {
@@ -47,7 +47,7 @@ init_tabstops(bool *tabstops, index_type count) {
}
}
static inline bool
static bool
init_overlay_line(Screen *self, index_type columns) {
PyMem_Free(self->overlay_line.cpu_cells);
PyMem_Free(self->overlay_line.gpu_cells);
@@ -146,7 +146,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
}
static void deactivate_overlay_line(Screen *self);
static inline Line* range_line_(Screen *self, int y);
static Line* range_line_(Screen *self, int y);
void
screen_reset(Screen *self) {
@@ -195,7 +195,7 @@ screen_dirty_sprite_positions(Screen *self) {
for (index_type i = 0; i < self->historybuf->count; i++) historybuf_mark_line_dirty(self->historybuf, i);
}
static inline HistoryBuf*
static HistoryBuf*
realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns, ANSIBuf *as_ansi_buf) {
HistoryBuf *ans = alloc_historybuf(lines, columns, 0);
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
@@ -213,7 +213,7 @@ typedef struct CursorTrack {
struct { index_type x, y; } temp;
} CursorTrack;
static inline LineBuf*
static LineBuf*
realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb, CursorTrack *a, CursorTrack *b, ANSIBuf *as_ansi_buf) {
LineBuf *ans = alloc_linebuf(lines, columns);
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
@@ -223,13 +223,13 @@ realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *n
return ans;
}
static inline bool
static bool
is_selection_empty(const Selection *s) {
int start_y = (int)s->start.y - (int)s->start_scrolled_by, end_y = (int)s->end.y - (int)s->end_scrolled_by;
return s->start.x == s->end.x && s->start.in_left_half_of_cell == s->end.in_left_half_of_cell && start_y == end_y;
}
static inline void
static void
index_selection(const Screen *self, Selections *selections, bool up) {
for (size_t i = 0; i < selections->count; i++) {
Selection *s = selections->items + i;
@@ -239,6 +239,8 @@ index_selection(const Screen *self, Selections *selections, bool up) {
s->start.y--;
if (s->input_start.y) s->input_start.y--;
if (s->input_current.y) s->input_current.y--;
if (s->initial_extent.start.y) s->initial_extent.start.y--;
if (s->initial_extent.end.y) s->initial_extent.end.y--;
}
if (s->end.y == 0) s->end_scrolled_by += 1;
else s->end.y--;
@@ -423,7 +425,7 @@ screen_designate_charset(Screen *self, uint32_t which, uint32_t as) {
}
}
static inline void
static void
move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) {
self->cursor->x = xpos; self->cursor->y = ypos;
CPUCell src_cpu = *cpu_cell, *dest_cpu;
@@ -448,7 +450,7 @@ move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type
*dest_gpu = src_gpu;
}
static inline bool
static bool
selection_has_screen_line(const Selections *selections, const int y) {
for (size_t i = 0; i < selections->count; i++) {
const Selection *s = selections->items + i;
@@ -498,11 +500,11 @@ remap_hyperlink_ids(Screen *self, hyperlink_id_type *map) {
}
static inline bool is_flag_pair(char_type a, char_type b) {
static bool is_flag_pair(char_type a, char_type b) {
return is_flag_codepoint(a) && is_flag_codepoint(b);
}
static inline bool
static bool
draw_second_flag_codepoint(Screen *self, char_type ch) {
index_type xpos = 0, ypos = 0;
if (self->cursor->x > 1) {
@@ -523,7 +525,7 @@ draw_second_flag_codepoint(Screen *self, char_type ch) {
return true;
}
static inline void
static void
draw_combining_char(Screen *self, char_type ch) {
bool has_prev_char = false;
index_type xpos = 0, ypos = 0;
@@ -715,12 +717,12 @@ select_graphic_rendition(Screen *self, int *params, unsigned int count, Region *
} else cursor_from_sgr(self->cursor, params, count);
}
static inline void
static void
write_to_test_child(Screen *self, const char *data, size_t sz) {
PyObject *r = PyObject_CallMethod(self->test_child, "write", "y#", data, sz); if (r == NULL) PyErr_Print(); Py_CLEAR(r);
}
static inline void
static void
write_to_child(Screen *self, const char *data, size_t sz) {
if (self->window_id) schedule_write_to_child(self->window_id, 1, data, sz);
if (self->test_child != Py_None) { write_to_test_child(self, data, sz); }
@@ -762,7 +764,7 @@ write_escape_code_to_child(Screen *self, unsigned char which, const char *data)
}
}
static inline bool
static bool
cursor_within_margins(Screen *self) {
return self->margin_top <= self->cursor->y && self->cursor->y <= self->margin_bottom;
}
@@ -1512,11 +1514,7 @@ screen_use_latin1(Screen *self, bool on) {
bool
screen_invert_colors(Screen *self) {
bool inverted = false;
if (self->start_visual_bell_at > 0) {
if (monotonic() - self->start_visual_bell_at <= OPT(visual_bell_duration)) inverted = true;
else self->start_visual_bell_at = 0;
}
if (self->modes.mDECSCNM) inverted = inverted ? false : true;
if (self->modes.mDECSCNM) inverted = true;
return inverted;
}
@@ -1796,20 +1794,20 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
// }}}
// Rendering {{{
static inline void
static void
update_line_data(Line *line, unsigned int dest_y, uint8_t *data) {
size_t base = sizeof(GPUCell) * dest_y * line->xnum;
memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell));
}
static inline void
static void
screen_reset_dirty(Screen *self) {
self->is_dirty = false;
self->history_line_added_count = 0;
}
static inline bool
static bool
screen_has_marker(Screen *self) {
return self->marker != NULL;
}
@@ -1980,7 +1978,7 @@ iteration_data(const Screen *self, const Selection *sel, IterationData *ans, int
ans->y = MAX(ans->y, min_y);
}
static inline XRange
static XRange
xrange_for_iteration(const IterationData *idata, const int y, const Line *line) {
XRange ans = {.x_limit=xlimit_for_line(line)};
if (y == idata->y) {
@@ -1996,7 +1994,7 @@ xrange_for_iteration(const IterationData *idata, const int y, const Line *line)
return ans;
}
static inline bool
static bool
iteration_data_is_empty(const Screen *self, const IterationData *idata) {
if (idata->y >= idata->y_limit) return true;
index_type xl = MIN(idata->first.x_limit, self->columns);
@@ -2008,7 +2006,7 @@ iteration_data_is_empty(const Screen *self, const IterationData *idata) {
return true;
}
static inline void
static void
apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) {
iteration_data(self, s, &s->last_rendered, -self->historybuf->count, true);
@@ -2047,7 +2045,7 @@ screen_apply_selection(Screen *self, void *address, size_t size) {
self->url_ranges.last_rendered_count = self->url_ranges.count;
}
static inline PyObject*
static PyObject*
text_for_range(Screen *self, const Selection *sel, bool insert_newlines) {
IterationData idata;
iteration_data(self, sel, &idata, -self->historybuf->count, false);
@@ -2065,7 +2063,7 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines) {
return ans;
}
static inline hyperlink_id_type
static hyperlink_id_type
hyperlink_id_for_range(Screen *self, const Selection *sel) {
IterationData idata;
iteration_data(self, sel, &idata, -self->historybuf->count, false);
@@ -2079,7 +2077,7 @@ hyperlink_id_for_range(Screen *self, const Selection *sel) {
return 0;
}
static inline PyObject*
static PyObject*
extend_tuple(PyObject *a, PyObject *b) {
Py_ssize_t bs = PyBytes_GET_SIZE(b);
if (bs < 1) return a;
@@ -2291,7 +2289,7 @@ as_text_non_visual(Screen *self, PyObject *args) {
return as_text_generic(args, self, get_range_line, self->lines, &self->as_ansi_buf);
}
static inline PyObject*
static PyObject*
as_text_generic_wrapper(Screen *self, PyObject *args, get_line_func get_line) {
return as_text_generic(args, self, get_line, self->lines, &self->as_ansi_buf);
}
@@ -2599,7 +2597,7 @@ screen_selection_range_for_line(Screen *self, index_type y, index_type *start, i
return true;
}
static inline bool
static bool
is_opt_word_char(char_type ch) {
if (OPT(select_by_word_characters)) {
for (const char_type *p = OPT(select_by_word_characters); *p; p++) {
@@ -2722,7 +2720,7 @@ screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_ha
#undef A
}
static inline void
static void
add_url_range(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) {
#define A(attr, val) r->attr = val;
ensure_space_for(&self->url_ranges, items, Selection, self->url_ranges.count + 8, capacity, 8, false);
@@ -2827,12 +2825,12 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
s->input_current.x = x; s->input_current.y = y;
s->input_current.in_left_half_of_cell = in_left_half_of_cell;
SelectionBoundary start, end, *a = &s->start, *b = &s->end, abs_start, abs_end, abs_current_input;
#define absy(b, scrolled_by) b.y = scrolled_by + self->lines - 1 - b.y
abs_start = s->start; absy(abs_start, s->start_scrolled_by);
abs_end = s->end; absy(abs_end, s->end_scrolled_by);
abs_current_input = s->input_current; absy(abs_current_input, self->scrolled_by);
#undef absy
if (upd.set_as_nearest_extend) {
#define set_abs(which, initializer, scrolled_by) which = initializer; which.y = scrolled_by + self->lines - 1 - which.y;
set_abs(abs_start, s->start, s->start_scrolled_by);
set_abs(abs_end, s->end, s->end_scrolled_by);
set_abs(abs_current_input, s->input_current, self->scrolled_by);
if (upd.set_as_nearest_extend || self->selections.extension_in_progress) {
self->selections.extension_in_progress = true;
bool start_is_nearer = false;
if (self->selections.extend_mode == EXTEND_LINE || self->selections.extend_mode == EXTEND_LINE_FROM_POINT) {
if (abs_start.y == abs_end.y) {
@@ -2843,7 +2841,25 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
}
} else start_is_nearer = num_cells_between_selection_boundaries(self, &abs_start, &abs_current_input) < num_cells_between_selection_boundaries(self, &abs_end, &abs_current_input);
if (start_is_nearer) s->adjusting_start = true;
} else if (!upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) {
SelectionBoundary abs_initial_start, abs_initial_end;
set_abs(abs_initial_start, s->initial_extent.start, s->initial_extent.scrolled_by);
set_abs(abs_initial_end, s->initial_extent.end, s->initial_extent.scrolled_by);
if (self->selections.extend_mode == EXTEND_WORD) {
s->adjusting_start = selection_boundary_less_than(&abs_current_input, &abs_initial_end);
} else {
const unsigned int initial_line = abs_initial_start.y;
if (initial_line == abs_current_input.y) {
s->adjusting_start = false;
s->start = s->initial_extent.start; s->start_scrolled_by = s->initial_extent.scrolled_by;
s->end = s->initial_extent.end; s->end_scrolled_by = s->initial_extent.scrolled_by;
}
else {
s->adjusting_start = abs_current_input.y > initial_line;
}
}
}
#undef set_abs
bool adjusted_boundary_is_before;
if (s->adjusting_start) adjusted_boundary_is_before = selection_boundary_less_than(&abs_start, &abs_end);
else { adjusted_boundary_is_before = selection_boundary_less_than(&abs_end, &abs_start); }
@@ -2923,7 +2939,13 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
}
if (!self->selections.in_progress) {
s->adjusting_start = false;
self->selections.extension_in_progress = false;
call_boss(set_primary_selection, NULL);
} else {
if (upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) {
s->initial_extent.start = s->start; s->initial_extent.end = s->end;
s->initial_extent.scrolled_by = s->start_scrolled_by;
}
}
}
@@ -2961,7 +2983,7 @@ send_escape_code_to_child(Screen *self, PyObject *args) {
Py_RETURN_NONE;
}
static inline void
static void
screen_mark_all(Screen *self) {
for (index_type y = 0; y < self->main_linebuf->ynum; y++) {
linebuf_init_line(self->main_linebuf, y);

View File

@@ -42,12 +42,16 @@ typedef struct {
bool rectangle_select, adjusting_start;
IterationData last_rendered;
int sort_y, sort_x;
struct {
SelectionBoundary start, end;
unsigned int scrolled_by;
} initial_extent;
} Selection;
typedef struct {
Selection *items;
size_t count, capacity, last_rendered_count;
bool in_progress;
bool in_progress, extension_in_progress;
SelectionExtendMode extend_mode;
} Selections;

View File

@@ -90,16 +90,16 @@ class Session:
self.tabs[-1].cwd = val
def parse_session(raw: str, opts: Options, default_title: Optional[str] = None) -> Generator[Session, None, None]:
def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
def finalize_session(ans: Session) -> Session:
from .tabs import SpecialWindow
for t in ans.tabs:
if not t.windows:
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), override_title=default_title))
t.windows.append(SpecialWindow(cmd=resolved_shell(opts)))
return ans
ans = Session(default_title)
ans = Session()
ans.add_tab(opts)
for line in raw.splitlines():
line = line.strip()
@@ -114,7 +114,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
ans.add_tab(opts, rest)
elif cmd == 'new_os_window':
yield finalize_session(ans)
ans = Session(default_title)
ans = Session()
ans.add_tab(opts, rest)
elif cmd == 'layout':
ans.set_layout(rest)
@@ -164,7 +164,7 @@ def create_sessions(
f = open(args.session)
with f:
session_data = f.read()
yield from parse_session(session_data, opts, getattr(args, 'title', None))
yield from parse_session(session_data, opts)
return
if default_session and default_session != 'none':
try:
@@ -173,7 +173,7 @@ def create_sessions(
except OSError:
log_error('Failed to read from session file, ignoring: {}'.format(default_session))
else:
yield from parse_session(session_data, opts, getattr(args, 'title', None))
yield from parse_session(session_data, opts)
return
default_watchers = args.watcher if args else ()
ans = Session(default_watchers=default_watchers)
@@ -186,7 +186,6 @@ def create_sessions(
cmd = [kitty_exe(), '+hold'] + cmd
from kitty.tabs import SpecialWindow
cwd: Optional[str] = args.directory if respect_cwd and args else None
title = getattr(args, 'title', None)
special_window = SpecialWindow(cmd, override_title=title, cwd_from=cwd_from, cwd=cwd)
special_window = SpecialWindow(cmd, cwd_from=cwd_from, cwd=cwd)
ans.add_special_window(special_window)
yield ans

View File

@@ -109,7 +109,7 @@ realloc_sprite_texture(FONTS_DATA_HANDLE fg) {
sprite_map->texture_id = tex;
}
static inline void
static void
ensure_sprite_map(FONTS_DATA_HANDLE fg) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
if (!sprite_map->texture_id) realloc_sprite_texture(fg);
@@ -241,7 +241,7 @@ struct CellUniformData {
static struct CellUniformData cell_uniform_data = {0, .prev_inactive_text_alpha=-1};
static inline void
static void
send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRenderData *render_data) {
size_t sz = sizeof(GLfloat) * 16 * image_count;
GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY);
@@ -249,7 +249,7 @@ send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRende
unmap_vao_buffer(gvao_idx, 0); a = NULL;
}
static inline void
static void
cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted, OSWindow *os_window) {
struct CellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
@@ -306,7 +306,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL;
}
static inline bool
static bool
cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) {
size_t sz;
CELL_BUFFERS;
@@ -440,7 +440,7 @@ draw_cells_simple(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
}
}
static inline bool
static bool
has_bgimage(OSWindow *w) {
return w->bgimage && w->bgimage->texture_id > 0;
}
@@ -458,6 +458,27 @@ draw_tint(bool premult, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
static void
draw_visual_bell_flash(GLfloat intensity, GLfloat xstart, GLfloat ystart, GLfloat w, GLfloat h, Screen *screen) {
glEnable(GL_BLEND);
// BLEND_PREMULT
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
bind_program(TINT_PROGRAM);
GLfloat attenuation = 0.4f;
const color_type flash = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg);
#define C(shift) ((((GLfloat)((flash >> shift) & 0xFF)) / 255.0f) )
const GLfloat r = C(16), g = C(8), b = C(0);
const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b);
#undef C
#define C(x) (x * intensity * attenuation)
if (max_channel > 0.45) attenuation = 0.6f; // light color
glUniform4f(tint_program_layout.tint_color_location, C(r), C(g), C(b), C(1));
#undef C
glUniform4f(tint_program_layout.edges_location, xstart, ystart - h, xstart + w, ystart);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisable(GL_BLEND);
}
static void
draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *w, GLfloat xstart, GLfloat ystart, GLfloat width, GLfloat height) {
glEnable(GL_BLEND);
@@ -570,7 +591,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen
glDisable(GL_BLEND);
}
static inline void
static void
set_cell_uniforms(float current_inactive_text_alpha, bool force) {
if (!cell_uniform_data.constants_set || force) {
cell_uniform_data.gploc = glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "inactive_text_alpha");
@@ -613,6 +634,36 @@ send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat
return changed;
}
static float
ease_out_cubic(float phase) {
return 1.0f - powf(1.0f - phase, 3.0f);
}
static float
ease_in_out_cubic(float phase) {
return phase < 0.5f ?
4.0f * powf(phase, 3.0f) :
1.0f - powf(-2.0f * phase + 2.0f, 3.0f) / 2.0f;
}
static float
visual_bell_intensity(float phase) {
static const float peak = 0.2f;
const float fade = 1.0f - peak;
return phase < peak ? ease_out_cubic(phase / peak) : ease_in_out_cubic((1.0f - phase) / fade);
}
static float
get_visual_bell_intensity(Screen *screen) {
if (screen->start_visual_bell_at > 0) {
monotonic_t progress = monotonic() - screen->start_visual_bell_at;
monotonic_t duration = OPT(visual_bell_duration);
if (progress <= duration) return visual_bell_intensity((float)progress / duration);
screen->start_visual_bell_at = 0;
}
return 0.0f;
}
void
draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window, bool is_active_window, bool can_be_focused) {
CELL_BUFFERS;
@@ -650,6 +701,11 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
vao_idx, gvao_idx, screen, os_window, xstart, ystart, w, h);
else draw_cells_simple(vao_idx, gvao_idx, screen);
}
if (screen->start_visual_bell_at) {
GLfloat intensity = get_visual_bell_intensity(screen);
if (intensity > 0.0f) draw_visual_bell_flash(intensity, xstart, ystart, w, h, screen);
}
}
// }}}

View File

@@ -196,7 +196,7 @@ add_os_window() {
return ans;
}
static inline id_type
static id_type
add_tab(id_type os_window_id) {
WITH_OS_WINDOW(os_window_id)
make_os_window_context_current(os_window);
@@ -209,13 +209,13 @@ add_tab(id_type os_window_id) {
return 0;
}
static inline void
static void
create_gpu_resources_for_window(Window *w) {
w->render_data.vao_idx = create_cell_vao();
w->render_data.gvao_idx = create_graphics_vao();
}
static inline void
static void
release_gpu_resources_for_window(Window *w) {
if (w->render_data.vao_idx > -1) remove_vao(w->render_data.vao_idx);
w->render_data.vao_idx = -1;
@@ -223,7 +223,7 @@ release_gpu_resources_for_window(Window *w) {
w->render_data.gvao_idx = -1;
}
static inline void
static void
initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
w->id = ++global_state.window_id_counter;
w->visible = true;
@@ -236,7 +236,7 @@ initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
}
}
static inline id_type
static id_type
add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
WITH_TAB(os_window_id, tab_id);
ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
@@ -248,7 +248,7 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
return 0;
}
static inline void
static void
update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < tab->num_windows; i++) {
@@ -264,6 +264,7 @@ update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyO
void
set_os_window_title_from_window(Window *w, OSWindow *os_window) {
if (os_window->disallow_title_changes) return;
if (w->title && w->title != os_window->window_title) {
Py_XDECREF(os_window->window_title);
os_window->window_title = w->title;
@@ -283,13 +284,13 @@ update_os_window_title(OSWindow *os_window) {
}
}
static inline void
static void
destroy_window(Window *w) {
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
release_gpu_resources_for_window(w);
}
static inline void
static void
remove_window_inner(Tab *tab, id_type id) {
id_type active_window_id = 0;
if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id;
@@ -303,7 +304,7 @@ remove_window_inner(Tab *tab, id_type id) {
}
}
static inline void
static void
remove_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
make_os_window_context_current(osw);
@@ -325,7 +326,7 @@ add_detached_window(Window *w) {
memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window));
}
static inline void
static void
detach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < tab->num_windows; i++) {
@@ -342,7 +343,7 @@ detach_window(id_type os_window_id, id_type tab_id, id_type id) {
}
static inline void
static void
resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
if (screen) {
screen->cell_size.width = os_window->fonts_data->cell_width;
@@ -352,7 +353,7 @@ resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
}
}
static inline void
static void
attach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < detached_windows.num_windows; i++) {
@@ -376,7 +377,7 @@ attach_window(id_type os_window_id, id_type tab_id, id_type id) {
END_WITH_TAB;
}
static inline void
static void
destroy_tab(Tab *tab) {
for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id);
remove_vao(tab->border_rects.vao_idx);
@@ -384,7 +385,7 @@ destroy_tab(Tab *tab) {
free(tab->windows); tab->windows = NULL;
}
static inline void
static void
remove_tab_inner(OSWindow *os_window, id_type id) {
id_type active_tab_id = 0;
if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id;
@@ -399,14 +400,14 @@ remove_tab_inner(OSWindow *os_window, id_type id) {
}
}
static inline void
static void
remove_tab(id_type os_window_id, id_type id) {
WITH_OS_WINDOW(os_window_id)
remove_tab_inner(os_window, id);
END_WITH_OS_WINDOW
}
static inline void
static void
destroy_os_window_item(OSWindow *w) {
for (size_t t = w->num_tabs; t > 0; t--) {
Tab *tab = w->tabs + t - 1;
@@ -439,7 +440,7 @@ remove_os_window(id_type os_window_id) {
}
static inline void
static void
set_active_tab(id_type os_window_id, unsigned int idx) {
WITH_OS_WINDOW(os_window_id)
os_window->active_tab = idx;
@@ -447,7 +448,7 @@ set_active_tab(id_type os_window_id, unsigned int idx) {
END_WITH_OS_WINDOW
}
static inline void
static void
set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) {
WITH_WINDOW(os_window_id, tab_id, window_id)
(void)window;
@@ -456,7 +457,7 @@ set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) {
END_WITH_WINDOW;
}
static inline void
static void
swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) {
WITH_OS_WINDOW(os_window_id)
Tab t = os_window->tabs[b];
@@ -650,7 +651,7 @@ static PyStructSequence_Field region_fields[] = {
};
static PyStructSequence_Desc region_desc = {"Region", NULL, region_fields, 6};
static inline PyObject*
static PyObject*
wrap_region(Region *r) {
PyObject *ans = PyStructSequence_New(&RegionType);
if (ans) {
@@ -1019,7 +1020,7 @@ pycreate_mock_window(PyObject *self UNUSED, PyObject *args) {
return ans;
}
static inline void
static void
click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) {
WITH_WINDOW(os_window_id, tab_id, window_id);
mouse_open_url(window);

View File

@@ -176,6 +176,7 @@ typedef struct {
double logical_dpi_x, logical_dpi_y, font_sz_in_pts;
bool mouse_button_pressed[32];
PyObject *window_title;
bool disallow_title_changes;
bool viewport_size_dirty, viewport_updated_at_least_once;
LiveResizeInfo live_resize;
bool has_pending_resizes, is_semi_transparent, shown_once, is_damaged;

View File

@@ -13,7 +13,7 @@ from .fast_data_types import (
from .layout.base import Rect
from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color
from .types import WindowGeometry, run_once
from .typing import PowerlineStyle
from .typing import PowerlineStyle, EdgeLiteral
from .utils import color_as_int, log_error
from .window import calculate_gl_geometry
@@ -44,6 +44,7 @@ class DrawData(NamedTuple):
active_title_template: Optional[str]
tab_activity_symbol: Optional[str]
powerline_style: PowerlineStyle
tab_bar_edge: EdgeLiteral
def as_rgb(x: int) -> int:
@@ -164,6 +165,44 @@ def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int)
screen.draw(title)
def draw_tab_with_slant(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int:
left_sep, right_sep = ('', '') if draw_data.tab_bar_edge == 'top' else ('', '')
tab_bg = as_rgb(color_as_int(draw_data.active_bg if tab.is_active else draw_data.inactive_bg))
slant_fg = as_rgb(color_as_int(draw_data.default_bg))
def draw_sep(which: str) -> None:
screen.cursor.bg = tab_bg
screen.cursor.fg = slant_fg
screen.draw(which)
screen.cursor.bg = tab_bg
screen.cursor.fg = 0
max_title_length += 1
if max_title_length <= 1:
screen.draw('')
elif max_title_length == 2:
screen.draw('…|')
elif max_title_length < 6:
draw_sep(left_sep)
screen.draw((' ' if max_title_length == 5 else '') + '' + (' ' if max_title_length >= 4 else ''))
draw_sep(right_sep)
else:
draw_sep(left_sep)
screen.draw(' ')
draw_title(draw_data, screen, tab, index)
extra = screen.cursor.x - before - max_title_length
if extra >= 0:
screen.cursor.x -= extra + 3
screen.draw('')
elif extra == -1:
screen.cursor.x -= 2
screen.draw('')
screen.draw(' ')
draw_sep(right_sep)
return screen.cursor.x
def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int:
tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg
screen.cursor.bg = as_rgb(color_as_int(tab_bg))
@@ -332,12 +371,15 @@ class TabBar:
opts.tab_bar_background or opts.background, opts.tab_title_template,
opts.active_tab_title_template,
opts.tab_activity_symbol,
opts.tab_powerline_style
opts.tab_powerline_style,
'top' if opts.tab_bar_edge == 1 else 'bottom'
)
if opts.tab_bar_style == 'separator':
self.draw_func = draw_tab_with_separator
elif opts.tab_bar_style == 'powerline':
self.draw_func = draw_tab_with_powerline
elif opts.tab_bar_style == 'slant':
self.draw_func = draw_tab_with_slant
else:
self.draw_func = draw_tab_with_fade

View File

@@ -26,6 +26,7 @@ from .fast_data_types import (
from .layout.base import Layout, Rect
from .layout.interface import create_layout_object_for, evict_cached_layouts
from .tab_bar import TabBar, TabBarData
from .types import ac
from .typing import EdgeLiteral, SessionTab, SessionType, TypedDict
from .utils import log_error, platform_window_id, resolved_shell
from .window import Watchers, Window, WindowDict
@@ -222,8 +223,8 @@ class Tab: # {{{
def create_layout_object(self, name: str) -> Layout:
return create_layout_object_for(name, self.os_window_id, self.id)
@ac('lay', 'Go to the next enabled layout')
def next_layout(self) -> None:
'@ac:lay: Go to the next enabled layout'
if len(self.enabled_layouts) > 1:
for i, layout_name in enumerate(self.enabled_layouts):
if layout_name == self.current_layout.full_name:
@@ -235,20 +236,20 @@ class Tab: # {{{
self._set_current_layout(nl)
self.relayout()
@ac('lay', 'Go to the previously used layout')
def last_used_layout(self) -> None:
'@ac:lay: Go to the previously used layout'
if len(self.enabled_layouts) > 1 and self._last_used_layout and self._last_used_layout != self._current_layout_name:
self._set_current_layout(self._last_used_layout)
self.relayout()
def goto_layout(self, layout_name: str, raise_exception: bool = False) -> None:
'''
@ac:lay: Switch to the named layout
@ac('lay', '''
Switch to the named layout
For example::
map f1 goto_layout tall
'''
''')
def goto_layout(self, layout_name: str, raise_exception: bool = False) -> None:
layout_name = layout_name.lower()
if layout_name not in self.enabled_layouts:
if raise_exception:
@@ -258,16 +259,16 @@ class Tab: # {{{
self._set_current_layout(layout_name)
self.relayout()
def toggle_layout(self, layout_name: str) -> None:
'''
@ac:lay: Toggle the named layout
@ac('lay', '''
Toggle the named layout
Switches to the named layout if another layout is current, otherwise
switches to the last used layout. Useful to "zoom" a window temporarily
by switching to the stack layout. For example::
map f1 toggle_layout stack
'''
''')
def toggle_layout(self, layout_name: str) -> None:
if self._current_layout_name == layout_name:
self.last_used_layout()
else:
@@ -280,12 +281,12 @@ class Tab: # {{{
return None
return 'Could not resize'
def resize_window(self, quality: str, increment: int) -> None:
'''
@ac:win: Resize the active window by the specified amount
@ac('win', '''
Resize the active window by the specified amount
See :ref:`window_resizing` for details.
'''
''')
def resize_window(self, quality: str, increment: int) -> None:
if increment < 1:
raise ValueError(increment)
is_horizontal = quality in ('wider', 'narrower')
@@ -296,13 +297,13 @@ class Tab: # {{{
if get_options().enable_audio_bell:
ring_bell()
@ac('win', 'Reset window sizes undoing any dynamic resizing of windows')
def reset_window_sizes(self) -> None:
'@ac:win:Reset window sizes undoing any dynamic resizing of windows'
if self.current_layout.remove_all_biases():
self.relayout()
@ac('lay', 'Perform a layout specific action. See :doc:`layouts` for details')
def layout_action(self, action_name: str, args: Sequence[str]) -> None:
'@ac:lay: Perform a layout specific action. See :doc:`layouts` for details'
ret = self.current_layout.layout_action(action_name, args, self.windows)
if ret is None:
ring_bell()
@@ -424,14 +425,14 @@ class Tab: # {{{
allow_remote_control=allow_remote_control, watchers=special_window.watchers
)
@ac('win', 'Close the currently active window')
def close_window(self) -> None:
'@ac:win: Close the currently active window'
w = self.active_window
if w is not None:
self.remove_window(w)
@ac('win', 'Close all windows in the tab other than the currently active window')
def close_other_windows_in_tab(self) -> None:
'@ac:win: Close all windows in the tab other than the currently active window'
if len(self.windows) > 1:
active_window = self.active_window
for window in tuple(self.windows):
@@ -469,14 +470,14 @@ class Tab: # {{{
if self.windows:
return self.current_layout.nth_window(self.windows, n)
def nth_window(self, num: int = 0) -> None:
'''
@ac:win: Focus the nth window if positive or the previously active windows if negative
@ac('win', '''
Focus the nth window if positive or the previously active windows if negative
For example, to ficus the previously active window::
map ctrl+p nth_window -1
'''
''')
def nth_window(self, num: int = 0) -> None:
if self.windows:
if num < 0:
self.windows.make_previous_group_active(-num)
@@ -489,12 +490,12 @@ class Tab: # {{{
self.current_layout.next_window(self.windows, delta)
self.relayout_borders()
@ac('win', 'Focus the next window in the current tab')
def next_window(self) -> None:
'@ac:win: Focus the next window in the current tab'
self._next_window()
@ac('win', 'Focus the previous window in the current tab')
def previous_window(self) -> None:
'@ac:win: Focus the previous window in the current tab'
self._next_window(-1)
prev_window = previous_window
@@ -510,34 +511,40 @@ class Tab: # {{{
if groups:
return groups[0]
def nth_active_window_id(self, n: int = 0) -> int:
if n <= 0:
return self.active_window.id if self.active_window else 0
ids = tuple(reversed(self.windows.active_window_history))
return ids[min(n - 1, len(ids) - 1)] if ids else 0
def neighboring_group_id(self, which: EdgeLiteral) -> Optional[int]:
neighbors = self.current_layout.neighbors(self.windows)
candidates = neighbors.get(which)
if candidates:
return self.most_recent_group(candidates)
def neighboring_window(self, which: EdgeLiteral) -> None:
'''
@ac:win: Focus the neighboring window in the current tab
@ac('win', '''
Focus the neighboring window in the current tab
For example::
map ctrl+left neighboring_window left
map ctrl+down neighboring_window bottom
'''
''')
def neighboring_window(self, which: EdgeLiteral) -> None:
neighbor = self.neighboring_group_id(which)
if neighbor:
self.windows.set_active_group(neighbor)
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None:
'''
@ac:win: Move the window in the specified direction
@ac('win', '''
Move the window in the specified direction
For example::
map ctrl+left move_window left
map ctrl+down move_window bottom
'''
''')
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None:
if isinstance(delta, int):
if self.current_layout.move_window(self.windows, delta):
self.relayout()
@@ -547,18 +554,18 @@ class Tab: # {{{
if self.current_layout.move_window_to_group(self.windows, neighbor):
self.relayout()
@ac('win', 'Move active window to the top (make it the first window)')
def move_window_to_top(self) -> None:
'@ac:win: Move active window to the top (make it the first window)'
n = self.windows.active_group_idx
if n > 0:
self.move_window(-n)
@ac('win', 'Move active window forward (swap it with the next window)')
def move_window_forward(self) -> None:
'@ac:win: Move active window forward (swap it with the next window)'
self.move_window()
@ac('win', 'Move active window backward (swap it with the previous window)')
def move_window_backward(self) -> None:
'@ac:win: Move active window backward (swap it with the previous window)'
self.move_window(-1)
def list_windows(self, active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:
@@ -747,6 +754,12 @@ class TabManager: # {{{
self.set_active_tab_idx(idx)
break
def nth_active_tab(self, n: int = 0) -> Optional[Tab]:
if n <= 0:
return self.active_tab
tab_ids = tuple(reversed(self.active_tab_history))
return self.tab_for_id(tab_ids[min(n - 1, len(tab_ids) - 1)]) if tab_ids else None
def __iter__(self) -> Iterator[Tab]:
return iter(self.tabs)

View File

@@ -113,7 +113,7 @@ string_capabilities = {
# Clear screen
'clear': r'\E[H\E[2J',
# Make cursor appear normal
'cnorm': r'\E[?12l\E[?25h',
'cnorm': r'\E[?12h\E[?25h',
# Carriage return
'cr': r'^M', # CR (carriage return \r)
# Change scroll region

View File

@@ -93,3 +93,22 @@ else:
def run_once(f: Callable[[], _T]) -> RunOnce:
return RunOnce(f)
if TYPE_CHECKING:
from typing import Literal
ActionGroup = Literal['cp', 'sc', 'win', 'tab', 'mouse', 'mk', 'lay', 'misc']
else:
ActionGroup = str
class ActionSpec(NamedTuple):
group: str
doc: str
def ac(group: ActionGroup, doc: str) -> Callable[[_T], _T]:
def w(f: _T) -> _T:
setattr(f, 'action_spec', ActionSpec(group, doc))
return f
return w

View File

@@ -528,7 +528,12 @@ def get_editor_from_env_vars(opts: Optional[Options] = None) -> List[str]:
def get_editor(opts: Optional[Options] = None) -> List[str]:
if opts is None:
from .fast_data_types import get_options
opts = get_options()
try:
opts = get_options()
except RuntimeError:
# we are in a kitten
from .cli import create_default_opts
opts = create_default_opts()
if opts.editor == '.':
return get_editor_from_env_vars()
import shlex
@@ -717,3 +722,37 @@ def get_new_os_window_size(
w = width or metrics['width']
h = height or metrics['height']
return w, h
def get_all_processes() -> Iterable[int]:
if is_macos:
from kitty.fast_data_types import get_all_processes as f
yield from f()
else:
for c in os.listdir('/proc'):
if c.isdigit():
yield int(c)
def is_kitty_gui_cmdline(*cmd: str) -> bool:
if not cmd:
return False
if os.path.basename(cmd[0]) != 'kitty':
return False
if len(cmd) == 1:
return True
if '+' in cmd or '@' in cmd or cmd[1].startswith('+') or cmd[1].startswith('@'):
return False
return True
def reload_conf_in_all_kitties() -> None:
import signal
from kitty.child import cmdline_of_process # type: ignore
for pid in get_all_processes():
try:
cmd = cmdline_of_process(pid)
except Exception:
continue
if cmd and is_kitty_gui_cmdline(*cmd):
os.kill(pid, signal.SIGUSR1)

View File

@@ -38,7 +38,7 @@ from .notify import NotificationCommand, handle_notification_cmd
from .options.types import Options
from .rgb import to_color
from .terminfo import get_capabilities
from .types import MouseEvent, ScreenGeometry, WindowGeometry
from .types import MouseEvent, ScreenGeometry, WindowGeometry, ac
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
from .utils import (
color_as_int, get_primary_selection, load_shaders, log_error, open_cmd,
@@ -529,12 +529,12 @@ class Window:
def close(self) -> None:
get_boss().close_window(self)
def send_text(self, *args: str) -> bool:
'''
@ac:misc: Send the specified text to the active window
@ac('misc', '''
Send the specified text to the active window
For details, see :sc:`send_text`.
'''
''')
def send_text(self, *args: str) -> bool:
mode = keyboard_mode_name(self.screen)
required_mode_, text = args[-2:]
required_mode = frozenset(required_mode_.split(','))
@@ -791,7 +791,8 @@ class Window:
self.clipboard_pending = ClipboardPending(where, text)
else:
self.clipboard_pending = self.clipboard_pending._replace(data=self.clipboard_pending[1] + text)
if len(self.clipboard_pending.data) > 8 * 1024 * 1024:
limit = get_options().clipboard_max_size
if limit and len(self.clipboard_pending.data) > limit * 1024 * 1024:
log_error('Discarding part of too large OSC 52 paste request')
self.clipboard_pending = self.clipboard_pending._replace(data='', truncated=True)
return
@@ -848,31 +849,31 @@ class Window:
# }}}
# mouse actions {{{
@ac('mouse', 'Click the URL under the mouse')
def mouse_click_url(self) -> None:
'@ac:mouse: Click the URL under the mouse'
click_mouse_url(self.os_window_id, self.tab_id, self.id)
@ac('mouse', 'Click the URL under the mouse only if the screen has no selection')
def mouse_click_url_or_select(self) -> None:
'@ac:mouse: Click the URL under the mouse only if the screen has no selection'
if not self.screen.has_selection():
self.mouse_click_url()
def mouse_selection(self, code: int) -> None:
'''
@ac:mouse: Manipulate the selection based on the current mouse position
@ac('mouse', '''
Manipulate the selection based on the current mouse position
For examples, see :ref:`conf-kitty-mouse.mousemap`
'''
''')
def mouse_selection(self, code: int) -> None:
mouse_selection(self.os_window_id, self.tab_id, self.id, code, self.current_mouse_event_button)
@ac('mouse', 'Paste the current primary selection')
def paste_selection(self) -> None:
'@ac:mouse: Paste the current primary selection'
txt = get_boss().current_primary_selection()
if txt:
self.paste(txt)
@ac('mouse', 'Paste the current primary selection or the clipboard if no selection is present')
def paste_selection_or_clipboard(self) -> None:
'@ac:mouse: Paste the current primary selection or the clipboard if no selection is present'
txt = get_boss().current_primary_selection_or_clipboard()
if txt:
self.paste(txt)
@@ -935,8 +936,8 @@ class Window:
# actions {{{
@ac('cp', 'Show scrollback in a pager like less')
def show_scrollback(self) -> None:
'@ac:cp: Show scrollback in a pager like less'
text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
data = self.pipe_data(text, has_wrap_markers=True)
get_boss().display_scrollback(self, data['text'], data['input_line_number'])
@@ -947,8 +948,8 @@ class Window:
text = text.encode('utf-8')
self.screen.paste_bytes(text)
@ac('cp', 'Paste the specified text into the current window')
def paste(self, text: Union[str, bytes]) -> None:
'@ac:cp: Paste the specified text into the current window'
if text and not self.destroyed:
if isinstance(text, str):
text = text.encode('utf-8')
@@ -964,8 +965,8 @@ class Window:
text = text.replace(b'\r\n', b'\n').replace(b'\n', b'\r')
self.screen.paste(text)
@ac('cp', 'Copy the selected text from the active window to the clipboard')
def copy_to_clipboard(self) -> None:
'@ac:cp: Copy the selected text from the active window to the clipboard'
text = self.text_for_selection()
if text:
set_clipboard_string(text)
@@ -978,21 +979,21 @@ class Window:
cursor_key_mode=self.screen.cursor_key_mode,
).encode('ascii')
@ac('cp', 'Copy the selected text from the active window to the clipboard, if no selection, send Ctrl-C')
def copy_or_interrupt(self) -> None:
'@ac:cp: Copy the selected text from the active window to the clipboard, if no selection, send Ctrl-C'
text = self.text_for_selection()
if text:
set_clipboard_string(text)
else:
self.write_to_child(self.encoded_key(KeyEvent(key=ord('c'), mods=GLFW_MOD_CONTROL)))
@ac('cp', 'Copy the selected text from the active window to the clipboard and clear selection, if no selection, send Ctrl-C')
def copy_and_clear_or_interrupt(self) -> None:
'@ac:cp: Copy the selected text from the active window to the clipboard and clear selection, if no selection, send Ctrl-C'
self.copy_or_interrupt()
self.screen.clear_selection()
@ac('cp', 'Pass the selected text from the active window to the specified program')
def pass_selection_to_program(self, *args: str) -> None:
'@ac:cp: Pass the selected text from the active window to the specified program'
cwd = self.cwd_of_child
text = self.text_for_selection()
if text:
@@ -1001,38 +1002,38 @@ class Window:
else:
open_url(text, cwd=cwd)
@ac('sc', 'Scroll up by one line')
def scroll_line_up(self) -> None:
'@ac:sc: Scroll up by one line'
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_LINE, True)
@ac('sc', 'Scroll down by one line')
def scroll_line_down(self) -> None:
'@ac:sc: Scroll down by one line'
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_LINE, False)
@ac('sc', 'Scroll up by one page')
def scroll_page_up(self) -> None:
'@ac:sc: Scroll up by one page'
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_PAGE, True)
@ac('sc', 'Scroll down by one page')
def scroll_page_down(self) -> None:
'@ac:sc: Scroll down by one page'
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_PAGE, False)
@ac('sc', 'Scroll to the top of the scrollback buffer')
def scroll_home(self) -> None:
'@ac:sc: Scroll to the top of the scrollback buffer'
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_FULL, True)
@ac('sc', 'Scroll to the bottom of the scrollback buffer')
def scroll_end(self) -> None:
'@ac:sc: Scroll to the bottom of the scrollback buffer'
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_FULL, False)
@ac('mk', 'Toggle the current marker on/off')
def toggle_marker(self, ftype: str, spec: Union[str, Tuple[Tuple[int, str], ...]], flags: int) -> None:
'@ac:mk: Toggle the current marker on/off'
from .marks import marker_from_spec
key = ftype, spec
if key == self.current_marker_spec:
@@ -1052,24 +1053,24 @@ class Window:
self.screen.set_marker(marker_from_spec(ftype, spec_, flags))
self.current_marker_spec = key
@ac('mk', 'Remove a previously created marker')
def remove_marker(self) -> None:
'@ac:mk: Remove a previously created marker'
if self.current_marker_spec is not None:
self.screen.set_marker()
self.current_marker_spec = None
@ac('mk', 'Scroll to the next or previous mark of the specified type')
def scroll_to_mark(self, prev: bool = True, mark: int = 0) -> None:
'@ac:mk: Scroll to the next or previous mark of the specified type'
self.screen.scroll_to_next_mark(mark, prev)
def signal_child(self, *signals: int) -> None:
'''
@ac:misc: Send the specified SIGNAL to the foreground process in the active window
@ac('misc', '''
Send the specified SIGNAL to the foreground process in the active window
For example::
map F1 signal_child SIGTERM
'''
''')
def signal_child(self, *signals: int) -> None:
pid = self.child.pid_for_cwd
if pid is not None:
for sig in signals:

View File

@@ -20,9 +20,9 @@ class TestBuild(BaseTest):
def test_loading_extensions(self) -> None:
import kitty.fast_data_types as fdt
from kittens.unicode_input import unicode_names
from kittens.choose import subseq_matcher
from kittens.diff import diff_speedup
from kittens.unicode_input import unicode_names
del fdt, unicode_names, subseq_matcher, diff_speedup
def test_loading_shaders(self) -> None:
@@ -31,7 +31,7 @@ class TestBuild(BaseTest):
load_shaders(name)
def test_glfw_modules(self) -> None:
from kitty.constants import is_macos, glfw_path
from kitty.constants import glfw_path, is_macos
linux_backends = ['x11']
if not self.is_ci:
linux_backends.append('wayland')
@@ -49,10 +49,18 @@ class TestBuild(BaseTest):
self.assertGreater(len(names), 8)
def test_filesystem_locations(self) -> None:
from kitty.constants import terminfo_dir, logo_png_file
from kitty.constants import logo_png_file, terminfo_dir
self.assertTrue(os.path.isdir(terminfo_dir), f'Terminfo dir: {terminfo_dir}')
self.assertTrue(os.path.exists(logo_png_file), f'Logo file: {logo_png_file}')
def test_ca_certificates(self):
import ssl
import sys
if not getattr(sys, 'frozen', False):
self.skipTest('CA certificates are only tested on frozen builds')
c = ssl.create_default_context()
self.assertGreater(c.cert_store_stats()['x509_ca'], 2)
def main() -> None:
tests = unittest.defaultTestLoader.loadTestsFromTestCase(TestBuild)

View File

@@ -11,7 +11,7 @@ from . import BaseTest
def run(input_data, query, **kw):
kw['threads'] = kw.get('threads', 1)
mark = kw.pop('mark', False)
from kittens.choose.main import match
from kittens.choose.match import match
mark_before = mark_after = ''
if mark:
if mark is True:
@@ -59,6 +59,7 @@ class TestMatcher(BaseTest):
def test_positions(self):
' Output of positions '
self.basic_test('abc\nac', 'ac', '0,1:ac\n0,2:abc', positions=True)
self.basic_test('abc\nv', 'a', '0:abc', positions=True)
def test_delimiter(self):
' Test using a custom line delimiter '

View File

@@ -150,6 +150,11 @@ class TestMouse(BaseTest):
multi_click(x=1, y=2)
self.ae(sel(), 'stuvX')
release()
multi_click(x=3.6)
self.ae(sel(), 'cd')
move(0.2)
release()
self.ae(sel(), 'ab cd')
# Line select with drag
s.reset()
@@ -169,6 +174,15 @@ class TestMouse(BaseTest):
move()
self.ae(sel(), str(s.line(0)))
release()
multi_click(y=1, count=3)
self.ae(sel(), '4 5 6')
move(y=0)
self.ae(sel(), '1 2 3\n4 5 6')
move(y=1)
self.ae(sel(), '4 5 6')
move(y=2)
self.ae(sel(), '4 5 6\n7 8 9X')
release()
s.reset()
s.draw(' 123')
s.linefeed(), s.carriage_return()
@@ -183,8 +197,8 @@ class TestMouse(BaseTest):
release(x=2, y=1, button=GLFW_MOUSE_BUTTON_RIGHT)
self.ae(sel(), '123\n 456')
press(button=GLFW_MOUSE_BUTTON_RIGHT)
release(button=GLFW_MOUSE_BUTTON_RIGHT)
self.ae(sel(), ' 123\n 456')
release(button=GLFW_MOUSE_BUTTON_RIGHT)
# Rectangle select
init()

View File

@@ -41,6 +41,8 @@ class TestConfParsing(BaseTest):
self.assertFalse(opts.keymap)
opts = p('clear_all_shortcuts y', 'map f1 next_window')
self.ae(len(opts.keymap), 1)
opts = p('clear_all_mouse_actions y', 'mouse_map left click ungrabbed mouse_click_url_or_select')
self.ae(len(opts.mousemap), 1)
opts = p('strip_trailing_spaces always')
self.ae(opts.strip_trailing_spaces, 'always')
self.assertFalse(bad_lines)

View File

@@ -30,12 +30,12 @@
#define KITTY_LIB_DIR_NAME "lib"
#endif
static inline void cleanup_free(void *p) { free(*(void**) p); }
static void cleanup_free(void *p) { free(*(void**) p); }
#define FREE_AFTER_FUNCTION __attribute__((cleanup(cleanup_free)))
#ifndef __FreeBSD__
static inline bool
static bool
safe_realpath(const char* src, char *buf, size_t buf_sz) {
FREE_AFTER_FUNCTION char* ans = realpath(src, NULL);
if (ans == NULL) return false;
@@ -44,7 +44,7 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
}
#endif
static inline bool
static bool
set_xoptions(const char *exe_dir_c, const char *lc_ctype, bool from_source) {
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_c, NULL);
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_c); return false; }
@@ -199,7 +199,7 @@ run_embedded(const RunData run_data) {
// read_exe_path() {{{
#ifdef __APPLE__
static inline bool
static bool
read_exe_path(char *exe, size_t buf_sz) {
(void)buf_sz;
uint32_t size = PATH_MAX;
@@ -212,7 +212,7 @@ read_exe_path(char *exe, size_t buf_sz) {
#include <sys/param.h>
#include <sys/sysctl.h>
static inline bool
static bool
read_exe_path(char *exe, size_t buf_sz) {
int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
size_t length = buf_sz;
@@ -225,14 +225,14 @@ read_exe_path(char *exe, size_t buf_sz) {
}
#elif defined(__NetBSD__)
static inline bool
static bool
read_exe_path(char *exe, size_t buf_sz) {
if (!safe_realpath("/proc/curproc/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/curproc/exe\n"); return false; }
return true;
}
#elif defined(__OpenBSD__)
static inline bool
static bool
read_exe_path(char *exe, size_t buf_sz) {
const char *path = getenv("PATH");
if (!path) { fprintf(stderr, "No PATH environment variable set, aborting\n"); return false; }
@@ -251,7 +251,7 @@ read_exe_path(char *exe, size_t buf_sz) {
#else
static inline bool
static bool
read_exe_path(char *exe, size_t buf_sz) {
if (!safe_realpath("/proc/self/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/self/exe\n"); return false; }
return true;

View File

@@ -24,7 +24,7 @@ xterm-kitty|KovIdTTY,
cbt=\E[Z,
civis=\E[?25l,
clear=\E[H\E[2J,
cnorm=\E[?12l\E[?25h,
cnorm=\E[?12h\E[?25h,
cr=^M,
csr=\E[%i%p1%d;%p2%dr,
cub=\E[%p1%dD,

Binary file not shown.