Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd7b4fcd8e | ||
|
|
f67995d5d3 | ||
|
|
f0e7344bc8 | ||
|
|
aa525c68c7 | ||
|
|
a2e25331a5 | ||
|
|
d8f09d377f | ||
|
|
cc96cb1c75 | ||
|
|
5966f04656 | ||
|
|
dbce9a8f29 | ||
|
|
4333552523 | ||
|
|
c00e945f6e | ||
|
|
56cb628ee8 | ||
|
|
827b6598b2 | ||
|
|
59bafea06d | ||
|
|
d61aca40b8 | ||
|
|
bf704d35a2 | ||
|
|
fbc8881a96 | ||
|
|
8cfb1efb01 | ||
|
|
f399b8466c | ||
|
|
437ba69049 | ||
|
|
dbc7e8e85d | ||
|
|
f3333ce941 | ||
|
|
97a9261096 | ||
|
|
5a76dab3c6 | ||
|
|
5a92d3f312 | ||
|
|
b173dd1c39 | ||
|
|
473e1a6f22 | ||
|
|
9c4a890688 | ||
|
|
a803c8bcc5 | ||
|
|
5c7ce18379 | ||
|
|
aff6fdfa84 | ||
|
|
477a652b44 | ||
|
|
15e0b42f04 | ||
|
|
b25ea9c863 | ||
|
|
c838cb03c9 | ||
|
|
ba9adc127e | ||
|
|
b7d603c4de | ||
|
|
71f1f3aa64 | ||
|
|
7d1c26202a | ||
|
|
80db2f6558 | ||
|
|
e376c79dda | ||
|
|
5a47e0d2e4 | ||
|
|
ca9143bebc | ||
|
|
a49f6799de | ||
|
|
d916ecc4f3 | ||
|
|
e1ed9aca10 | ||
|
|
0e2f1b9405 | ||
|
|
db5a2d2141 | ||
|
|
a9771dccba | ||
|
|
4849e07c26 | ||
|
|
4645e78fa7 | ||
|
|
9e2590eb15 | ||
|
|
94575a5cf6 | ||
|
|
a597a8d86b | ||
|
|
3287798efe | ||
|
|
d01ac17334 | ||
|
|
2c96727c45 | ||
|
|
ca1b2454bd | ||
|
|
064fc17ce3 | ||
|
|
43ccf9cb41 | ||
|
|
96857a197c | ||
|
|
079ff7785c | ||
|
|
d6a6cbe153 | ||
|
|
2486cfd45d | ||
|
|
30cb7286db | ||
|
|
9e7253c179 | ||
|
|
276a82d1f7 | ||
|
|
57ced9bc83 | ||
|
|
3c3662b032 | ||
|
|
f490b9a8bd | ||
|
|
c7ccedae95 | ||
|
|
03517459db | ||
|
|
96326280e5 | ||
|
|
9b7899780b | ||
|
|
e01cd057e8 | ||
|
|
ba85ca1991 | ||
|
|
92a9b71f21 | ||
|
|
e50c26d1b9 | ||
|
|
7090c24321 | ||
|
|
55319cd6d6 | ||
|
|
8a6b51441c | ||
|
|
36670b49a0 | ||
|
|
a402d848d2 | ||
|
|
37c563802c | ||
|
|
2d7032973c | ||
|
|
70b5f5bce3 | ||
|
|
be34af4555 | ||
|
|
815539a933 | ||
|
|
ab889e2945 | ||
|
|
b3231c8003 | ||
|
|
5f6cb34f77 | ||
|
|
a99a080c50 | ||
|
|
4a1ca8d582 | ||
|
|
050eb5660d |
2
.github/workflows/ci.py
vendored
2
.github/workflows/ci.py
vendored
@@ -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:
|
||||
|
||||
26
__main__.py
26
__main__.py
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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...')
|
||||
|
||||
@@ -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]
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -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::
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
66
docs/kittens/themes.rst
Normal 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
|
||||
@@ -10,6 +10,7 @@ Extend with kittens
|
||||
kittens/icat
|
||||
kittens/diff
|
||||
kittens/unicode-input
|
||||
kittens/themes
|
||||
kittens/hints
|
||||
kittens/remote_file
|
||||
kittens/hyperlinked_grep
|
||||
|
||||
@@ -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`.
|
||||
|
||||
6
glfw/backend_utils.c
vendored
6
glfw/backend_utils.c
vendored
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
4
glfw/dbus_glfw.c
vendored
@@ -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
12
glfw/ibus_glfw.c
vendored
@@ -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
2
glfw/wl_init.c
vendored
@@ -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;
|
||||
}
|
||||
|
||||
2
glfw/wl_text_input.c
vendored
2
glfw/wl_text_input.c
vendored
@@ -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
2
glfw/wl_window.c
vendored
@@ -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
6
glfw/x11_window.c
vendored
@@ -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
8
glfw/xkb_glfw.c
vendored
@@ -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;
|
||||
|
||||
@@ -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
39
kittens/choose/match.py
Normal 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')))
|
||||
@@ -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) ? ':' : ',';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
5
kittens/diff/options/types.py
generated
5
kittens/diff/options/types.py
generated
@@ -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, ...]:
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
0
kittens/themes/__init__.py
Normal file
0
kittens/themes/__init__.py
Normal file
387
kittens/themes/collection.py
Normal file
387
kittens/themes/collection.py
Normal 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
594
kittens/themes/main.py
Normal 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
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
209
kitty/boss.py
209
kitty/boss.py
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
29
kitty/glfw.c
29
kitty/glfw.c
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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));
|
||||
|
||||
22
kitty/line.c
22
kitty/line.c
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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, ¢ral, &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;
|
||||
|
||||
@@ -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
12
kitty/options/parse.py
generated
@@ -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
13
kitty/options/types.py
generated
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
12
kitty/rgb.py
generated
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
102
kitty/screen.c
102
kitty/screen.c
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 '
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
16
launcher.c
16
launcher.c
@@ -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;
|
||||
|
||||
@@ -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.
Reference in New Issue
Block a user