Compare commits

..

79 Commits

Author SHA1 Message Date
129186761c Update patches from https://github.com/KittyPatch/kitty to work on current kitty 2023-05-12 21:06:00 -07:00
Kovid Goyal
491297ea1d
When asking for permission to exec a shebang script also add options to view or edit the script 2023-05-12 16:02:47 +05:30
Kovid Goyal
c101a6acb0
Implement a dedicated function for word matching rather than relying on a regex and being at the mercy of the vagaries of regex implementations 2023-05-12 15:43:56 +05:30
Kovid Goyal
65f8bb7397
hints kitten: Switch to using a regex engine that supports lookaround
Note that we loose unicode char matching for --type=word because of
https://github.com/dlclark/regexp2/issues/65 and of course user regexps
cant use \p{N} escapes any more. Hopefully regexp2 will add support for
these soon-ish. IMO lookaround is more important than \p.

Fixes #6265
2023-05-12 12:24:59 +05:30
Kovid Goyal
5b8b91b6a3
Add support for OSC 1337 SetUserVar
See https://github.com/kovidgoyal/kitty/discussions/6229
2023-05-11 17:57:45 +05:30
Kovid Goyal
6a2edfa847
Merge branch 'pr-fix-shade' of https://github.com/MithicSpirit/kitty 2023-05-10 09:56:59 +05:30
MithicSpirit
28b84a2d5b
Add support for 0x1fb90
Allocation in box_glyph_id is larger than necessary to account for the
addition of 0x1fb8c ... 0x1fb94 eventually, which are quite similar but
will require more work to add. Note that 0x1fb93 is not present in the
standard yet, but it is easy to guess what it will likely be from
context, so it should be kept in the allocation imo.
2023-05-09 22:19:03 -04:00
MithicSpirit
c247fe2336
Revert "Improve shade character appearance"
This reverts commit c883a024ba2a58533fb5bea021fe6b3d2dfb11a2.

To maximize compatibility with the appearance in the standard.
2023-05-09 22:06:05 -04:00
MithicSpirit
c883a024ba
Improve shade character appearance
I was really unhappy with the previous checkerboard appearance, so I
changed it to supersampled diagonal lines. The fill ratios are still the
same, so it should still be compliant with the standard if I understood
it correctly.

Feel free to revert (or tell me to revert) this commit if you want the
previous look.
2023-05-09 16:08:02 -04:00
Kovid Goyal
0cc38e1086
... 2023-05-09 09:50:11 +05:30
Kovid Goyal
1777b87c45
Improve docs for reset the terminal 2023-05-09 09:44:05 +05:30
Kovid Goyal
e72975cc98
A new escape code that moves the current contents of the screen into the scrollback before clearing it 2023-05-09 09:32:39 +05:30
Kovid Goyal
8f15654985
Ensure kitty is rebuilt after publishing the nightly 2023-05-09 08:54:29 +05:30
Kovid Goyal
2408ccb635
... 2023-05-09 08:48:37 +05:30
Kovid Goyal
a0cf4214df
... 2023-05-09 08:44:51 +05:30
Kovid Goyal
07203c67ca
Add a note about why kitty terminfo does not have E3
See #6255
2023-05-09 08:28:54 +05:30
MithicSpirit
a36fe45181
Fix shade characters to follow unicode standard
- Light shade: 25% fill
- Medium shade: 50% fill
- Dark shade: 75% fill (implemented as inverse of light shade)
2023-05-08 18:46:24 -04:00
Kovid Goyal
061c444f20
... 2023-05-08 16:36:47 +05:30
Kovid Goyal
a1d791083b
ssh_kitten: Proper exit code for termination by SIGINT 2023-05-08 16:27:07 +05:30
Kovid Goyal
454acd4f5c
ssh kitten: Fix a regression in 0.28.0 that caused interrupt during setup to not be handled gracefully
Fixes #6254
2023-05-08 16:18:05 +05:30
Kovid Goyal
71189aee9f
Correct the type signature for callback 2023-05-08 16:03:27 +05:30
Kovid Goyal
23d7494e3a
Fix #6251 2023-05-08 08:04:20 +05:30
Kovid Goyal
404f83a277
Add a link to awrit in the integrations page 2023-05-07 10:06:37 +05:30
Kovid Goyal
474244268c
edit-in-kitty: Fix running edit-in-kitty with elevated privileges to edit a restricted file not working 2023-05-07 09:36:16 +05:30
Kovid Goyal
79cd6f38fe
... 2023-05-07 09:24:30 +05:30
Kovid Goyal
b7c3946f8f
... 2023-05-07 08:13:57 +05:30
Kovid Goyal
537cabca71
kitty +open: Ask for permission before executing script files that are not marked as executable
This prevents accidental execution of script files via MIME type
association from programs that unconditionally "open"
attachments/downloaded files via MIME type associations.
2023-05-07 08:11:39 +05:30
Kovid Goyal
79c19562b5
When publishing stash untracked files as well 2023-05-07 07:42:25 +05:30
Kovid Goyal
52afc79476
Fix re-using an image id for an animated image for a still image causing a crash
Fixes #6244
2023-05-06 09:37:55 +05:30
Kovid Goyal
877d8d7008
... 2023-05-04 10:36:02 +05:30
Kovid Goyal
ce70320a62
... 2023-05-04 10:26:18 +05:30
Kovid Goyal
3eb18a416a
Entry point for parsing theme metadata 2023-05-04 10:14:58 +05:30
Kovid Goyal
8ba7258db9
Merge branch 'fix-bash-intergration-var-leak' of https://github.com/syyyr/kitty 2023-05-04 08:05:33 +05:30
Václav Kubernát
a502e94950 bash_integration: Do not leak variable i
With shell-integration, the user would see the last value of this
variable (as set by the shell-integration script.

Fix this by making it local.
2023-05-03 18:35:30 +02:00
Kovid Goyal
ea5634b3fd
When parsing theme metadata ignore the name if it is the placeholder value from the template 2023-05-03 21:55:33 +05:30
Kovid Goyal
87943079fb
Fix #6238 2023-05-03 21:40:42 +05:30
Kovid Goyal
a77b2b20c2
Fix #6230 2023-05-03 18:25:07 +05:30
Kovid Goyal
8f96395f74
diff kitten: Fix a regression in 0.28.0 that broke using relative paths as arguments to the kitten
Fixes #6235
2023-05-03 08:34:46 +05:30
Kovid Goyal
1fc4e53bea
hints kitten: Fix a regression in 0.28.0 that broke using sub-groups in regexp captures
Fixes #6228
2023-04-30 21:16:24 +05:30
Kovid Goyal
f6ccd2ad2c
Dont apply linear2srgb in borders with bg image as the cell shader doesnt apply it then either 2023-04-30 10:19:35 +05:30
Kovid Goyal
bc2af4acf9
Update changelog 2023-04-30 09:09:09 +05:30
Kovid Goyal
07dbfaa297
Fix #6224 2023-04-30 08:28:02 +05:30
Kovid Goyal
8020d5823b
Fix #6225 2023-04-30 07:15:56 +05:30
Kovid Goyal
73f10aaf43
clipboard kitten: Fix a bug causing the last MIME type available on the clipboard not being recognized when pasting with arbitrary MIME types 2023-04-30 06:48:09 +05:30
Kovid Goyal
59c4d4a4bd
DRYer 2023-04-28 20:30:15 +05:30
Kovid Goyal
ef999c9024
Also show stderr from tmux on failure 2023-04-28 20:16:37 +05:30
Kovid Goyal
514888a274
Use FindExe to find the tmux executable and return a nicer error message when running tmux fails 2023-04-28 20:11:15 +05:30
Kovid Goyal
09ebdcd809
Allow using set_tab_title without a pre-filled title. Fixes #6217 2023-04-28 10:14:25 +05:30
Kovid Goyal
8ebe4084cc
Merge branch 'fix/6209-background_opacity_fringing' of https://github.com/m4rw3r/kitty 2023-04-28 09:28:09 +05:30
Martin Wernstål
9f41183628 fix: account for incorrect gamma-blending performed by compositor on transparent windows
Fixes #6209
2023-04-27 18:35:06 +02:00
Martin Wernstål
289957ef1c style: use ifdef to be consistent with the other cases 2023-04-27 18:34:00 +02:00
Martin Wernstål
920b350ac9 feat: more exact sRGB approximation 2023-04-27 18:27:46 +02:00
Kovid Goyal
d14655f644
Merge pull request #6216 from jaseg/master
docs/basic.rst: Add resize window shortcut
2023-04-27 16:25:03 +05:30
jaseg
29583411e6
docs/basic.rst: Add resize window shortcut 2023-04-27 12:48:05 +02:00
Kovid Goyal
019359b219
show_key kitten: In kitty mode show the actual bytes sent by the terminal rather than a re-encoding of the parsed key event
Also port the kitten to Go
2023-04-26 21:48:53 +05:30
Kovid Goyal
7b6d11fd1e
Fix rendering of :doc: links with explicit titles in help text in the terminal 2023-04-26 16:46:20 +05:30
Kovid Goyal
bb33c66570
Fix #6213 2023-04-26 16:38:25 +05:30
Kovid Goyal
c2fc4eadc8
unicode_input: Only serialize favorites if no user config exists 2023-04-26 16:02:18 +05:30
Kovid Goyal
a7b4d07601
unicode_input kitten: Fix a regression in 0.28.0 that caused the order of recent and favorites entries to not be respected
Fixes #6214
2023-04-26 15:55:56 +05:30
Kovid Goyal
6a07435bb0
hints kitten: Fix regression causing editing of favorites to sometimes hang 2023-04-26 15:23:38 +05:30
Kovid Goyal
93a5107e79
Fix #6202 2023-04-21 21:35:59 +05:30
Kovid Goyal
6cc8e67580
Add example code to get screen size in Bash 2023-04-21 15:18:30 +05:30
Kovid Goyal
ccdb951716
Website: Fix optimization of social preview images 2023-04-21 14:19:36 +05:30
Kovid Goyal
07bcc5ba61
version 0.28.1 2023-04-21 13:10:01 +05:30
Kovid Goyal
6e90bc1996
... 2023-04-20 21:48:07 +05:30
Kovid Goyal
6269f78ed2
Make it clearer that exclude operates only on directories 2023-04-18 09:22:34 +05:30
Kovid Goyal
dd0e1cce9e
Bump versions of various go deps 2023-04-18 09:13:08 +05:30
Kovid Goyal
92e68a6e0c
Fix #6193 2023-04-18 09:05:28 +05:30
Kovid Goyal
e4baca6d97
Emphasize that names of custom theme conf files must actual builtin theme names to override them 2023-04-17 08:47:26 +05:30
Kovid Goyal
a09464dee9
Fix a regression in the previous release that broke usage of custom themes
Fixes #6191
2023-04-17 08:45:46 +05:30
Kovid Goyal
b966013a2b
Make Samefile interface a bit nicer for working with paths 2023-04-17 08:35:50 +05:30
Kovid Goyal
046fbb860b
themes kitten: ignore custom theme files if they are stdout 2023-04-17 08:02:41 +05:30
Kovid Goyal
91700b3e42
Fix a bug in the Go code of the CSI key event parser
Fixes #6189
2023-04-16 15:31:56 +05:30
Kovid Goyal
b314303787
pep8 2023-04-16 15:31:03 +05:30
Kovid Goyal
176cfe771c
Merge branch 'void_functions' of https://github.com/derekschrock/kitty 2023-04-16 11:06:35 +05:30
Derek Schrock
3b57acf03c More cases of #5477 functions with empty argument lists
Building on macOS 13.3.1 (22E261) clang 14.0.3 (clang-1403.0.22.14.1)
running to errors like #5477 where functions without argument lists at
least need void.

Looking for possible suspect functions via:

  git grep -E "^([A-Za-z_]+ )?[A-Za-z_]+\()" \*.c \*.m
2023-04-16 01:09:36 -04:00
Kovid Goyal
77e2572c5a
Optimize social preview images before publishing website 2023-04-15 21:49:32 +05:30
Kovid Goyal
39eff0fe8c
Fix a regression in the previous release that broke the remote file kitten
Fixes #6186
2023-04-15 21:04:30 +05:30
Kovid Goyal
12efff6d08
Fix #6185 2023-04-15 20:43:58 +05:30
80 changed files with 1595 additions and 403 deletions

4
.github/FUNDING.yml vendored
View File

@ -1,4 +1,2 @@
github: kovidgoyal
patreon: kovidgoyal
liberapay: kovidgoyal
custom: https://my.fsf.org/donate
custom: https://sw.kovidgoyal.net/kitty/support.html

View File

@ -58,6 +58,7 @@ Action Shortcut
New window :sc:`new_window` (also :kbd:`⌘+↩` on macOS)
New OS window :sc:`new_os_window` (also :kbd:`⌘+n` on macOS)
Close window :sc:`close_window` (also :kbd:`⇧+⌘+d` on macOS)
Resize window :sc:`start_resizing_window` (also :kbd:`⌘+r` on macOS)
Next window :sc:`next_window`
Previous window :sc:`previous_window`
Move window forward :sc:`move_window_forward`

View File

@ -31,7 +31,7 @@ Manually installing
If something goes wrong or you simply do not want to run the installer, you can
manually download and install |kitty| from the `GitHub releases page
<https://github.com/kovidgoyal/kitty/releases>`__. If you are on macOS, download
<https://gitea.rexy712.xyz/KittyPatch/kitty/releases>`__. If you are on macOS, download
the :file:`.dmg` and install as normal. If you are on Linux, download the
tarball and extract it into a directory. The |kitty| executable will be in the
:file:`bin` sub-directory.

View File

@ -1,9 +1,9 @@
Build from source
==================
.. image:: https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg
.. image:: https://gitea.rexy712.xyz/KittyPatch/kitty/workflows/CI/badge.svg
:alt: Build status
:target: https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI
:target: https://gitea.rexy712.xyz/KittyPatch/kitty/actions?query=workflow%3ACI
.. highlight:: sh
@ -61,6 +61,7 @@ Build-time dependencies:
- ``libfontconfig-dev``
- ``libx11-xcb-dev``
- ``liblcms2-dev``
- ``libssl-dev``
- ``libpython3-dev``
- ``librsync-dev``
@ -70,7 +71,7 @@ Install and run from source
.. code-block:: sh
git clone https://github.com/kovidgoyal/kitty && cd kitty
git clone https://gitea.rexy712.xyz/KittyPatch/kitty && cd kitty
Now build the native code parts of |kitty| with the following command::
@ -105,7 +106,7 @@ dependencies you might have to rebuild the app.
.. note::
The released :file:`kitty.dmg` includes all dependencies, unlike the
:file:`kitty.app` built above and is built automatically by using the
`bypy framework <https://github.com/kovidgoyal/bypy>`__ however, that is
`bypy framework <https://gitea.rexy712.xyz/KittyPatch/bypy>`__ however, that is
designed to run on Linux and is not for the faint of heart.
.. note::
@ -154,7 +155,7 @@ Notes for Linux/macOS packagers
----------------------------------
The released |kitty| source code is available as a `tarball`_ from
`the GitHub releases page <https://github.com/kovidgoyal/kitty/releases>`__.
`the GitHub releases page <https://gitea.rexy712.xyz/KittyPatch/kitty/releases>`__.
While |kitty| does use Python, it is not a traditional Python package, so please
do not install it in site-packages.

View File

@ -35,6 +35,45 @@ mouse anywhere in the current command to move the cursor there. See
Detailed list of changes
-------------------------------------
0.28.2 [future]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new escape code ``<ESC>[22J`` that moves the current contents of the screen into the scrollback before clearing it
- unicode_input kitten: Fix a regression in 0.28.0 that caused the order of recent and favorites entries to not be respected (:iss:`6214`)
- unicode_input kitten: Fix a regression in 0.28.0 that caused editing of favorites to sometimes hang
- clipboard kitten: Fix a bug causing the last MIME type available on the clipboard not being recognized when pasting
- Fix regression in 0.28.0 causing color fringing when rendering in transparent windows on light backgrounds (:iss:`6209`)
- show_key kitten: In kitty mode show the actual bytes sent by the terminal rather than a re-encoding of the parsed key event
- hints kitten: Fix a regression in 0.28.0 that broke using sub-groups in regexp captures (:iss:`6228`)
- hints kitten: Fix a regression in 0.28.0 that broke using lookahead/lookbehind in regexp captures (:iss:`6265`)
- diff kitten: Fix a regression in 0.28.0 that broke using relative paths as arguments to the kitten (:iss:`6325`)
- Fix re-using the image id of an animated image for a still image causing a crash (:iss:`6244`)
- kitty +open: Ask for permission before executing script files that are not marked as executable. This prevents accidental execution
of script files via MIME type association from programs that unconditionally "open" attachments/downloaded files
- edit-in-kitty: Fix running edit-in-kitty with elevated privileges to edit a restricted file not working (:disc:`6245`)
- ssh kitten: Fix a regression in 0.28.0 that caused interrupt during setup to not be handled gracefully (:iss:`6254`)
0.28.1 [2023-04-21]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Fix a regression in the previous release that broke the remote file kitten (:iss:`6186`)
- Fix a regression in the previous release that broke handling of some keyboard shortcuts in some kittens on some keyboard layouts (:iss:`6189`)
- Fix a regression in the previous release that broke usage of custom themes (:iss:`6191`)
0.28.0 [2023-04-15]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -33,8 +33,8 @@ from kitty.constants import str_version, website_url # noqa
# -- Project information -----------------------------------------------------
project = 'kitty'
copyright = time.strftime('%Y, Kovid Goyal')
author = 'Kovid Goyal'
copyright = time.strftime('%Y, Kovid Goyal, KittyPatch')
author = 'Kovid Goyal, KittyPatch'
building_man_pages = 'man' in sys.argv
# The short X.Y version
@ -100,7 +100,7 @@ exclude_patterns = [
rst_prolog = '''
.. |kitty| replace:: *kitty*
.. |version| replace:: VERSION
.. _tarball: https://github.com/kovidgoyal/kitty/releases/download/vVERSION/kitty-VERSION.tar.xz
.. _tarball: https://gitea.rexy712.xyz/KittyPatch/kitty/releases/download/vVERSION/kitty-VERSION.tar.xz
.. role:: italic
'''.replace('VERSION', str_version)
@ -215,7 +215,7 @@ def commit_role(
f'GitHub commit id "{text}" not recognized.', line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
url = f'https://github.com/kovidgoyal/kitty/commit/{commit_id}'
url = f'https://gitea.rexy712.xyz/KittyPatch/kitty/commit/{commit_id}'
set_classes(options)
short_id = subprocess.check_output(
f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip()

View File

@ -58,7 +58,8 @@ Getting the window size
In order to know what size of images to display and how to position them, the
client must be able to get the window size in pixels and the number of cells
per row and column. This can be done by using the ``TIOCGWINSZ`` ioctl. Some
per row and column. The cell width is then simply the window size divided by the
number of rows. This can be done by using the ``TIOCGWINSZ`` ioctl. Some
code to demonstrate its use
.. tab:: C
@ -99,6 +100,20 @@ code to demonstrate its use
fmt.Println("rows: %v columns: %v width: %v height %v", sz.Row, sz.Col, sz.Xpixel, sz.Ypixel)
.. tab:: Bash
.. code-block:: sh
#!/bin/bash
# This uses the kitten standalone binary from kitty to get the pixel sizes
# since we cant do IOCTLs directly. Fortunately, kitten is a static exe
# pre-built for every Unix like OS under the sun.
builtin read -r rows cols < <(command stty size)
IFS=x builtin read -r width height < <(command kitten icat --print-window-size); builtin unset IFS
builtin echo "number of rows: $rows number of columns: $cols screen width: $width screen height: $height"
Note that some terminals return ``0`` for the width and height values. Such
terminals should be modified to return the correct values. Examples of

View File

@ -80,6 +80,11 @@ base application that uses kitty's graphics protocol for images.
A text mode WWW browser that supports kitty's graphics protocol to display
images.
`awrit <https://github.com/chase/awrit>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A full Chromium based web browser running in the terminal using kitty's
graphics protocol.
.. _tool_mpv:
`mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_

View File

@ -44,7 +44,8 @@ You can also create your own themes as :file:`.conf` files. Put them in the
usually, :file:`~/.config/kitty/themes`. 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
theme of that name. Here, ``Some theme name`` is the actual builtin theme name, not
its file name. Note that after doing so you have to run the kitten and
choose that theme once for your changes to be applied.

View File

@ -1,5 +1,21 @@
A message from us at KittyPatch
===============================
KittyPatch was created as a home for useful features that are unavailable
in the kitty main branch. To this end, we do not accept donations directly.
If you wish to support KittyPatch: share your ideas and spread the word.
If you still wish to donate, please `support the Free Software Foundation
<https://my.fsf.org/donate>`.
A message from the maintainer of Kitty
======================================
Support kitty development ❤️
==============================
>>>>>>> upstream/master
My goal with |kitty| is to move the stagnant terminal ecosystem forward. To that
end kitty has many foundational features, such as: :doc:`image support

View File

@ -1014,7 +1014,7 @@ static pthread_t main_thread;
static NSLock *tick_lock = NULL;
void _glfwDispatchTickCallback() {
void _glfwDispatchTickCallback(void) {
if (tick_lock && tick_callback) {
[tick_lock lock];
while(tick_callback_requested) {
@ -1026,7 +1026,7 @@ void _glfwDispatchTickCallback() {
}
static void
request_tick_callback() {
request_tick_callback(void) {
if (!tick_callback_requested) {
tick_callback_requested = true;
[NSApp performSelectorOnMainThread:@selector(tick_callback) withObject:nil waitUntilDone:NO];

View File

@ -323,7 +323,7 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
void _glfwClearDisplayLinks() {
void _glfwClearDisplayLinks(void) {
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
if (_glfw.ns.displayLinks.entries[i].displayLink) {
CVDisplayLinkStop(_glfw.ns.displayLinks.entries[i].displayLink);

19
go.mod
View File

@ -5,24 +5,25 @@ go 1.20
require (
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924
github.com/alecthomas/chroma/v2 v2.7.0
github.com/bmatcuk/doublestar v1.3.4
github.com/bmatcuk/doublestar/v4 v4.6.0
github.com/disintegration/imaging v1.6.2
github.com/dlclark/regexp2 v1.9.0
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f
github.com/seancfoley/ipaddress-go v1.5.3
github.com/shirou/gopsutil/v3 v3.23.1
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b
golang.org/x/image v0.5.0
golang.org/x/sys v0.4.0
github.com/seancfoley/ipaddress-go v1.5.4
github.com/shirou/gopsutil/v3 v3.23.3
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/image v0.7.0
golang.org/x/sys v0.7.0
)
require (
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/seancfoley/bintree v1.2.1 // indirect
github.com/shoenig/go-m1cpu v0.1.5 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect

51
go.sum
View File

@ -4,15 +4,15 @@ github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2
github.com/alecthomas/chroma/v2 v2.7.0 h1:hm1rY6c/Ob4eGclpQ7X/A3yhqBOZNUTk9q+yhyLIViI=
github.com/alecthomas/chroma/v2 v2.7.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@ -23,25 +23,32 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f h1:Ko4+g6K16vSyUrtd/pPXuQnWsiHe5BYptEtTxfwYwCc=
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f/go.mod h1:eHzfhOKbTGJEGPSdMHzU6jft192tHHt2Bu2vIZArvC0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/seancfoley/bintree v1.2.1 h1:Z/iNjRKkXnn0CTW7jDQYtjW5fz2GH1yWvOTJ4MrMvdo=
github.com/seancfoley/bintree v1.2.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
github.com/seancfoley/ipaddress-go v1.5.3 h1:fLnn4nsatd2rp3IJsVWriXv5gXn2Qiy8uxjxe4iZtTg=
github.com/seancfoley/ipaddress-go v1.5.3/go.mod h1:fpvVPC+Jso+YEhNcNiww8HQmBgKP8T4T6BTp1SLxxIo=
github.com/shirou/gopsutil/v3 v3.23.1 h1:a9KKO+kGLKEvcPIs4W62v0nu3sciVDOOOPUD0Hz7z/4=
github.com/shirou/gopsutil/v3 v3.23.1/go.mod h1:NN6mnm5/0k8jw4cBfCnJtr5L7ErOTg18tMNpgFkn0hA=
github.com/seancfoley/ipaddress-go v1.5.4 h1:ZdjewWC1J2y5ruQjWHwK6rA1tInWB6mz1ftz6uTm+Uw=
github.com/seancfoley/ipaddress-go v1.5.4/go.mod h1:fpvVPC+Jso+YEhNcNiww8HQmBgKP8T4T6BTp1SLxxIo=
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
@ -51,17 +58,20 @@ github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPR
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b h1:EqBVA+nNsObCwQoBEHy4wLU0pi7i8a4AL3pbItPdPkE=
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -70,17 +80,22 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -60,7 +60,7 @@ func extra_for(width, screen_width int) int {
return utils.Max(0, screen_width-width)/2 + 1
}
func choices(o *Options) (response string, err error) {
func GetChoices(o *Options) (response string, err error) {
response = ""
lp, err := loop.New()
if err != nil {

View File

@ -33,7 +33,7 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
}
switch o.Type {
case "yesno", "choices":
result.Response, err = choices(o)
result.Response, err = GetChoices(o)
if err != nil {
return 1, err
}

View File

@ -343,7 +343,7 @@ func run_get_loop(opts *Options, args []string) (err error) {
if reading_available_mimes {
switch metadata["status"] {
case "DATA":
available_mimes = strings.Split(utils.UnsafeBytesToString(payload), " ")
available_mimes = utils.Map(strings.TrimSpace, strings.Split(utils.UnsafeBytesToString(payload), " "))
case "OK":
case "DONE":
reading_available_mimes = false

View File

@ -258,6 +258,10 @@ func resolve_remote_name(path, defval string) string {
}
func walk(base string, patterns []string, names *utils.Set[string], pmap, path_name_map map[string]string) error {
base, err := filepath.Abs(base)
if err != nil {
return err
}
return filepath.WalkDir(base, func(path string, d fs.DirEntry, err error) error {
is_allowed := allowed(path, patterns...)
if !is_allowed {

View File

@ -90,7 +90,8 @@ for the operating system. Various special values are supported:
copy the match to the specified buffer, e.g. :code:`@a`
:code:`default`
run the default open program.
run the default open program. Note that when using the hyperlink :code:`--type`
the default is to use the kitty :doc:`hyperlink handling </open_actions>` facilities.
:code:`launch`
run :doc:`/launch` to open the program in a new kitty tab, window, overlay, etc.
@ -349,8 +350,9 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
else:
from kitty.conf.utils import to_cmdline
cwd = data['cwd']
program = get_options().open_url_with if program == 'default' else program
if text_type == 'hyperlink':
is_default_program = program == 'default'
program = get_options().open_url_with if is_default_program else program
if text_type == 'hyperlink' and is_default_program:
w = boss.window_id_map.get(target_window_id)
for m in matches:
if w is not None:

View File

@ -13,9 +13,12 @@ import (
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/dlclark/regexp2"
"github.com/seancfoley/ipaddress-go/ipaddr"
"golang.org/x/exp/slices"
)
@ -252,15 +255,6 @@ func functions_for(opts *Options) (pattern string, post_processors []PostProcess
// IPv6 with no validation
`(?:[a-fA-F0-9]{0,4}:){2,7}[a-fA-F0-9]{1,4})`)
post_processors = append(post_processors, PostProcessorMap()["ip"])
case "word":
chars := opts.WordCharacters
if chars == "" {
chars = RelevantKittyOpts().Select_by_word_characters
}
chars = regexp.QuoteMeta(chars)
chars = strings.ReplaceAll(chars, "-", "\\-")
pattern = fmt.Sprintf(`[%s\pL\pN]{%d,}`, chars, opts.MinimumMatchLength)
post_processors = append(post_processors, PostProcessorMap()["brackets"], PostProcessorMap()["quotes"])
default:
pattern = opts.Regex
if opts.Type == "linenum" {
@ -274,11 +268,112 @@ func functions_for(opts *Options) (pattern string, post_processors []PostProcess
return
}
func mark(r *regexp.Regexp, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc, text string, opts *Options) (ans []Mark) {
type Capture struct {
Text string
Text_as_runes []rune
Byte_Offsets struct {
Start, End int
}
Rune_Offsets struct {
Start, End int
}
}
func (self Capture) String() string {
return fmt.Sprintf("Capture(start=%d, end=%d, %#v)", self.Byte_Offsets.Start, self.Byte_Offsets.End, self.Text)
}
type Group struct {
Name string
IsNamed bool
Captures []Capture
}
func (self Group) LastCapture() Capture {
if len(self.Captures) == 0 {
return Capture{}
}
return self.Captures[len(self.Captures)-1]
}
func (self Group) String() string {
return fmt.Sprintf("Group(name=%#v, captures=%v)", self.Name, self.Captures)
}
type Match struct {
Groups []Group
}
func (self Match) HasNamedGroups() bool {
for _, g := range self.Groups {
if g.IsNamed {
return true
}
}
return false
}
func find_all_matches(re *regexp2.Regexp, text string) (ans []Match, err error) {
m, err := re.FindStringMatch(text)
if err != nil {
return
}
rune_to_bytes := utils.RuneOffsetsToByteOffsets(text)
get_byte_offset_map := func(groups []regexp2.Group) (ans map[int]int, err error) {
ans = make(map[int]int, len(groups)*2)
rune_offsets := make([]int, 0, len(groups)*2)
for _, g := range groups {
for _, c := range g.Captures {
if _, found := ans[c.Index]; !found {
rune_offsets = append(rune_offsets, c.Index)
ans[c.Index] = -1
}
end := c.Index + c.Length
if _, found := ans[end]; !found {
rune_offsets = append(rune_offsets, end)
ans[end] = -1
}
}
}
slices.Sort(rune_offsets)
for _, pos := range rune_offsets {
if ans[pos] = rune_to_bytes(pos); ans[pos] < 0 {
return nil, fmt.Errorf("Matches are not monotonic cannot map rune offsets to byte offsets")
}
}
return
}
for m != nil {
groups := m.Groups()
bom, err := get_byte_offset_map(groups)
if err != nil {
return nil, err
}
match := Match{Groups: make([]Group, len(groups))}
for i, g := range m.Groups() {
match.Groups[i].Name = g.Name
match.Groups[i].IsNamed = g.Name != "" && g.Name != strconv.Itoa(i)
for _, c := range g.Captures {
cn := Capture{Text: c.String(), Text_as_runes: c.Runes()}
cn.Rune_Offsets.End = c.Index + c.Length
cn.Rune_Offsets.Start = c.Index
cn.Byte_Offsets.Start, cn.Byte_Offsets.End = bom[c.Index], bom[cn.Rune_Offsets.End]
match.Groups[i].Captures = append(match.Groups[i].Captures, cn)
}
}
ans = append(ans, match)
m, _ = re.FindNextMatch(m)
}
return
}
func mark(r *regexp2.Regexp, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc, text string, opts *Options) (ans []Mark) {
sanitize_pat := regexp.MustCompile("[\r\n\x00]")
names := r.SubexpNames()
for i, v := range r.FindAllStringSubmatchIndex(text, -1) {
match_start, match_end := v[0], v[1]
all_matches, _ := find_all_matches(r, text)
for i, m := range all_matches {
full_capture := m.Groups[0].LastCapture()
match_start, match_end := full_capture.Byte_Offsets.Start, full_capture.Byte_Offsets.End
for match_end > match_start+1 && text[match_end-1] == 0 {
match_end--
}
@ -296,14 +391,14 @@ func mark(r *regexp.Regexp, post_processors []PostProcessorFunc, group_processor
continue
}
full_match = sanitize_pat.ReplaceAllLiteralString(text[match_start:match_end], "")
gd := make(map[string]string, len(names))
for x, name := range names {
if name != "" {
idx := 2 * x
if s, e := v[idx], v[idx+1]; s > -1 && e > -1 {
gd := make(map[string]string, len(m.Groups))
for idx, g := range m.Groups {
if idx > 0 && g.IsNamed {
c := g.LastCapture()
if s, e := c.Byte_Offsets.Start, c.Byte_Offsets.End; s > -1 && e > -1 {
s = utils.Max(s, match_start)
e = utils.Min(e, match_end)
gd[name] = sanitize_pat.ReplaceAllLiteralString(text[s:e], "")
gd[g.Name] = sanitize_pat.ReplaceAllLiteralString(text[s:e], "")
}
}
}
@ -314,15 +409,89 @@ func mark(r *regexp.Regexp, post_processors []PostProcessorFunc, group_processor
for k, v := range gd {
gd2[k] = v
}
if opts.Type == "regex" && len(m.Groups) > 1 && !m.HasNamedGroups() {
cp := m.Groups[1].LastCapture()
ms, me := cp.Byte_Offsets.Start, cp.Byte_Offsets.End
match_start = utils.Max(match_start, ms)
match_end = utils.Min(match_end, me)
full_match = sanitize_pat.ReplaceAllLiteralString(text[match_start:match_end], "")
}
if full_match != "" {
ans = append(ans, Mark{
Index: i, Start: match_start, End: match_end, Text: full_match, Groupdict: gd2,
})
}
}
return
}
type ErrNoMatches struct{ Type string }
func is_word_char(ch rune, current_chars []rune) bool {
return unicode.IsLetter(ch) || unicode.IsNumber(ch) || (unicode.IsMark(ch) && len(current_chars) > 0 && unicode.IsLetter(current_chars[len(current_chars)-1]))
}
func mark_words(text string, opts *Options) (ans []Mark) {
left := text
var current_run struct {
chars []rune
start, size int
}
chars := opts.WordCharacters
if chars == "" {
chars = RelevantKittyOpts().Select_by_word_characters
}
allowed_chars := make(map[rune]bool, len(chars))
for _, ch := range chars {
allowed_chars[ch] = true
}
pos := 0
post_processors := []PostProcessorFunc{PostProcessorMap()["brackets"], PostProcessorMap()["quotes"]}
commit_run := func() {
if len(current_run.chars) >= opts.MinimumMatchLength {
match_start, match_end := current_run.start, current_run.start+current_run.size
for _, f := range post_processors {
match_start, match_end = f(text, match_start, match_end)
if match_start < 0 {
break
}
}
if match_start > -1 && match_end > match_start {
full_match := text[match_start:match_end]
if len([]rune(full_match)) >= opts.MinimumMatchLength {
ans = append(ans, Mark{
Index: len(ans), Start: match_start, End: match_end, Text: full_match,
})
}
}
}
current_run.chars = nil
current_run.start = 0
current_run.size = 0
}
for {
ch, size := utf8.DecodeRuneInString(left)
if ch == utf8.RuneError {
break
}
if allowed_chars[ch] || is_word_char(ch, current_run.chars) {
if len(current_run.chars) == 0 {
current_run.start = pos
}
current_run.chars = append(current_run.chars, ch)
current_run.size += size
} else {
commit_run()
}
left = left[size:]
pos += size
}
commit_run()
return
}
func adjust_python_offsets(text string, marks []Mark) error {
// python returns rune based offsets (unicode chars not utf-8 bytes)
adjust := utils.RuneOffsetsToByteOffsets(text)
@ -356,7 +525,7 @@ func find_marks(text string, opts *Options, cli_args ...string) (sanitized_text
run_basic_matching := func() error {
pattern, post_processors, group_processors := functions_for(opts)
r, err := regexp.Compile(pattern)
r, err := regexp2.Compile(pattern, regexp2.RE2)
if err != nil {
return fmt.Errorf("Failed to compile the regex pattern: %#v with error: %w", pattern, err)
}
@ -393,6 +562,8 @@ func find_marks(text string, opts *Options, cli_args ...string) (sanitized_text
}
} else if opts.Type == "hyperlink" {
ans = hyperlinks
} else if opts.Type == "word" {
ans = mark_words(text, opts)
} else {
err = run_basic_matching()
if err != nil {

View File

@ -47,7 +47,7 @@ func TestHintMarking(t *testing.T) {
for _, m := range marks {
q := strings.NewReplacer("\n", "", "\r", "", "\x00", "").Replace(ptext[m.Start:m.End])
if diff := cmp.Diff(m.Text, q); diff != "" {
t.Fatalf("Mark start and end dont point to correct offset in text for %#v\n%s", text, diff)
t.Fatalf("Mark start (%d) and end (%d) dont point to correct offset in text for %#v\n%s", m.Start, m.End, text, diff)
}
}
return
@ -107,6 +107,18 @@ func TestHintMarking(t *testing.T) {
r(`255.255.255.256`)
r(`:1`)
reset()
opts.Type = "regex"
opts.Regex = `(?ms)^[*]?\s(\S+)`
r(`* 2b687c2 - test1`, `2b687c2`)
opts.Regex = `(?<=got: )sha256.{4}`
r(`got: sha256-L8=`, `sha256-L8=`)
reset()
opts.Type = "word"
r(`#one (two) 😍 a-1b `, `#one`, `two`, `a-1b`)
r("fōtiz час a\u0310b ", `fōtiz`, `час`, "a\u0310b")
reset()
tdir := t.TempDir()
simple := filepath.Join(tdir, "simple.py")

View File

@ -4,11 +4,33 @@
import sys
from typing import List, Optional
from ..show_key.kitty_mode import format_mods
from kitty.key_encoding import ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, SHIFT, SUPER
from ..tui.handler import Handler
from ..tui.loop import Loop, MouseEvent
from ..tui.operations import MouseTracking
mod_names = {
SHIFT: 'Shift',
ALT: 'Alt',
CTRL: 'Ctrl',
SUPER: 'Super',
HYPER: 'Hyper',
META: 'Meta',
NUM_LOCK: 'NumLock',
CAPS_LOCK: 'CapsLock',
}
def format_mods(mods: int) -> str:
if not mods:
return ''
lmods = []
for m, name in mod_names.items():
if mods & m:
lmods.append(name)
return '+'.join(lmods)
class Mouse(Handler):
mouse_tracking = MouseTracking.full

78
kittens/show_key/kitty.go Normal file
View File

@ -0,0 +1,78 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package show_key
import (
"fmt"
"strings"
"kitty/tools/cli/markup"
"kitty/tools/tui/loop"
)
var _ = fmt.Print
func csi(csi string) string {
return "CSI " + strings.NewReplacer(":", " : ", ";", " ; ").Replace(csi[:len(csi)-1]) + " " + csi[len(csi)-1:]
}
func run_kitty_loop(opts *Options) (err error) {
lp, err := loop.New(loop.FullKeyboardProtocol)
if err != nil {
return err
}
ctx := markup.New(true)
lp.OnInitialize = func() (string, error) {
lp.SetCursorVisible(false)
lp.SetWindowTitle("kitty extended keyboard protocol demo")
lp.Println("Press any keys - Ctrl+C or Ctrl+D will terminate")
return "", nil
}
lp.OnKeyEvent = func(e *loop.KeyEvent) (err error) {
e.Handled = true
if e.MatchesPressOrRepeat("ctrl+c") || e.MatchesPressOrRepeat("ctrl+d") {
lp.Quit(0)
return
}
mods := e.Mods.String()
if mods != "" {
mods += "+"
}
etype := e.Type.String()
key := e.Key
if key == " " {
key = "space"
}
key = mods + key
lp.Printf("%s %s %s\r\n", ctx.Green(key), ctx.Yellow(etype), e.Text)
lp.Println(ctx.Cyan(csi(e.CSI)))
if e.AlternateKey != "" || e.ShiftedKey != "" {
if e.ShiftedKey != "" {
lp.QueueWriteString(ctx.Dim("Shifted key: "))
lp.QueueWriteString(e.ShiftedKey + " ")
}
if e.AlternateKey != "" {
lp.QueueWriteString(ctx.Dim("Alternate key: "))
lp.QueueWriteString(e.AlternateKey + " ")
}
lp.Println()
}
lp.Println()
return
}
err = lp.Run()
if err != nil {
return
}
ds := lp.DeathSignalName()
if ds != "" {
fmt.Println("Killed by signal: ", ds)
lp.KillIfSignalled()
return
}
return
}

View File

@ -1,79 +0,0 @@
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from kittens.tui.handler import Handler
from kittens.tui.loop import Loop
from kitty.key_encoding import ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, PRESS, RELEASE, REPEAT, SHIFT, SUPER, KeyEvent, encode_key_event
mod_names = {
SHIFT: 'Shift',
ALT: 'Alt',
CTRL: 'Ctrl',
SUPER: 'Super',
HYPER: 'Hyper',
META: 'Meta',
NUM_LOCK: 'NumLock',
CAPS_LOCK: 'CapsLock',
}
def format_mods(mods: int) -> str:
if not mods:
return ''
lmods = []
for m, name in mod_names.items():
if mods & m:
lmods.append(name)
return '+'.join(lmods)
class KeysHandler(Handler):
def initialize(self) -> None:
self.cmd.set_window_title('kitty extended keyboard protocol demo')
self.cmd.set_cursor_visible(False)
self.print('Press any keys - Ctrl+C or Ctrl+D will terminate')
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
etype = {
PRESS: 'PRESS',
REPEAT: 'REPEAT',
RELEASE: 'RELEASE'
}[key_event.type]
mods = format_mods(key_event.mods)
if mods:
mods += '+'
kk = key_event.key
if kk == ' ':
kk = 'SPACE'
key = f'{mods}{kk} '
self.cmd.colored(key, 'green')
self.cmd.colored(etype + ' ', 'yellow')
self.cmd.styled(key_event.text, italic=True)
self.print()
rep = f'CSI {encode_key_event(key_event)[2:]}'
rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1]
self.cmd.styled(rep, fg='magenta')
if (key_event.shifted_key or key_event.alternate_key):
self.print()
if key_event.shifted_key:
self.cmd.colored('Shifted key: ', 'gray')
self.print(key_event.shifted_key + ' ', end='')
if key_event.alternate_key:
self.cmd.colored('Alternate key: ', 'gray')
self.print(key_event.alternate_key + ' ', end='')
self.print()
self.print()
def on_interrupt(self) -> None:
self.quit_loop(0)
def on_eot(self) -> None:
self.quit_loop(0)
def main() -> None:
loop = Loop()
handler = KeysHandler()
loop.loop(handler)
raise SystemExit(loop.return_code)

View File

@ -0,0 +1,76 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package show_key
import (
"errors"
"fmt"
"io"
"kitty/tools/cli/markup"
"kitty/tools/tty"
"os"
)
var _ = fmt.Print
func print_key(buf []byte, ctx *markup.Context) {
const ctrl_keys = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
unix := ""
send_text := ""
for _, ch := range buf {
switch {
case int(ch) < len(ctrl_keys):
unix += "^" + ctrl_keys[ch:ch+1]
case ch == 127:
unix += "^?"
default:
unix += string(rune(ch))
}
}
for _, ch := range string(buf) {
q := fmt.Sprintf("%#v", string(ch))
send_text += q[1 : len(q)-1]
}
os.Stdout.WriteString(unix + "\t\t")
os.Stdout.WriteString(ctx.Yellow(send_text) + "\r\n")
}
func run_legacy_loop(opts *Options) (err error) {
term, err := tty.OpenControllingTerm(tty.SetRaw)
if err != nil {
return err
}
defer func() {
term.RestoreAndClose()
}()
if opts.KeyMode != "unchanged" {
os.Stdout.WriteString("\x1b[?1")
switch opts.KeyMode {
case "normal":
os.Stdout.WriteString("l")
default:
os.Stdout.WriteString("h")
}
defer func() {
os.Stdout.WriteString("\x1b[?1l")
}()
}
fmt.Print("Press any keys - Ctrl+D will terminate this program\r\n")
ctx := markup.New(true)
fmt.Print(ctx.Green("UNIX\t\tsend_text\r\n"))
buf := make([]byte, 64)
for {
n, err := term.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
print_key(buf[:n], ctx)
if n == 1 && buf[0] == 4 {
break
}
}
return
}

27
kittens/show_key/main.go Normal file
View File

@ -0,0 +1,27 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package show_key
import (
"fmt"
"kitty/tools/cli"
)
var _ = fmt.Print
func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
if opts.KeyMode == "kitty" {
err = run_kitty_loop(opts)
} else {
err = run_legacy_loop(opts)
}
if err != nil {
rc = 1
}
return
}
func EntryPoint(parent *cli.Command) {
create_cmd(parent, main)
}

View File

@ -2,56 +2,9 @@
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from typing import List
from kittens.tui.operations import raw_mode, styled
from kitty.cli import parse_args
from kitty.cli_stub import ShowKeyCLIOptions
ctrl_keys = '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'
def print_key(raw: bytearray) -> None:
unix = ''
for ch in raw:
if ch < len(ctrl_keys):
unix += f'^{ctrl_keys[ch]}'
elif ch == 127:
unix += '^?'
else:
unix += chr(ch)
print(unix + '\t\t', end='')
for ch in raw:
x = chr(ch).encode('utf-8')
print(styled(repr(x)[2:-1], fg='yellow'), end='')
print(end='\r\n', flush=True)
def read_keys() -> None:
fd = sys.stdin.fileno()
while True:
try:
raw = bytearray(os.read(fd, 64))
except OSError as err:
print(err, file=sys.stderr, flush=True)
break
if not raw:
break
print_key(raw)
if len(raw) == 1 and raw[0] == 4:
break
def legacy_main() -> None:
print('Press any keys - Ctrl+D will terminate this program', end='\r\n', flush=True)
print(styled('UNIX', italic=True, fg='green'), styled('send_text', italic=True, fg='green'), sep='\t\t', end='\r\n')
with raw_mode():
read_keys()
OPTIONS = r'''
--key-mode -m
default=normal
@ -66,17 +19,7 @@ usage = ''
def main(args: List[str]) -> None:
cli_opts, items = parse_args(args[1:], OPTIONS, '', help_text, 'kitty +kitten show_key', result_class=ShowKeyCLIOptions)
if cli_opts.key_mode == 'kitty':
from .kitty_mode import main as kitty_main
return kitty_main()
if cli_opts.key_mode != 'unchanged':
print(end='\x1b[?1' + ('l' if cli_opts.key_mode == 'normal' else 'h'), flush=True)
try:
return legacy_main()
finally:
if cli_opts.key_mode != 'unchanged':
print(end='\x1b[?1l', flush=True)
raise SystemExit('This should be reun as kitten show_key')
if __name__ == '__main__':
@ -86,3 +29,4 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['short_desc'] = help_text

View File

@ -19,7 +19,7 @@ import (
"kitty/tools/utils/paths"
"kitty/tools/utils/shlex"
"github.com/bmatcuk/doublestar"
"github.com/bmatcuk/doublestar/v4"
"golang.org/x/sys/unix"
)
@ -145,7 +145,7 @@ func resolve_file_spec(spec string, is_glob bool) ([]string, error) {
ans = paths_ctx.AbspathFromHome(ans)
}
if is_glob {
files, err := doublestar.Glob(ans)
files, err := doublestar.FilepathGlob(ans)
if err != nil {
return nil, fmt.Errorf("%s is not a valid glob pattern with error: %w", spec, err)
}

View File

@ -78,6 +78,9 @@ func TestSSHConfigParsing(t *testing.T) {
rt(`unset ["a"]`)
conf = "env LOCAL_ENV=_kitty_copy_env_var_"
rt(`export ["LOCAL_ENV","LOCAL_VAL",false]`)
conf = "env a=b\nhostname 2\ncolor_scheme xyz"
hostname = "2"
rt()
ci, err := ParseCopyInstruction("--exclude moose --dest=target " + cf)
if err != nil {

View File

@ -16,6 +16,7 @@ import (
"net/url"
"os"
"os/exec"
"os/signal"
"os/user"
"path"
"path/filepath"
@ -669,6 +670,9 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
if err != nil {
return 1, err
}
sigs := make(chan os.Signal, 8)
signal.Notify(sigs, unix.SIGINT, unix.SIGTERM)
if !cd.request_data {
rq := fmt.Sprintf("id=%s:pwfile=%s:pw=%s", cd.replacements["REQUEST_ID"], cd.replacements["PASSWORD_FILENAME"], cd.replacements["DATA_PASSWORD"])
err := term.ApplyOperations(tty.TCSANOW, tty.SetNoEcho)
@ -685,11 +689,22 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
return 1, err
}
}
go func() {
_ = <-sigs
// ignore any interrupt and terminate signals as they will usually be sent to the ssh child process as well
// and we are waiting on that.
}()
err = c.Wait()
drain_potential_tty_garbage(term)
signal.Reset(unix.SIGINT, unix.SIGTERM)
if err != nil {
var exit_err *exec.ExitError
if errors.As(err, &exit_err) {
if state := exit_err.ProcessState.String(); state == "signal: interrupt" {
unix.Kill(os.Getpid(), unix.SIGINT)
// Give the signal time to be delivered
time.Sleep(20 * time.Millisecond)
}
return exit_err.ExitCode(), nil
}
return 1, err

View File

@ -20,7 +20,7 @@ def option_text() -> str:
--glob
type=bool-set
Interpret file arguments as glob patterns. Globbing is based on
Based on standard wildcards with the addition that ``/**/`` matches any number of directories.
standard wildcards with the addition that ``/**/`` matches any number of directories.
See the :link:`detailed syntax <https://github.com/bmatcuk/doublestar#patterns>`.
@ -35,7 +35,7 @@ variables and ~ are not expanded.
--exclude
type=list
A glob pattern. Files with names matching this pattern are excluded from being
transferred. Useful when adding directories. Can
transferred. Only used when copying directories. Can
be specified multiple times, if any of the patterns match the file will be
excluded. If the pattern includes a :code:`/` then it will match against the full
path, not just the filename. In such patterns you can use :code:`/**/` to match zero
@ -77,7 +77,11 @@ by spaces. The hostname can include an optional username in the form
first hostname specification is found. Note that matching of hostname is done
against the name you specify on the command line to connect to the remote host.
If you wish to include the same basic configuration for many different hosts,
you can do so with the :ref:`include <include>` directive.
you can do so with the :ref:`include <include>` directive. In version 0.28.0
the behavior of this option was changed slightly, now, when a hostname is encountered
all its config values are set to defaults instead of being inherited from a previous
matching hostname block. In particular it means hostnames dont inherit configurations,
thereby avoiding hard to understand action-at-a-distance.
''')
opt('interpreter', 'sh', long_text='''

View File

@ -3,7 +3,11 @@
package themes
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
@ -96,3 +100,48 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
func EntryPoint(parent *cli.Command) {
create_cmd(parent, main)
}
func parse_theme_metadata() error {
raw, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
paths := utils.Splitlines(utils.UnsafeBytesToString(raw))
ans := make([]*themes.ThemeMetadata, 0, len(paths))
for _, path := range paths {
if path != "" {
metadata, _, err := themes.ParseThemeMetadata(path)
if metadata.Name == "" {
metadata.Name = themes.ThemeNameFromFileName(filepath.Base(path))
}
if err != nil {
return err
}
ans = append(ans, metadata)
}
}
raw, err = json.Marshal(ans)
if err != nil {
return err
}
_, err = os.Stdout.Write(raw)
if err != nil {
return err
}
return nil
}
func ParseEntryPoint(parent *cli.Command) {
parent.AddSubCommand(&cli.Command{
Name: "__parse_theme_metadata__",
Hidden: true,
Run: func(cmd *cli.Command, args []string) (rc int, err error) {
err = parse_theme_metadata()
if err != nil {
rc = 1
}
return
},
})
}

View File

@ -92,6 +92,7 @@ func serialize_favorites(favs []rune) string {
}
var loaded_favorites []rune
var favorites_loaded_from_user_config bool
func favorites_path() string {
return filepath.Join(utils.ConfigDir(), "unicode-input-favorites.conf")
@ -102,8 +103,10 @@ func load_favorites(refresh bool) []rune {
raw, err := os.ReadFile(favorites_path())
if err == nil {
loaded_favorites = parse_favorites(utils.UnsafeBytesToString(raw))
favorites_loaded_from_user_config = true
} else {
loaded_favorites = DEFAULT_SET
favorites_loaded_from_user_config = false
}
}
return loaded_favorites
@ -427,8 +430,9 @@ func (self *handler) handle_favorites_key_event(event *loop.KeyEvent) {
self.lp.Quit(1)
return
}
raw := serialize_favorites(load_favorites(false))
fp := favorites_path()
if len(load_favorites(false)) == 0 || !favorites_loaded_from_user_config {
raw := serialize_favorites(load_favorites(false))
err = os.MkdirAll(filepath.Dir(fp), 0o755)
if err != nil {
self.err = fmt.Errorf("Failed to create config directory to store favorites in: %w", err)
@ -441,13 +445,8 @@ func (self *handler) handle_favorites_key_event(event *loop.KeyEvent) {
self.lp.Quit(1)
return
}
resume, err := self.lp.Suspend()
if err != nil {
self.err = err
self.lp.Quit(1)
return
}
defer resume()
err = self.lp.SuspendAndRun(func() error {
cmd := exec.Command(exe, "edit-in-kitty", "--type=overlay", fp)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
@ -461,6 +460,13 @@ func (self *handler) handle_favorites_key_event(event *loop.KeyEvent) {
var ln string
fmt.Scanln(&ln)
}
return nil
})
if err != nil {
self.err = err
self.lp.Quit(1)
return
}
}
}

View File

@ -82,7 +82,7 @@ func (self *table) current_codepoint() rune {
func (self *table) set_codepoints(codepoints []rune, mode Mode, current_idx int) {
self.codepoints = codepoints
if self.codepoints != nil {
if self.codepoints != nil && mode != FAVORITES && mode != HEX {
slices.Sort(self.codepoints)
}
self.mode = mode

View File

@ -1,7 +1,7 @@
#version GLSL_VERSION
uniform uvec2 viewport;
uniform uint colors[9];
uniform float background_opacity;
uniform float background_opacity, do_srgb_correction;
uniform float tint_opacity, tint_premult;
uniform float gamma_lut[256];
in vec4 rect; // left, top, right, bottom
@ -22,6 +22,14 @@ const uvec2 pos_map[] = uvec2[4](
uvec2(LEFT, TOP)
);
float linear2srgb(float x) {
// Linear to sRGB conversion. Needed to match alpha from the cell shader
float lower = 12.92 * x;
float upper = 1.055 * pow(x, 1.0f / 2.4f) - 0.055f;
return mix(lower, upper, step(0.0031308f, x));
}
float to_color(uint c) {
return gamma_lut[c & FF];
}
@ -45,5 +53,6 @@ void main() {
color3 = is_window_bg * window_bg + (1. - is_window_bg) * color3;
float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg) * background_opacity;
float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg) * background_opacity;
final_opacity = do_srgb_correction * linear2srgb(final_opacity) + (1. - do_srgb_correction) * final_opacity;
color = vec4(color3 * final_premult_opacity, final_opacity);
}

View File

@ -10,6 +10,7 @@ import sys
from contextlib import contextmanager, suppress
from functools import partial
from gettext import gettext as _
from gettext import ngettext
from time import monotonic, sleep
from typing import (
TYPE_CHECKING,
@ -1010,9 +1011,8 @@ class Boss:
tm = tab.tab_manager_ref()
if tm is not None:
tm.set_active_tab(tab)
self.confirm(_(
'Are you sure you want to close this tab, it has {}'
' windows running?').format(num),
self.confirm(ngettext('Are you sure you want to close this tab, it has one window running?',
'Are you sure you want to close this tab, it has {} windows running?', num).format(num),
self.handle_close_tab_confirmation, tab.id,
window=tab.active_window,
)
@ -1602,8 +1602,8 @@ class Boss:
if tm is not None:
w = tm.active_window
self.confirm(
_('Are you sure you want to close this OS window, it has {}'
' windows running?').format(num),
ngettext('Are you sure you want to close this OS window, it has one window running?',
'Are you sure you want to close this OS window, it has {} windows running', num).format(num),
self.handle_close_os_window_confirmation, os_window_id,
window=w,
)
@ -1642,7 +1642,8 @@ class Boss:
return
assert tm is not None
self.confirm(
_('Are you sure you want to quit kitty, it has {} windows running?').format(num),
ngettext('Are you sure you want to quit kitty, it has one window running?',
'Are you sure you want to quit kitty, it has {} windows running?', num).format(num),
self.handle_quit_confirmation,
window=tm.active_window,
)
@ -1812,7 +1813,8 @@ class Boss:
'tab', '''
Change the title of the active tab interactively, by typing in the new title.
If you specify an argument to this action then that is used as the title instead of asking for it.
Use the empty string ("") to reset the title to default. For example::
Use the empty string ("") to reset the title to default. Use a space (" ") to indicate that the
prompt should not be pre-filled. For example::
# interactive usage
map f1 set_tab_title
@ -1820,19 +1822,24 @@ class Boss:
map f2 set_tab_title some title
# reset to default
map f3 set_tab_title ""
# interactive usage without prefilled prompt
map f3 set_tab_title " "
'''
)
def set_tab_title(self, title: Optional[str] = None) -> None:
tab = self.active_tab
if tab:
if title is not None:
if title is not None and title not in ('" "', "' '"):
if title in ('""', "''"):
title = ''
tab.set_title(title)
return
prefilled = tab.name or tab.title
if title in ('" "', "' '"):
prefilled = ''
args = [
'--name=tab-title', '--message', _('Enter the new title for this tab below.'),
'--default', tab.name or tab.title, 'do_set_tab_title', str(tab.id)]
'--default', prefilled, 'do_set_tab_title', str(tab.id)]
self.run_kitten_with_metadata('ask', args)
def do_set_tab_title(self, title: str, tab_id: int) -> None:
@ -2628,7 +2635,7 @@ class Boss:
where = 'new' if args[0] == 'new-tab' else args[0][4:]
return self._move_window_to(target_tab_id=where)
ct = self.active_tab
items: List[Tuple[Union[str, int], str]] = [(t.id, t.title) for t in self.all_tabs if t is not ct]
items: List[Tuple[Union[str, int], str]] = [(t.id, t.effective_title) for t in self.all_tabs if t is not ct]
items.append(('new_tab', 'New tab'))
items.append(('new_os_window', 'New OS Window'))
target_window = self.active_window

View File

@ -37,6 +37,22 @@ in float colored_sprite;
out vec4 final_color;
// Util functions {{{
float linear2srgb(float x) {
// Linear to sRGB conversion.
float lower = 12.92 * x;
float upper = 1.055 * pow(x, 1.0f / 2.4f) - 0.055f;
return mix(lower, upper, step(0.0031308f, x));
}
float srgb2linear(float x) {
// sRGB to linear conversion
float lower = x / 12.92;
float upper = pow((x + 0.055f) / 1.055f, 2.4f);
return mix(lower, upper, step(0.04045f, x));
}
vec4 alpha_blend(vec4 over, vec4 under) {
// Alpha blend two colors returning the resulting color pre-multiplied by its alpha
// and its alpha.
@ -103,20 +119,9 @@ vec4 vec4_premul(vec4 rgba) {
#ifdef NEEDS_FOREGROUND
// sRGB luminance values
const vec3 Y = vec3(0.2126, 0.7152, 0.0722);
const float gamma_factor = 2.2;
// Scaling factor for the extra text-alpha adjustment for luminance-difference.
const float text_gamma_scaling = 0.5;
float linear2srgb(float x) {
// Approximation of linear-to-sRGB conversion
return pow(x, 1.0 / gamma_factor);
}
float srgb2linear(float x) {
// Approximation of sRGB-to-linear conversion
return pow(x, gamma_factor);
}
float clamp_to_unit_float(float x) {
// Clamp value to suitable output range
return clamp(x, 0.0f, 1.0f);
@ -179,6 +184,12 @@ void main() {
vec4 fg = calculate_foreground(background);
#ifdef TRANSPARENT
final_color = alpha_blend_premul(fg, vec4_premul(background, bg_alpha));
// Adjust the transparent alpha-channel to account for incorrect
// gamma-blending performed by the compositor (true for at least wlroots,
// picom, GNOME, MacOS).
// This is the last pass:
final_color.a = linear2srgb(final_color.a);
#else
final_color = alpha_blend_premul(fg, background);
#endif
@ -193,7 +204,7 @@ void main() {
#endif
#ifdef BACKGROUND
#if defined(TRANSPARENT)
#ifdef TRANSPARENT
final_color = vec4_premul(background, bg_alpha);
#else
final_color = vec4(background, draw_bg);
@ -202,6 +213,10 @@ void main() {
#ifdef FOREGROUND
final_color = calculate_foreground(); // pre-multiplied foreground
// This is the last pass, adjust alpha to compensate for gamma-incorrect
// blending in compositor:
final_color.a = linear2srgb(final_color.a);
#endif
}

View File

@ -3,6 +3,7 @@
#define {WHICH_PROGRAM}
#define NOT_TRANSPARENT
#define BOLD_SHIFT {BOLD_SHIFT}
#define DECORATION_SHIFT {DECORATION_SHIFT}
#define REVERSE_SHIFT {REVERSE_SHIFT}
#define STRIKE_SHIFT {STRIKE_SHIFT}
@ -17,6 +18,7 @@ layout(std140) uniform CellRenderData {
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted;
uint bold_is_bright;
uint xnum, ynum, cursor_fg_sprite_idx;
float cursor_x, cursor_y, cursor_w;
@ -93,6 +95,22 @@ vec3 color_to_vec(uint c) {
return vec3(gamma_lut[r], gamma_lut[g], gamma_lut[b]);
}
uint byte_to_bool(uint n) {
uint n1 = (n >> 1) | n;
uint n2 = (n1 >> 2) | n1;
uint n3 = (n2 >> 4) | n2;
return n3 & 1u;
}
uint brighten_color(uint c, uint is_bold) {
uint table_idx = (c >> 8) & 0xFFu;
uint is_table_color = c & 1u;
uint is_rgb_color = byte_to_bool(c & 0xFEu);
uint is_8bit_color = byte_to_bool(table_idx & 0xF8u);
uint should_brighten = bold_is_bright * is_bold * (1u >> (is_rgb_color + is_8bit_color)) * is_table_color;
return c | (0x800u * should_brighten);
}
uint resolve_color(uint c, uint defval) {
// Convert a cell color to an actual color based on the color table
int t = int(c & BYTE_MASK);
@ -161,6 +179,7 @@ void main() {
// set cell color indices {{{
uvec2 default_colors = uvec2(default_fg, default_bg);
uint text_attrs = sprite_coords[3];
uint is_bold = ((text_attrs >> BOLD_SHIFT) & ONE);
uint is_reversed = ((text_attrs >> REVERSE_SHIFT) & ONE);
uint is_inverted = is_reversed + inverted;
int fg_index = fg_index_map[is_inverted];
@ -170,10 +189,10 @@ void main() {
float cell_has_block_cursor = cell_has_cursor * is_block_cursor;
int mark = int(text_attrs >> MARK_SHIFT) & MARK_MASK;
uint has_mark = uint(step(1, float(mark)));
uint bg_as_uint = resolve_color(colors[bg_index], default_colors[bg_index]);
uint bg_as_uint = resolve_color(brighten_color(colors[bg_index], is_bold), default_colors[bg_index]);
bg_as_uint = has_mark * color_table[NUM_COLORS + mark] + (ONE - has_mark) * bg_as_uint;
vec3 bg = color_to_vec(bg_as_uint);
uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]);
uint fg_as_uint = resolve_color(brighten_color(colors[fg_index], is_bold), default_colors[fg_index]);
// }}}
// Foreground {{{

View File

@ -429,7 +429,7 @@ class Child:
return environ_of_process(pid)
return {}
def send_signal_for_key(self, key_num: int) -> bool:
def send_signal_for_key(self, key_num: bytes) -> bool:
import signal
import termios
if self.child_fd is None:

View File

@ -960,7 +960,7 @@ cocoa_set_dock_icon(PyObject UNUSED *self, PyObject *args) {
static NSSound *beep_sound = nil;
static void
cleanup() {
cleanup(void) {
@autoreleasepool {
if (dockMenu) [dockMenu release];

View File

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

View File

@ -155,7 +155,7 @@ font_descriptor_from_python(PyObject *src) {
static CTFontCollectionRef all_fonts_collection_data = NULL;
static CTFontCollectionRef
all_fonts_collection() {
all_fonts_collection(void) {
if (all_fonts_collection_data == NULL) all_fonts_collection_data = CTFontCollectionCreateFromAvailableFonts(NULL);
return all_fonts_collection_data;
}

View File

@ -47,7 +47,7 @@ user_cache_dir(void) {
}
static PyObject*
process_group_map() {
process_group_map(void) {
int num_of_processes = proc_listallpids(NULL, 0);
size_t bufsize = sizeof(pid_t) * (num_of_processes + 1024);
FREE_AFTER_FUNCTION pid_t *buf = malloc(bufsize);

View File

@ -94,6 +94,7 @@ def edit(args: List[str]) -> None:
def shebang(args: List[str]) -> None:
from kitty.constants import kitten_exe
script_path = args[1]
cmd = args[2:]
if cmd == ['__ext__']:
@ -111,7 +112,7 @@ def shebang(args: List[str]) -> None:
cmd = line.split(' ')
else:
cmd = line.split(' ', maxsplit=1)
os.execvp(cmd[0], cmd + [script_path])
os.execvp(kitten_exe(), ['kitten', '__confirm_and_run_shebang__'] + cmd + [script_path])
def run_kitten(args: List[str]) -> None:

View File

@ -265,6 +265,7 @@ CELL_FG_PROGRAM: int
CELL_PROGRAM: int
CELL_SPECIAL_PROGRAM: int
CSI: int
BOLD: int
DCS: int
DECORATION: int
DIM: int

View File

@ -543,6 +543,7 @@ START_ALLOW_CASE_RANGE
case 0xe0b0 ... 0xe0bf: // powerline box drawing
case 0x1fb00 ... 0x1fb8b: // symbols for legacy computing
case 0x1fba0 ... 0x1fbae:
case 0x1fb90: // inverse medium shade
return BOX_FONT;
default:
*is_emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell);
@ -585,6 +586,9 @@ START_ALLOW_CASE_RANGE
return 0x151 + ch - 0x1fba0;
case 0x2800 ... 0x28ff:
return 0x160 + ch - 0x2800;
case 0x1fb90:
// Allocated to allow for 0x1fb8c ... 0x1fb94 eventually
return 0x25f + ch - 0x1fb8c;
default:
return 0xffff;
}

View File

@ -617,37 +617,18 @@ def shade(buf: BufType, width: int, height: int, light: bool = False, invert: bo
square_sz = max(1, width // 12)
number_of_rows = height // square_sz
number_of_cols = width // square_sz
nums = tuple(range(square_sz))
dest = bytearray(width * height) if invert else buf
nums = range(square_sz)
for r in range(number_of_rows):
is_odd = r % 2 != 0
if is_odd:
for c in range(number_of_cols):
if invert ^ ((r % 2 != c % 2) or (light and r % 2 == 1)):
continue
fill_even = r % 4 == 0
for yr in nums:
y = r * square_sz + yr
if y >= height:
break
off = width * y
for c in range(number_of_cols):
if light:
fill = (c % 4) == (0 if fill_even else 2)
else:
fill = (c % 2 == 0) == fill_even
if fill:
offset = width * y
for xc in nums:
x = (c * square_sz) + xc
if x >= width:
break
dest[off + x] = 255
if invert:
for y in range(height):
off = width * y
for x in range(width):
q = off + x
buf[q] = 255 - dest[q]
x = c * square_sz + xc
buf[offset + x] = 255
def quad(buf: BufType, width: int, height: int, x: int = 0, y: int = 0) -> None:
@ -883,7 +864,8 @@ box_chars: Dict[str, List[Callable[[BufType, int, int], Any]]] = {
'': [p(eight_block, which=(4, 5, 6, 7))],
'': [p(shade, light=True)],
'': [shade],
'': [p(shade, invert=True)],
'': [p(shade, light=True, invert=True)],
'🮐': [p(shade, invert=True)],
'': [p(eight_bar, horizontal=True)],
'': [p(eight_bar, which=7)],
'': [p(quad, y=1)],

View File

@ -561,6 +561,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
img->root_frame_data_loaded = false;
img->is_drawn = false;
img->current_frame_shown_at = 0;
img->extra_framecnt = 0;
free_image(self, img);
*is_dirty = true;
self->layers_dirty = true;

View File

@ -715,7 +715,7 @@ class EditCmd:
self.is_local_file = False
with suppress(OSError):
st = os.stat(self.file_localpath)
self.is_local_file = (st.st_dev, st.st_ino) == self.file_inode
self.is_local_file = (st.st_dev, st.st_ino) == self.file_inode and os.access(self.file_localpath, os.W_OK | os.R_OK)
if not self.is_local_file:
import tempfile
self.tdir = tempfile.mkdtemp()

View File

@ -332,7 +332,7 @@ exec_kitten(int argc, char *argv[], char *exe_dir) {
newargv[argc] = 0;
newargv[0] = "kitten";
errno = 0;
execv(exe, argv);
execv(exe, newargv);
fprintf(stderr, "Failed to execute kitten (%s) with error: %s\n", exe, strerror(errno));
exit(1);
}

View File

@ -24,7 +24,7 @@ cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
// Read the maximum argument size for processes
static int
get_argmax() {
get_argmax(void) {
int argmax;
int mib[] = { CTL_KERN, KERN_ARGMAX };
size_t size = sizeof(argmax);

View File

@ -1328,6 +1328,10 @@ color is chosen to match the background color of the neighboring tab.
)
egr() # }}}
opt('bold_is_bright', 'no',
option_type='to_bool', ctype='bool',
long_text='Display bold text with bright colors'
)
# colors {{{
agr('colors', 'Color scheme')
@ -3990,18 +3994,31 @@ You can create shortcuts to clear/reset the terminal. For example::
If you want to operate on all kitty windows instead of just the current one, use
:italic:`all` instead of :italic:`active`.
It is also possible to remap :kbd:`Ctrl+L` to both scroll the current screen
Some useful functions that can be defined in the shell rc files to perform various kinds of
clearing of the current window:
.. code-block:: sh
clear-only-screen() {
printf "\e[H\e[2J"
}
clear-screen-and-scrollback() {
printf "\e[H\e[3J"
}
clear-screen-saving-contents-in-scrollback() {
printf "\e[H\e[22J"
}
For instance, using these functions, it is possible to remap :kbd:`Ctrl+L` to both scroll the current screen
contents into the scrollback buffer and clear the screen, instead of just
clearing the screen, for example, for ZSH add the following to :file:`~/.zshrc`:
clearing the screen. For ZSH, in :file:`~/.zshrc` after the above functions, add:
.. code-block:: zsh
scroll-and-clear-screen() {
printf '\\n%.0s' {1..$LINES}
zle clear-screen
}
zle -N scroll-and-clear-screen
bindkey '^l' scroll-and-clear-screen
zle -N clear-screen-saving-contents-in-scrollback
bindkey '^l' clear-screen-saving-contents-in-scrollback
'''
)

View File

@ -100,6 +100,9 @@ class Parser:
def bold_font(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['bold_font'] = str(val)
def bold_is_bright(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['bold_is_bright'] = to_bool(val)
def bold_italic_font(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['bold_italic_font'] = str(val)

View File

@ -837,6 +837,19 @@ convert_from_opts_dim_opacity(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_bold_is_bright(PyObject *val, Options *opts) {
opts->bold_is_bright = PyObject_IsTrue(val);
}
static void
convert_from_opts_bold_is_bright(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "bold_is_bright");
if (ret == NULL) return;
convert_from_python_bold_is_bright(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_mark1_foreground(PyObject *val, Options *opts) {
opts->mark1_foreground = color_as_int(val);
@ -1188,6 +1201,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_dim_opacity(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_bold_is_bright(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark1_foreground(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark1_background(py_opts, opts);

View File

@ -71,6 +71,7 @@ option_names = ( # {{{
'bell_on_tab',
'bell_path',
'bold_font',
'bold_is_bright',
'bold_italic_font',
'box_drawing_scale',
'clear_all_mouse_actions',
@ -490,6 +491,7 @@ class Options:
bell_on_tab: str = '🔔 '
bell_path: typing.Optional[str] = None
bold_font: str = 'auto'
bold_is_bright: bool = False
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

View File

@ -422,6 +422,7 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case 9:
case 99:
case 777:
case 1337:
START_DISPATCH
DISPATCH_OSC_WITH_CODE(desktop_notify)
END_DISPATCH

View File

@ -1628,6 +1628,26 @@ screen_clear_scrollback(Screen *self) {
}
}
static Line* visual_line_(Screen *self, int y_);
static void
screen_move_into_scrollback(Screen *self) {
if (self->linebuf != self->main_linebuf || self->margin_top != 0 || self->margin_bottom != self->lines - 1) return;
unsigned int num_of_lines_to_move = self->lines;
while (num_of_lines_to_move) {
Line *line = visual_line_(self, num_of_lines_to_move-1);
if (!line_is_empty(line)) break;
num_of_lines_to_move--;
}
if (num_of_lines_to_move) {
unsigned int top, bottom;
for (; num_of_lines_to_move; num_of_lines_to_move--) {
top = 0, bottom = num_of_lines_to_move - 1;
INDEX_UP
}
}
}
void
screen_erase_in_display(Screen *self, unsigned int how, bool private) {
/* Erases display in a specific way.
@ -1640,6 +1660,8 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
including cursor position.
* ``2`` -- Erases complete display. All lines are erased
and changed to single-width. Cursor does not move.
* ``22`` -- Copy screen contents into scrollback if in main screen,
then do the same as ``2``.
* ``3`` -- Erase complete display and scrollback buffer as well.
:param bool private: when ``True`` character attributes are left unchanged
*/
@ -1649,6 +1671,10 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
a = self->cursor->y + 1; b = self->lines; break;
case 1:
a = 0; b = self->cursor->y; break;
case 22:
screen_move_into_scrollback(self);
how = 2;
/* fallthrough */
case 2:
case 3:
grman_clear(self->grman, how == 3, self->cell_size);
@ -1671,7 +1697,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
self->is_dirty = true;
clear_selection(&self->selections);
}
if (how != 2) {
if (how < 2) {
screen_erase_in_line(self, how, private);
if (how == 1) linebuf_clear_attrs_and_dirty(self->linebuf, self->cursor->y);
}

View File

@ -303,6 +303,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_color, use_cell_for_selection_bg;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted;
GLuint bold_is_bright;
GLuint xnum, ynum, cursor_fg_sprite_idx;
GLfloat cursor_x, cursor_y, cursor_w;
@ -374,6 +375,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
rd->inverted = inverted ? 1 : 0;
rd->background_opacity = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f;
rd->bold_is_bright = OPT(bold_is_bright) ? 1 : 0;
#undef COLOR
rd->url_color = OPT(url_color); rd->url_style = OPT(url_style);
@ -434,8 +436,13 @@ draw_bg(OSWindow *w) {
glUniform1i(bgimage_program_layout.image_location, BGIMAGE_UNIT);
glUniform1f(bgimage_program_layout.opacity_location, OPT(background_opacity));
#ifdef __APPLE__
int window_width = w->window_width, window_height = w->window_height;
#else
int window_width = w->viewport_width, window_height = w->viewport_height;
#endif
glUniform4f(bgimage_program_layout.sizes_location,
(GLfloat)w->viewport_width, (GLfloat)w->viewport_height, (GLfloat)w->bgimage->width, (GLfloat)w->bgimage->height);
(GLfloat)window_width, (GLfloat)window_height, (GLfloat)w->bgimage->width, (GLfloat)w->bgimage->height);
glUniform1f(bgimage_program_layout.premult_location, w->is_semi_transparent ? 1.f : 0.f);
GLfloat tiled = 0.f;;
GLfloat left = -1.0, top = 1.0, right = 1.0, bottom = -1.0;
@ -446,12 +453,12 @@ draw_bg(OSWindow *w) {
tiled = 0.f; break;
case CENTER_CLAMPED:
tiled = 1.f;
if (w->viewport_width > (int)w->bgimage->width) {
GLfloat frac = (w->viewport_width - w->bgimage->width) / (GLfloat)w->viewport_width;
if (window_width > (int)w->bgimage->width) {
GLfloat frac = (window_width - w->bgimage->width) / (GLfloat)window_width;
left += frac; right += frac;
}
if (w->viewport_height > (int)w->bgimage->height) {
GLfloat frac = (w->viewport_height - w->bgimage->height) / (GLfloat)w->viewport_height;
if (window_height > (int)w->bgimage->height) {
GLfloat frac = (window_height - w->bgimage->height) / (GLfloat)window_height;
top -= frac; bottom -= frac;
}
break;
@ -979,7 +986,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, const ScreenRenderData *srd, float
// }}}
// Borders {{{
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_tint_opacity, BORDER_tint_premult, BORDER_colors, BORDER_gamma_lut, NUM_BORDER_UNIFORMS };
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_tint_opacity, BORDER_tint_premult, BORDER_colors, BORDER_gamma_lut, BORDER_do_srgb_correction, NUM_BORDER_UNIFORMS };
static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
static void
@ -989,6 +996,7 @@ init_borders_program(void) {
SET_LOC(background_opacity)
SET_LOC(tint_opacity)
SET_LOC(tint_premult)
SET_LOC(do_srgb_correction)
SET_LOC(colors)
SET_LOC(gamma_lut)
#undef SET_LOC
@ -1012,6 +1020,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
float background_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f;
float tint_opacity = background_opacity;
float tint_premult = background_opacity;
float do_srgb_correction = 1.0f;
if (has_bgimage(w)) {
glEnable(GL_BLEND);
BLEND_ONTO_OPAQUE;
@ -1020,6 +1029,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
background_opacity = 1.0f;
tint_opacity = OPT(background_tint) * OPT(background_tint_gaps);
tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f;
do_srgb_correction = 0.0f;
}
if (num_border_rects) {
@ -1041,6 +1051,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
glUniform1f(border_uniform_locations[BORDER_background_opacity], background_opacity);
glUniform1f(border_uniform_locations[BORDER_tint_opacity], tint_opacity);
glUniform1f(border_uniform_locations[BORDER_tint_premult], tint_premult);
glUniform1f(border_uniform_locations[BORDER_do_srgb_correction], do_srgb_correction);
glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height);
glUniform1fv(border_uniform_locations[BORDER_gamma_lut], 256, srgb_lut);
if (has_bgimage(w)) {

View File

@ -1130,6 +1130,7 @@ PYWRAP1(patch_global_colors) {
P(background); P(url_color);
P(mark1_background); P(mark1_foreground); P(mark2_background); P(mark2_foreground);
P(mark3_background); P(mark3_foreground);
P(bold_is_bright);
}
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;

View File

@ -39,6 +39,7 @@ typedef struct {
char_type *select_by_word_characters_forward;
color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color, tab_bar_background, tab_bar_margin_color;
color_type mark1_foreground, mark1_background, mark2_foreground, mark2_background, mark3_foreground, mark3_background;
bool bold_is_bright;
monotonic_t repaint_delay, input_delay;
bool focus_follows_mouse;
unsigned int hide_window_decorations;

View File

@ -111,6 +111,10 @@ string_capabilities = {
'civis': r'\E[?25l',
# Clear screen
'clear': r'\E[H\E[2J',
# Clear scrollback. This is disabled because the clear program on Linux by default, not as
# an option, uses it and nukes the scrollback. What's more this behavior was silently changed
# around 2013. Given clear is maintained as part of ncurses this kind of crap is no surprise.
# 'E3': r'\E[3J',
# Make cursor appear normal
'cnorm': r'\E[?12h\E[?25h',
# Carriage return

View File

@ -49,6 +49,7 @@ from .fast_data_types import (
CELL_PROGRAM,
CELL_SPECIAL_PROGRAM,
CURSOR_BEAM,
BOLD,
CURSOR_BLOCK,
CURSOR_UNDERLINE,
DCS,
@ -392,6 +393,7 @@ class LoadShaderPrograms:
STRIKE_SHIFT=STRIKETHROUGH,
DIM_SHIFT=DIM,
DECORATION_SHIFT=DECORATION,
BOLD_SHIFT=BOLD,
MARK_SHIFT=MARK,
MARK_MASK=MARK_MASK,
DECORATION_MASK=DECORATION_MASK,
@ -597,6 +599,7 @@ class Window:
self.default_title = os.path.basename(child.argv[0] or appname)
self.child_title = self.default_title
self.title_stack: Deque[str] = deque(maxlen=10)
self.user_vars: Dict[str, bytes] = {}
self.id: int = add_window(tab.os_window_id, tab.id, self.title)
self.clipboard_request_manager = ClipboardRequestManager(self.id)
self.margin = EdgeWidths()
@ -922,7 +925,27 @@ class Window:
self.override_title = title or None
self.title_updated()
def set_user_var(self, key: str, val: Optional[bytes]) -> None:
self.user_vars.pop(key, None) # ensure key will be newest in user_vars even if already present
if len(self.user_vars) > 64: # dont store too many user vars
oldest_key = next(iter(self.user_vars))
self.user_vars.pop(oldest_key)
if val is not None:
self.user_vars[key] = val
# screen callbacks {{{
def osc_1337(self, raw_data: str) -> None:
for record in raw_data.split(';'):
key, _, val = record.partition('=')
if key == 'SetUserVar':
from base64 import standard_b64decode
ukey, has_equal, uval = val.partition('=')
self.set_user_var(ukey, (standard_b64decode(uval) if uval else b'') if has_equal == '=' else None)
def desktop_notify(self, osc_code: int, raw_data: str) -> None:
if osc_code == 1337:
self.osc_1337(raw_data)
if osc_code == 777:
if not raw_data.startswith('notify;'):
log_error(f'Ignoring unknown OSC 777: {raw_data}')
@ -932,7 +955,6 @@ class Window:
if cmd is not None and osc_code == 99:
self.prev_osc99_cmd = cmd
# screen callbacks {{{
def use_utf8(self, on: bool) -> None:
get_boss().child_monitor.set_iutf8_winid(self.id, on)
@ -1004,7 +1026,7 @@ class Window:
'--ssh-connection-data', json.dumps(conn_data)
)
def send_signal_for_key(self, key_num: int) -> bool:
def send_signal_for_key(self, key_num: bytes) -> bool:
try:
return self.child.send_signal_for_key(key_num)
except OSError as err:

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -25,7 +25,7 @@ from urllib.parse import urlencode, urlparse
os.chdir(os.path.dirname(os.path.abspath(__file__)))
docs_dir = os.path.abspath('docs')
publish_dir = os.path.abspath(os.path.join('..', 'kovidgoyal.github.io', 'kitty'))
publish_dir = os.path.abspath(os.path.join('..', 'kittypatch.github.io', 'kitty'))
building_nightly = False
with open('kitty/constants.py') as f:
raw = f.read()
@ -101,7 +101,7 @@ def run_man(args: Any) -> None:
def run_html(args: Any) -> None:
call('make FAIL_WARN=1 "OPTS=-D analytics_id=G-XTJK3R7GF2" dirhtml', cwd=docs_dir)
call('make FAIL_WARN=1 "OPTS=-D analytics_id=G-XXXXXXXXXX" dirhtml', cwd=docs_dir)
add_old_redirects('docs/_build/dirhtml')
@ -154,6 +154,7 @@ def run_website(args: Any) -> None:
f.write(version)
shutil.copy2(os.path.join(docs_dir, 'installer.sh'), publish_dir)
os.chdir(os.path.dirname(publish_dir))
subprocess.check_call(['optipng', '-o7'] + glob.glob('kitty/_images/social_previews/*.png'))
subprocess.check_call(['git', 'add', 'kitty'])
subprocess.check_call(['git', 'commit', '-m', 'kitty website updates'])
subprocess.check_call(['git', 'push'])
@ -492,7 +493,7 @@ def safe_read(path: str) -> str:
@contextmanager
def change_to_git_master() -> Generator[None, None, None]:
stash_ref_before = safe_read('.git/refs/stash')
subprocess.check_call(['git', 'stash'])
subprocess.check_call(['git', 'stash', '-u'])
try:
branch_before = current_branch()
if branch_before != 'master':
@ -547,6 +548,7 @@ def main() -> None:
with change_to_git_master():
building_nightly = True
exec_actions(NIGHTLY_ACTIONS, args)
subprocess.run(['make', 'debug'])
return
require_git_master()
if args.action == 'all':

View File

@ -816,8 +816,6 @@ def compile_kittens(compilation_database: CompilationDatabase) -> None:
return kitten, sources, headers, f'kittens/{kitten}/{output}', includes, libraries
for kitten, sources, all_headers, dest, includes, libraries in (
files('unicode_input', 'unicode_names'),
files('diff', 'diff_speedup'),
files('transfer', 'rsync', libraries=('rsync',)),
):
final_env = kenv.copy()

View File

@ -80,7 +80,7 @@ _ksi_prompt=(
)
_ksi_main() {
builtin local ifs="$IFS"
builtin local ifs="$IFS" i
IFS=" "
for i in ${KITTY_SHELL_INTEGRATION[@]}; do
case "$i" in

View File

@ -24,7 +24,7 @@ exec_kitty() {
is_wrapped_kitten() {
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh themes diff"
wrapped_kittens="clipboard icat hyperlinked_grep ask hints unicode_input ssh themes diff show_key"
[ -n "$1" ] && {
case " $wrapped_kittens " in
*" $1 "*) printf "%s" "$1" ;;

View File

@ -140,11 +140,12 @@ func (self *Context) Prettify(text string) string {
return self.ref_hyperlink(val, "envvar-")
case "doc":
text, target := text_and_target(val)
if text == target {
no_title := text == target
target = strings.Trim(target, "/")
if title, ok := kitty.DocTitleMap[target]; ok {
if title, ok := kitty.DocTitleMap[target]; ok && no_title {
val = title + " <" + target + ">"
}
} else {
val = text + " <" + target + ">"
}
return self.ref_hyperlink(val, "doc-")
case "iss":

View File

@ -0,0 +1,70 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package tool
import (
"fmt"
"os"
"os/exec"
"golang.org/x/sys/unix"
"kitty/kittens/ask"
"kitty/tools/cli"
"kitty/tools/cli/markup"
"kitty/tools/utils"
)
var _ = fmt.Print
func ask_for_permission(script_path string) (response string, err error) {
opts := &ask.Options{Type: "choices", Default: "n", Choices: []string{"y;green:Yes", "n;red:No", "v;yellow:View", "e;magenta:Edit"}}
ctx := markup.New(true)
opts.Message = ctx.Prettify(fmt.Sprintf(
"Attempting to execute the script: :yellow:`%s`\nExecuting untrusted scripts can be dangerous. Proceed anyway?", script_path))
response, err = ask.GetChoices(opts)
return response, err
}
func confirm_and_run_shebang(args []string) (rc int, err error) {
script_path := args[len(args)-1]
if unix.Access(script_path, unix.X_OK) != nil {
response, err := ask_for_permission(script_path)
if err != nil {
return 1, err
}
switch response {
default:
return 1, fmt.Errorf("Execution of %s was denied by user", script_path)
case "v":
raw, err := os.ReadFile(script_path)
if err != nil {
return 1, err
}
cli.ShowHelpInPager(utils.UnsafeBytesToString(raw))
return confirm_and_run_shebang(args)
case "e":
exe, err := os.Executable()
if err != nil {
return 1, err
}
editor := exec.Command(exe, "edit-in-kitty", script_path)
editor.Stdin = os.Stdin
editor.Stdout = os.Stdout
editor.Stderr = os.Stderr
editor.Run()
return confirm_and_run_shebang(args)
case "y":
}
}
exe := utils.FindExe(args[0])
if exe == "" {
return 1, fmt.Errorf("Failed to find the script interpreter: %s", args[0])
}
err = unix.Exec(exe, args, os.Environ())
if err != nil {
rc = 1
}
return
}

View File

@ -11,6 +11,7 @@ import (
"kitty/kittens/hints"
"kitty/kittens/hyperlinked_grep"
"kitty/kittens/icat"
"kitty/kittens/show_key"
"kitty/kittens/ssh"
"kitty/kittens/themes"
"kitty/kittens/unicode_input"
@ -41,6 +42,8 @@ func KittyToolEntryPoints(root *cli.Command) {
ssh.EntryPoint(root)
// unicode_input
unicode_input.EntryPoint(root)
// show_key
show_key.EntryPoint(root)
// hyperlinked_grep
hyperlinked_grep.EntryPoint(root)
// ask
@ -51,6 +54,7 @@ func KittyToolEntryPoints(root *cli.Command) {
diff.EntryPoint(root)
// themes
themes.EntryPoint(root)
themes.ParseEntryPoint(root)
// __pytest__
pytest.EntryPoint(root)
// __hold_till_enter__
@ -63,4 +67,13 @@ func KittyToolEntryPoints(root *cli.Command) {
return
},
})
// __confirm_and_run_shebang__
root.AddSubCommand(&cli.Command{
Name: "__confirm_and_run_shebang__",
Hidden: true,
OnlyArgsAllowed: true,
Run: func(cmd *cli.Command, args []string) (rc int, err error) {
return confirm_and_run_shebang(args)
},
})
}

View File

@ -463,7 +463,7 @@ type ThemeMetadata struct {
Author string `json:"author"`
}
func parse_theme_metadata(path string) (*ThemeMetadata, map[string]string, error) {
func ParseThemeMetadata(path string) (*ThemeMetadata, map[string]string, error) {
var in_metadata, in_blurb, finished_metadata bool
ans := ThemeMetadata{}
settings := map[string]string{}
@ -507,7 +507,9 @@ func parse_theme_metadata(path string) (*ThemeMetadata, map[string]string, error
val = strings.TrimSpace(val)
switch key {
case "name":
if val != "The name of the theme (if not present, derived from filename)" {
ans.Name = val
}
case "author":
ans.Author = val
case "upstream":
@ -536,6 +538,7 @@ type Theme struct {
settings map[string]string
zip_reader *zip.File
is_user_defined bool
path_for_user_defined_theme string
}
func (self *Theme) Name() string { return self.metadata.Name }
@ -558,6 +561,13 @@ func (self *Theme) load_code() (string, error) {
}
self.code = utils.UnsafeBytesToString(data)
}
if self.is_user_defined && self.path_for_user_defined_theme != "" && self.code == "" {
raw, err := os.ReadFile(self.path_for_user_defined_theme)
if err != nil {
return "", err
}
self.code = utils.UnsafeBytesToString(raw)
}
return self.code, nil
}
@ -774,7 +784,7 @@ var camel_case_pat = (&utils.Once[*regexp.Regexp]{Run: func() *regexp.Regexp {
return regexp.MustCompile(`([a-z])([A-Z])`)
}}).Get
func theme_name_from_file_name(fname string) string {
func ThemeNameFromFileName(fname string) string {
fname = fname[:len(fname)-len(path.Ext(fname))]
fname = strings.ReplaceAll(fname, "_", " ")
fname = camel_case_pat().ReplaceAllString(fname, "$1 $2")
@ -806,14 +816,14 @@ func (self *Themes) Filtered(is_ok func(*Theme) bool) *Themes {
}
func (self *Themes) AddFromFile(path string) (*Theme, error) {
m, conf, err := parse_theme_metadata(path)
m, conf, err := ParseThemeMetadata(path)
if err != nil {
return nil, err
}
if m.Name == "" {
m.Name = theme_name_from_file_name(filepath.Base(path))
m.Name = ThemeNameFromFileName(filepath.Base(path))
}
t := Theme{metadata: m, is_user_defined: true, settings: conf}
t := Theme{metadata: m, is_user_defined: true, settings: conf, path_for_user_defined_theme: path}
self.name_map[m.Name] = &t
return &t, nil
@ -829,7 +839,13 @@ func (self *Themes) add_from_dir(dirpath string) error {
}
for _, e := range entries {
if !e.IsDir() && strings.HasSuffix(e.Name(), ".conf") {
if _, err = self.AddFromFile(filepath.Join(dirpath, e.Name())); err != nil {
path := filepath.Join(dirpath, e.Name())
// ignore files if they are the STDOUT of the current processes
// allows using kitten theme --dump-theme name > ~/.config/kitty/themes/name.conf
if utils.Samefile(path, os.Stdout) {
continue
}
if _, err = self.AddFromFile(path); err != nil {
return err
}
}

View File

@ -26,7 +26,7 @@ func TestThemeCollections(t *testing.T) {
"mooseCat": "Moose Cat",
"a_bC": "A B C",
} {
actual := theme_name_from_file_name(fname)
actual := ThemeNameFromFileName(fname)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("Unexpected theme name for %s:\n%s", fname, diff)
}
@ -36,7 +36,7 @@ func TestThemeCollections(t *testing.T) {
pt := func(expected ThemeMetadata, lines ...string) {
os.WriteFile(filepath.Join(tdir, "temp.conf"), []byte(strings.Join(lines, "\n")), 0o600)
actual, _, err := parse_theme_metadata(filepath.Join(tdir, "temp.conf"))
actual, _, err := ParseThemeMetadata(filepath.Join(tdir, "temp.conf"))
if err != nil {
t.Fatal(err)
}

View File

@ -66,8 +66,9 @@ type Loop struct {
style_ctx style.Context
atomic_update_active bool
// Suspend the loop restoring terminal state. Call the return resume function to restore the loop
Suspend func() (func() error, error)
// Suspend the loop restoring terminal state, and run the provided function. When it returns terminal state is
// put back to what it was before suspending unless the function returns an error or an error occurs saving/restoring state.
SuspendAndRun func(func() error) error
// Callbacks

View File

@ -105,6 +105,9 @@ type KeyEvent struct {
AlternateKey string
Text string
Handled bool
// The CSI string this key event was decoded from. Empty if not decoded from CSI.
CSI string
}
func (self *KeyEvent) String() string {
@ -133,6 +136,7 @@ func KeyEventFromCSI(csi string) *KeyEvent {
if len(csi) == 0 {
return nil
}
orig_csi := csi
last_char := csi[len(csi)-1:]
if !strings.Contains("u~ABCDEHFPQRS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) {
return nil
@ -140,28 +144,32 @@ func KeyEventFromCSI(csi string) *KeyEvent {
csi = csi[:len(csi)-1]
sections := strings.Split(csi, ";")
get_sub_sections := func(section string) []int {
get_sub_sections := func(section string, missing int) []int {
p := strings.Split(section, ":")
ans := make([]int, len(p))
for i, x := range p {
if x == "" {
ans[i] = missing
} else {
q, err := strconv.Atoi(x)
if err != nil {
return nil
}
ans[i] = q
}
}
return ans
}
first_section := get_sub_sections(sections[0])
second_section := make([]int, 0)
third_section := make([]int, 0)
first_section := get_sub_sections(sections[0], 0)
second_section := []int{}
third_section := []int{}
if len(sections) > 1 {
second_section = get_sub_sections(sections[1])
second_section = get_sub_sections(sections[1], 1)
}
if len(sections) > 2 {
third_section = get_sub_sections(sections[2])
third_section = get_sub_sections(sections[2], 0)
}
var ans = KeyEvent{Type: PRESS}
var ans = KeyEvent{Type: PRESS, CSI: orig_csi}
var keynum int
if val, ok := letter_trailer_to_csi_number_map[last_char]; ok {
keynum = val

View File

@ -0,0 +1,30 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package loop
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
)
var _ = fmt.Print
func TestKeyEventFromCSI(t *testing.T) {
test_text := func(csi string, expected, alternate string) {
ev := KeyEventFromCSI(csi)
if ev == nil {
t.Fatalf("Failed to get parse %#v", csi)
}
if diff := cmp.Diff(expected, ev.Text); diff != "" {
t.Fatalf("Failed to get text from %#v:\n%s", csi, diff)
}
if diff := cmp.Diff(alternate, ev.AlternateKey); diff != "" {
t.Fatalf("Failed to get alternate from %#v:\n%s", csi, diff)
}
}
test_text("121;;121u", "y", "")
test_text("121::122;;121u", "y", "z")
}

View File

@ -271,10 +271,8 @@ func (self *Loop) run() (err error) {
self.keep_going = true
self.pending_mouse_events = utils.NewRingBuffer[MouseEvent](4)
tty_read_channel := make(chan []byte)
tty_write_channel := make(chan *write_msg, 1) // buffered so there is no race between initial queueing and startup of writer thread
write_done_channel := make(chan IdType)
tty_reading_done_channel := make(chan byte)
self.wakeup_channel = make(chan byte, 256)
self.pending_writes = make([]*write_msg, 0, 256)
err_channel := make(chan error, 8)
@ -286,25 +284,49 @@ func (self *Loop) run() (err error) {
no_timeout_channel := make(<-chan time.Time)
finalizer := ""
w_r, w_w, err := os.Pipe()
var r_r, r_w *os.File
if err == nil {
var r_r, r_w, w_r, w_w *os.File
var tty_reading_done_channel chan byte
var tty_read_channel chan []byte
start_tty_reader := func() (err error) {
r_r, r_w, err = os.Pipe()
if err != nil {
w_r.Close()
w_w.Close()
return err
}
} else {
tty_read_channel = make(chan []byte)
tty_reading_done_channel = make(chan byte)
go read_from_tty(r_r, controlling_term, tty_read_channel, err_channel, tty_reading_done_channel)
return
}
err = start_tty_reader()
if err != nil {
return err
}
w_r, w_w, err = os.Pipe() // these are closed in the writer thread and the shutdown defer in this thread
if err != nil {
return err
}
self.QueueWriteString(self.terminal_options.SetStateEscapeCodes())
needs_reset_escape_codes := true
defer func() {
shutdown_tty_reader := func() {
// notify tty reader that we are shutting down
if r_w != nil {
r_w.Close()
close(tty_reading_done_channel)
r_w = nil
tty_reading_done_channel = nil
}
}
wait_for_tty_reader_to_quit := func() {
// wait for tty reader to exit cleanly
for range tty_read_channel {
}
}
defer func() {
shutdown_tty_reader()
if self.OnFinalize != nil {
finalizer += self.OnFinalize()
@ -318,13 +340,10 @@ func (self *Loop) run() (err error) {
// flush queued data and wait for it to be written for a timeout, then wait for writer to shutdown
flush_writer(w_w, tty_write_channel, write_done_channel, self.pending_writes, 2*time.Second)
self.pending_writes = nil
// wait for tty reader to exit cleanly
for range tty_read_channel {
}
wait_for_tty_reader_to_quit()
}()
go write_to_tty(w_r, controlling_term, tty_write_channel, err_channel, write_done_channel)
go read_from_tty(r_r, controlling_term, tty_read_channel, err_channel, tty_reading_done_channel)
if self.OnInitialize != nil {
finalizer, err = self.OnInitialize()
@ -333,27 +352,30 @@ func (self *Loop) run() (err error) {
}
}
self.Suspend = func() (func() error, error) {
self.SuspendAndRun = func(run func() error) (err error) {
write_id := self.QueueWriteString(self.terminal_options.ResetStateEscapeCodes())
needs_reset_escape_codes = false
err := self.wait_for_write_to_complete(write_id, tty_write_channel, write_done_channel, 2*time.Second)
if err != nil {
return nil, err
if err = self.wait_for_write_to_complete(write_id, tty_write_channel, write_done_channel, 2*time.Second); err != nil {
return err
}
shutdown_tty_reader()
wait_for_tty_reader_to_quit()
resume, err := controlling_term.Suspend()
if err != nil {
return nil, err
return err
}
return func() (err error) {
err = resume()
if err != nil {
return
if err = run(); err != nil {
return err
}
if err = start_tty_reader(); err != nil {
return err
}
if err = resume(); err != nil {
return err
}
write_id = self.QueueWriteString(self.terminal_options.SetStateEscapeCodes())
needs_reset_escape_codes = true
return self.wait_for_write_to_complete(write_id, tty_write_channel, write_done_channel, 2*time.Second)
}, nil
}
self.on_SIGTSTP = func() error {

View File

@ -5,7 +5,6 @@ package tui
import (
"errors"
"fmt"
"kitty/tools/utils"
"os"
"os/exec"
"path/filepath"
@ -15,10 +14,16 @@ import (
"github.com/shirou/gopsutil/v3/process"
"golang.org/x/sys/unix"
"kitty/tools/utils"
)
var _ = fmt.Print
var TmuxExe = (&utils.Once[string]{Run: func() string {
return utils.FindExe("tmux")
}}).Get
func tmux_socket_address() (socket string) {
socket = os.Getenv("TMUX")
if socket == "" {
@ -52,14 +57,21 @@ func tmux_socket_address() (socket string) {
var TmuxSocketAddress = (&utils.Once[string]{Run: tmux_socket_address}).Get
func tmux_command(args ...string) (c *exec.Cmd, stderr *strings.Builder) {
c = exec.Command(TmuxExe(), args...)
stderr = &strings.Builder{}
c.Stderr = stderr
return c, stderr
}
func tmux_allow_passthrough() error {
c := exec.Command("tmux", "show", "-Ap", "allow-passthrough")
c, stderr := tmux_command("show", "-Ap", "allow-passthrough")
allowed, not_allowed := errors.New("allowed"), errors.New("not allowed")
get_result := make(chan error)
go func() {
output, err := c.Output()
if err != nil {
get_result <- err
get_result <- fmt.Errorf("Running %#v failed with error: %w. STDERR: %s", c.Args, err, stderr.String())
} else {
q := strings.TrimSpace(utils.UnsafeBytesToString(output))
if strings.HasSuffix(q, " on") || strings.HasSuffix(q, " all") {
@ -77,7 +89,12 @@ func tmux_allow_passthrough() error {
if r != not_allowed {
return r
}
return exec.Command("tmux", "set", "-p", "allow-passthrough", "on").Run()
c, stderr = tmux_command("set", "-p", "allow-passthrough", "on")
err := c.Run()
if err != nil {
err = fmt.Errorf("Running %#v failed with error: %w. STDERR: %s", c.Args, err, stderr.String())
}
return err
case <-time.After(2 * time.Second):
return fmt.Errorf("Tmux command timed out. This often happens when the version of tmux on your PATH is older than the version of the running tmux server")
}

View File

@ -4,6 +4,7 @@ package utils
import (
"fmt"
"os"
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
@ -153,3 +154,50 @@ func Memset[T any](dest []T, pattern ...T) []T {
}
return dest
}
type statable interface {
Stat() (os.FileInfo, error)
}
func Samefile(a, b any) bool {
var sta, stb os.FileInfo
var err error
switch v := a.(type) {
case string:
sta, err = os.Stat(v)
if err != nil {
return false
}
case statable:
sta, err = v.Stat()
if err != nil {
return false
}
case *os.FileInfo:
sta = *v
case os.FileInfo:
sta = v
default:
panic(fmt.Sprintf("a must be a string, os.FileInfo or a stat-able object not %T", v))
}
switch v := b.(type) {
case string:
stb, err = os.Stat(v)
if err != nil {
return false
}
case statable:
stb, err = v.Stat()
if err != nil {
return false
}
case *os.FileInfo:
stb = *v
case os.FileInfo:
stb = v
default:
panic(fmt.Sprintf("b must be a string, os.FileInfo or a stat-able object not %T", v))
}
return os.SameFile(sta, stb)
}

View File

@ -2,6 +2,7 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import atexit
import glob
import os
import shlex
@ -9,8 +10,6 @@ import shutil
import subprocess
import sys
import tempfile
import atexit
if False:
dmg = sys.argv[-1]